Qt广告机服务器(上位机)
目录
- 功能
- 结构
- adSever.pro
- main.cpp
- tcp_MSG.h 共用Tcp传输信息
- adsever.h 服务器
- adsever.cpp 服务器
- addate.h 时间处理
- addate.cpp 时间处理
- adtcp.h 客户端Socket处理
- adtcp.cpp 客户端Socket处理
- client.h 客户端信息类
- client.cpp 客户端信息类
- admsglist.h 信息记录模块
- admsglist.cpp 信息记录模块
- weather.h 天气信息模块
- weather.cpp 天气信息模块
- ui
- 效果
- 源码
- 难点
下位机:Qt广告机客户端(下位机)
功能
- 客户端列表(下位机)
- 广告图片广播
- 天气信息多选点播
- 消息提醒广播
- 日期显示模块
可以显示jpg、jpeg、png、bmp。可以从电脑上拖动图到窗口并显示出来或者打开文件选择
重载实现dragEnterEvent(拖拽)、dropEvent(拖拽放下)、resizeEvent(窗口大小改变)
发送消息历史记录及右键复制消息
结构
adSever.pro
QT += core gui networkgreaterThan(QT_MAJOR_VERSION, 4): QT += widgetsCONFIG += c++11TARGET = adSever
TEMPLATE = appSOURCES += main.cpp\addate.cpp \admsglist.cpp \adsever.cpp \adtcp.cpp \client.cpp \weather.cppHEADERS += adsever.h \addate.h \admsglist.h \adtcp.h \client.h \tcp_MSG.h \weather.hFORMS += adsever.uiRESOURCES += \res.qrc
main.cpp
#include "adsever.h"
#include <QApplication>
#include <QDebug>
int main(int argc, char *argv[])
{QApplication a(argc, argv);// 判断当前运行环境是否为Linux或Windows。
#ifdef Q_OS_LINUXqDebug() << "Current OS is Linux";
#elif defined(Q_OS_WIN)qDebug() << "Current OS is Windows";
#elseqDebug() << "Unknown OS";
#endifAdSever w;w.show();return a.exec();
}
tcp_MSG.h 共用Tcp传输信息
#ifndef TCP_MSG_H
#define TCP_MSG_H
#include<QMetaType>
#define tcp_MSG_txt_NUM 256
#define tcp_MSG_city_NUM 32
#define tcp_MSG_weather_NUM 128
#define tcp_MSG_path_NUM 128
#define tcp_MSG_photo_NUM 1280*800
#pragma pack(1) //设置结构体为1字节对齐
typedef struct
{int type;// 消息类型char txt[tcp_MSG_txt_NUM];// 文字信息char city[tcp_MSG_city_NUM];// 城市char area[tcp_MSG_city_NUM];// 地区char weather[tcp_MSG_weather_NUM];// 天气char fileName[tcp_MSG_path_NUM];// 文件名int index; // 编号int allAd_Num;// 总数int fileSize;// 文件大小
}tcp_MSG;
#pragma pack() //结束结构体对齐设置
Q_DECLARE_METATYPE(tcp_MSG)
// 实现对象的元编程功能。它可以用来定义宏,类型和函数,以支持将元数据与类型关联起来。它还可以用来实现类型安全性,类型转换和序列化功能。
//宏来注册tcp_MSG类型enum MsgType{Init=0, // 初始化WEATHER, //天气MASSEGE,// 留言VIDEO, // 视频AD_add, // 添加广告AD_delete, // 删除广告Write_back,//回复
};#pragma pack(1) //设置结构体为1字节对齐
typedef struct
{int type;// 消息类型int state;// 状态 0不发送 1发送char id[32];// id
}tcp_backMSG;
#pragma pack() //结束结构体对齐设置
Q_DECLARE_METATYPE(tcp_backMSG)
//宏来注册tcp_backMSG类型#pragma pack(1) //设置结构体为1字节对齐
typedef struct
{int type;// 消息类型char photo[tcp_MSG_photo_NUM];
}tcp_Image;
#pragma pack() //结束结构体对齐设置
Q_DECLARE_METATYPE(tcp_Image)
//宏来注册tcp_Image类型#endif // TCP_MSG_H
adsever.h 服务器
#ifndef ADSEVER_H
#define ADSEVER_H#include <QMainWindow>
#include <QClipboard>
#include <QMessageBox>
#include <QFile>
#include <QFileInfo>
#include <QFileDialog>
#include <QMovie>
#include <QDragEnterEvent>
#include <QDropEvent>
#include <QMimeData>
#include "adtcp.h"
#include "addate.h"
#include "tcp_MSG.h"
#include "string.h"
#include "admsglist.h"
#include "weather.h"namespace Ui {
class AdSever;
}class AdSever : public QMainWindow
{Q_OBJECTpublic:explicit AdSever(QWidget *parent = 0);~AdSever();void dragEnterEvent(QDragEnterEvent *event)override;//拖进事件void dropEvent(QDropEvent *event)override;// 拖进放下事件void resizeEvent(QResizeEvent *event)override;//用于在窗口大小改变时处理事件QString GetLocalIP();// 获取本地IPvoid InitStatusBar();// 初始化底部状态栏private slots:void on_clear_msg_bt_clicked();// 清空void on_broast_msg_bt_clicked();// 广播void on_set_city_bt_clicked();//设置地区void GUI_WarningMsg(QString title,QString text,QString buttons,QString defaultButton);//设置警报void on_add_ad_bt_clicked();// 添加广告void on_delete_ad_bt_clicked();// 删除广告void qListWidget_clicked(const QModelIndex &index);//广告列表点击void on_ad_sendAdd_bt_clicked();// 发送添加void on_ad_sendDelete_bt_clicked();// 发送删除private:Ui::AdSever *ui;AdTcp *tcpsever;AdDate *date;AdMsgList *msgList;QPixmap pixmap;QVector<QString> photoPath;//存放照片相对路径的容器int num; //照片张数QLabel *mLocalIP;
};#endif // ADSEVER_H
adsever.cpp 服务器
#include "adsever.h"
#include "ui_adsever.h"AdSever::AdSever(QWidget *parent) :QMainWindow(parent),ui(new Ui::AdSever)
{ui->setupUi(this);this->setWindowIcon(QIcon(":/server.webp"));ui->centralWidget->setLayout(ui->horizontalLayout_5);ui->horizontalLayout_5->setContentsMargins(5,5,5,5);date = new AdDate(ui->date_lb);//时间date->start();//更新时间tcpsever = new AdTcp(ui->client_lw);// 客户端列表(下位机)connect(tcpsever, SIGNAL(GUI_WarningSignal(QString,QString,QString,QString)), this, SLOT(GUI_WarningMsg(QString,QString,QString,QString)));msgList=new AdMsgList(ui->msg_lw);// 已经发送消息列表,实现右键复制{// 广告模块this->setAcceptDrops(true);//设置允许向窗口拖入图片ui->video_lb->setAlignment(Qt::AlignCenter); //居中显示//自适应的label+pixmap充满窗口后,无法缩小只能放大ui->video_lb->setSizePolicy(QSizePolicy::Ignored,QSizePolicy::Ignored);// Ignored忽略//不显示行向滚动条,子项文本过长自动显示...ui->ad_lw->setHorizontalScrollBarPolicy(Qt::ScrollBarAlwaysOff);num=0; //照片张数connect(ui->ad_lw,SIGNAL(clicked(const QModelIndex &)),this,SLOT(qListWidget_clicked(const QModelIndex &)));// 内容是否自动缩放,参数true自动缩放ui->video_lb->setScaledContents(true);//显示图片的全部}InitStatusBar();// 初始化底部状态栏
}AdSever::~AdSever()
{delete ui;
}//拖进事件
void AdSever::dragEnterEvent(QDragEnterEvent *event)
{// 如果文件的后缀名是jpg、jpeg、bmp或png,则接受拖放事件,否则忽略拖放事件QStringList acceptedFileTypes;acceptedFileTypes.append("jpg");acceptedFileTypes.append("jpeg");acceptedFileTypes.append("bmp");acceptedFileTypes.append("png");// 用于检查拖放的数据是否包含URL,并且获取拖放事件中的URL数量==1if(event->mimeData()->hasUrls()&&event->mimeData()->urls().count()==1){// 获取拖放事件中的第一个URL的本地文件路径QFileInfo file(event->mimeData()->urls().at(0).toLocalFile());// 检查文件的后缀名是否在接受的文件类型列表中;(获取文件的后缀名,并将其转换为小写字母)if(acceptedFileTypes.contains(file.suffix().toLower())){event->acceptProposedAction();//表明用户可以在窗口部件上拖放对象[接受拖放事件的操作]}}
}// 拖进放下事件
void AdSever::dropEvent(QDropEvent *event)
{// 获取拖放事件中的第一个URL的本地文件路径QFileInfo file(event->mimeData()->urls().at(0).toLocalFile());qDebug()<<"绝对路径:"<<file.absoluteFilePath();//从文件中加载图片,参数file.absoluteFilePath()表示文件的绝对路径,load()返回一个bool值,表示是否加载成功if(pixmap.load(file.absoluteFilePath())){// 将图片缩放到指定大小,参数ui->label->size()表示缩放的大小,Qt::KeepAspectRatio表示保持图片的宽高比,Qt::SmoothTransformation表示使用平滑缩放算法ui->video_lb->setPixmap(pixmap.scaled(ui->video_lb->size(),Qt::KeepAspectRatio,Qt::SmoothTransformation));ui->videoname_lb->setText(file.absolutePath());//显示打开的文件的绝对路径,这不包括文件名。photoPath.append(file.absoluteFilePath());// 把图片的路径装到容器中QListWidgetItem *item = new QListWidgetItem(QIcon(file.absoluteFilePath()),file.fileName());//建立文件缩小图标item->setToolTip(file.fileName());// tip提示item->setTextAlignment(Qt::AlignCenter);//设置item项中的文字位置ui->ad_lw->addItem(item);//把图片相对路径显示到窗口中}else{// 错误消息框QMessageBox::critical(this,tr("Error"),tr("The image file count not be read"));}
}//用于在窗口大小改变时处理事件
int i=0;
void AdSever::resizeEvent(QResizeEvent *event)
{Q_UNUSED(event);//忽略编译器发出的警告,表明变量event未使用qDebug()<<"窗口大小改变:"<<i++;if(!pixmap.isNull()){qDebug()<<"";ui->video_lb->setPixmap(pixmap.scaled(ui->video_lb->size(),Qt::KeepAspectRatio,Qt::SmoothTransformation));}
}// 获取本地IP
QString AdSever::GetLocalIP()
{QList<QHostAddress> list=QNetworkInterface::allAddresses();foreach(QHostAddress address,list){if(address.protocol()==QAbstractSocket::IPv4Protocol){qDebug()<<address.toString();return address.toString();}}return "";
}// 初始化底部状态栏
void AdSever::InitStatusBar()
{mLocalIP=new QLabel(this);mLocalIP->setMinimumWidth(230);QString ip = GetLocalIP();mLocalIP->setText("本地IP:"+tr("<font color=\"red\">%1</font>").arg(ip));ui->statusBar->addWidget(mLocalIP);
}// 清空
void AdSever::on_clear_msg_bt_clicked()
{ui->msg_te->clear();
}// 广播
void AdSever::on_broast_msg_bt_clicked()
{// 获取文本内容QString info = ui->msg_te->toPlainText();if(!info.isEmpty())// 是否空的{tcp_MSG msg={};msg.type=MsgType::MASSEGE;strcpy(msg.txt,info.toUtf8().data());// memcpy(msg.txt,info.toUtf8().data(),info.toUtf8().length());memset(msg.city,0,sizeof(msg.city));memset(msg.area,0,sizeof(msg.area));memset(msg.weather,0,sizeof(msg.weather));qDebug()<<"广播发送信息:"<<info;tcpsever->broadcastMsg(msg);QListWidgetItem *item = new QListWidgetItem(QString(QTime::currentTime().toString("hh:mm:ss")+"\t"+info));item->setToolTip(info);// tip提示item->setTextAlignment(Qt::AlignLeft);//设置item项中的文字位置ui->msg_lw->addItem(item);ui->msg_te->clear();}
}//设置地区
void AdSever::on_set_city_bt_clicked()
{if(ui->client_lw->selectedItems().isEmpty()){QMessageBox::warning(this,"提示","请选择下位机");return;}if(ui->city_cb->currentText().isEmpty()||ui->distict_cb->currentText().isEmpty()){QMessageBox::warning(this,"提示","请选择城市和地区");return;}// 获取文本内容QString city = ui->city_cb->currentText();QString area = ui->distict_cb->currentText();QString weather = ui->weather_lb->text();tcp_MSG msg={};msg.type=MsgType::WEATHER;memset(msg.txt,0,sizeof(msg.txt));strcpy(msg.city,city.toUtf8().data());strcpy(msg.area,area.toUtf8().data());strcpy(msg.weather,weather.toUtf8().data());qDebug()<<"发送天气";tcpsever->MultiSelectUnicastMsg(msg);}//设置警报
void AdSever::GUI_WarningMsg(QString title, QString text, QString buttons, QString defaultButton)
{Q_UNUSED(buttons)//忽略编译器发出的警告,表明变量event未使用Q_UNUSED(defaultButton)QMessageBox::warning(this,title,text);return;
}// 添加广告
void AdSever::on_add_ad_bt_clicked()
{QFileDialog dialog(this);//文件选择窗口dialog.setNameFilter(tr("Images (*.jpg *.jpeg *.bmp *.png)"));// 过滤器dialog.setFileMode(QFileDialog::AnyFile);//设置文件模式(文件/文件夹):任意文件,无论是否存在QStringList fileNames;if (dialog.exec())fileNames = dialog.selectedFiles();// 存所有选择的文件if(!fileNames.isEmpty()){if(pixmap.load(fileNames[0])){qDebug()<<"文件名:"<<fileNames[0];ui->video_lb->setPixmap(pixmap.scaled(ui->video_lb->size(),Qt::KeepAspectRatio,Qt::SmoothTransformation));QFileInfo file(fileNames[0]);ui->videoname_lb->setText(file.absolutePath());//显示打开的文件的绝对路径,这不包括文件名。photoPath.append(file.absoluteFilePath());// 把图片的路径装到容器中QListWidgetItem *item = new QListWidgetItem(QIcon(file.absoluteFilePath()),file.fileName());//建立文件缩小图标item->setToolTip(file.fileName());// tip提示item->setTextAlignment(Qt::AlignCenter);//设置item项中的文字位置ui->ad_lw->addItem(item);//把图片相对路径显示到窗口中}}
}
// 删除广告
void AdSever::on_delete_ad_bt_clicked()
{int deleteNum = ui->ad_lw->row(ui->ad_lw->currentItem()); //获取当前点击的内容的行号if(deleteNum<0)return;QListWidgetItem *item=ui->ad_lw->takeItem(deleteNum);//删除该列表项delete item;//手工再释放该列表项占用的资源photoPath.takeAt(deleteNum);ui->video_lb->clear();qDebug()<<"删除图片:"<<deleteNum;
}//广告列表点击
void AdSever::qListWidget_clicked(const QModelIndex &index)
{Q_UNUSED(index);//忽略编译器发出的警告,表明变量event未使用num = ui->ad_lw->row(ui->ad_lw->currentItem()); //获取当前点击的内容的行号qDebug()<<"点击播放图片:"<<num;QString tempDir;tempDir.clear();tempDir=photoPath.at(num); //从容器中找到要播放的照片的相对路径pixmap.load(tempDir);// 更新全局图片ui->video_lb->setPixmap(pixmap.scaled(ui->video_lb->size(),Qt::KeepAspectRatio,Qt::SmoothTransformation));//显示图片
}// 发送添加
void AdSever::on_ad_sendAdd_bt_clicked()
{if(ui->client_lw->selectedItems().isEmpty()){QMessageBox::warning(this,"提示","请选择下位机");return;}if(ui->ad_lw->selectedItems().isEmpty()){QMessageBox::warning(this,"提示","请选择图片");return;}tcpsever->Ad_SendAction(MsgType::AD_add,photoPath.at(num),num,photoPath.count());
}// 发送删除
void AdSever::on_ad_sendDelete_bt_clicked()
{if(ui->client_lw->selectedItems().isEmpty()){QMessageBox::warning(this,"提示","请选择下位机");return;}if(ui->ad_lw->selectedItems().isEmpty()){QMessageBox::warning(this,"提示","请选择图片");return;}tcpsever->Ad_SendAction(MsgType::AD_delete,photoPath.at(num),num,photoPath.count());
}
addate.h 时间处理
#ifndef ADDATE_H
#define ADDATE_H#include <QTime>
#include <QDate>
#include <QLabel>
#include <QTimer>class AdDate : public QObject
{Q_OBJECT
public:AdDate(QLabel *_mlabel, QObject *parent = 0);~AdDate();void start();// 定时器开启
public slots:void updateTime();// 定时器1s超时执行一次
private:QLabel *mlabel;QTimer *mtimer;
};#endif // ADDATE_H
addate.cpp 时间处理
#include "addate.h"AdDate::AdDate(QLabel *_mlabel, QObject *parent):QObject(parent)
{mlabel = _mlabel;mtimer = new QTimer;connect(mtimer, SIGNAL(timeout()), this, SLOT(updateTime()));mlabel->setAlignment(Qt::AlignCenter);// 居中QFont font;font.setFamilies(QStringList("微软雅黑"));font.setPixelSize(30);mlabel->setFont(font);}AdDate::~AdDate()
{delete mtimer;
}void AdDate::start()
{mtimer->start(1000);
}void AdDate::updateTime()
{QString time = QTime::currentTime().toString("hh:mm:ss")+"\n"+QDate::currentDate().toString("yy/MM/dd ddd");mlabel->setText(time);
}
adtcp.h 客户端Socket处理
#ifndef ADTCP_H
#define ADTCP_H#include <QTcpServer>
#include <QTcpSocket>
#include <QHostAddress>
#include <QMessageBox>
#include <QList>
#include <QListWidget>
#include <QBuffer>
#include <QFileInfo>
#include <QTime>
#include <QNetworkInterface>
#include "client.h"
#include "tcp_MSG.h"class AdTcp : public QTcpServer
{Q_OBJECT
public:AdTcp(QListWidget *_client_lw, QObject *parent = 0);~AdTcp();void broadcastMsg(tcp_MSG msg);// 文字 广播下发void MultiSelectUnicastMsg(tcp_MSG msg);// 天气 多选单播下发void Ad_SendAction(int action,QString path,int index,int allAd_Num);// 广告发送操作signals:void GUI_WarningSignal(QString title,QString text,QString buttons,QString defaultButton);//设置警报
public slots:void newClient();// 新的客户端连接void read_back();//读取客户端上传IDvoid rmClient();//删除客户端private:QList<Client *> *client_list;QListWidget *client_lw;QByteArray sendImage;
};#endif // ADTCP_H\
adtcp.cpp 客户端Socket处理
#include "adtcp.h"
#include <QDebug>
#include<QVariant>// QTcpSocket会自动处理大小端问题AdTcp::AdTcp(QListWidget *_client_lw, QObject *parent) :QTcpServer(parent)
{//注册tcp_MSG类型qRegisterMetaType<tcp_MSG>("tcp_MSG");// 监听任意地址8888端口if(!listen(QHostAddress::Any, 8888)){//QMessageBox::warning(this, "服务器启动失败");close();}client_list = new QList<Client *>;client_lw =_client_lw;//设置多选项client_lw->setSelectionMode(QAbstractItemView::ExtendedSelection);// 每当有新的连接可用connect(this, SIGNAL(newConnection()), this, SLOT(newClient()));qDebug()<<"init tcp";// connect(this, SIGNAL(), this, SLOT(newClient()));// connect(this, SIGNAL(acceptError(QAbstractSocket::SocketError)), this, SLOT(newClient()));
}AdTcp::~AdTcp()
{delete client_list;close();
}// 新的客户端连接
void AdTcp::newClient()
{Client *new_client = new Client;// 与客户端链接,动态创建socket对象new_client->msocket = this->nextPendingConnection();// 等待连接的//作为已连接的QTcpSocket对象,返回下一个挂起连接/* 每当有新的输入数据时,就会发出这个信号。请记住,新传入的数据只报告一次;如果您不读取所有数据,这个类会缓冲数据,您可以稍后读取它,但是除非新数据到达,否则不会发出信号。*/connect(new_client->msocket, SIGNAL(readyRead()), this, SLOT(read_back()));// 该信号在套接字断开连接时发出connect(new_client->msocket, SIGNAL(disconnected()), this, SLOT(rmClient()));client_list->append(new_client);
}//读取客户端上传ID
void AdTcp::read_back()
{// 返回此信号的 发送对象QTcpSocket *getSocket=qobject_cast<QTcpSocket *>(sender());//读取缓冲区数据QByteArray buffer = getSocket->readAll();//client_list->last()->msocket->readAll();// 读取最后客户端(也就是最新的)tcp_backMSG *msg=(tcp_backMSG *)buffer.data(); //强转为结构体,需要用结构体指针接收qDebug()<<"消息类型"<<msg->type;if(msg->type==MsgType::Init&&msg->state==0){QString id(msg->id);client_list->last()->id = id;client_lw->addItem(id);// 添加项到 客户端列表(下位机)}else if(msg->type==MsgType::Write_back&&msg->state==1){qDebug()<<"收到回复";// 记录开始时间QTime startTime = QTime::currentTime();getSocket->write(sendImage);// 用于等待发送的数据被写入到网络套接字描述符中,这样就可以确保数据完全被发送出去,//该函数会阻塞程序继续执行,直到数据被完全发送出去if (getSocket->waitForBytesWritten()){getSocket->flush(); //释放socket缓存}// 记录结束时间QTime endTime = QTime::currentTime();// 计算运行时间int milsec = startTime.msecsTo(endTime);qDebug()<<"发送消耗时间"<<milsec<<"毫秒";}
}// 文字 广播下发
void AdTcp::broadcastMsg(tcp_MSG msg)
{QByteArray sendTcpData;//使用字节数组,将结构体转为字符数组,发送的是字符数组(数据在传输过程中都是byte类型的)//直接sizeof(senddata)内存会变小,设置了对齐方式解决sendTcpData.resize(sizeof(tcp_MSG));//将封装好的结构体转为QByteArray数组,因为传输都是Byte类型memcpy(sendTcpData.data(),&msg,sizeof(tcp_MSG));int count = client_list->size();qDebug()<<"广播下发数量:"<<count;for(int i = 0; i< count; i++){client_list->at(i)->msocket->write(sendTcpData);//往套接字缓存中写入数据,并发送client_list->at(i)->msocket->flush(); //释放socket缓存}
}// 天气 多选单播下发
void AdTcp::MultiSelectUnicastMsg(tcp_MSG msg)
{QByteArray sendTcpData;//使用字节数组,将结构体转为字符数组,发送的是字符数组(数据在传输过程中都是byte类型的)//直接sizeof(senddata)内存会变小,设置了对齐方式解决sendTcpData.resize(sizeof(tcp_MSG));//将封装好的结构体转为QByteArray数组,因为传输都是Byte类型memcpy(sendTcpData.data(),&msg,sizeof(tcp_MSG));if(client_list->size()!=client_lw->count()){emit GUI_WarningSignal("提示","客户端存储数量出错",NULL,NULL);return;}QList<QListWidgetItem *> selectedItems = client_lw->selectedItems();for (auto item : selectedItems){int index = client_lw->row(item);qDebug() << "选定 item:" << item->text();client_list->at(index)->msocket->write(sendTcpData);client_list->at(index)->msocket->flush();}
}// 广告发送操作
void AdTcp::Ad_SendAction(int action, QString path, int index, int allAd_Num)
{QByteArray sendTcpData;//使用字节数组,将结构体转为字符数组,发送的是字符数组(数据在传输过程中都是byte类型的)//直接sizeof(senddata)内存会变小,设置了对齐方式解决sendTcpData.resize(sizeof(tcp_MSG));tcp_MSG msg={};msg.type=action;QFileInfo file(path);strcpy(msg.fileName,file.fileName().toUtf8().data());msg.index=index;msg.allAd_Num=allAd_Num;if(action==MsgType::AD_add){QImage image(path);QByteArray byteArray;QBuffer buffer(&byteArray);buffer.open(QIODevice::WriteOnly);//获取文件的后缀名,并将其转换为大写字母qDebug()<<"图片格式:"<<file.suffix().toUpper().toStdString().c_str();image.save(&buffer,file.suffix().toUpper().toStdString().c_str()); //将图片保存为PNG/JPG等格式sendImage = byteArray.toBase64();qDebug()<<"图片size:"<<sendImage.size();msg.fileSize=sendImage.size();buffer.close();}else if(action==MsgType::AD_delete){}//将封装好的结构体转为QByteArray数组,因为传输都是Byte类型memcpy(sendTcpData.data(),&msg,sizeof(tcp_MSG));QList<QListWidgetItem *> selectedItems = client_lw->selectedItems();for (auto item : selectedItems){int index = client_lw->row(item);qDebug() << "发送 item:" << item->text();client_list->at(index)->msocket->write(sendTcpData);client_list->at(index)->msocket->flush();}
}// 删除客户端
void AdTcp::rmClient()
{qDebug()<<"客户端断开";// 返回此信号的 发送对象QTcpSocket *getSocket=qobject_cast<QTcpSocket *>(sender());// if(client_list->first()->msocket->isSequential())
// {
// client_lw->clear();
// }for(int i=0;i<client_list->count();i++){if(getSocket==client_list->at(i)->msocket){if(client_list->count()==client_lw->count()){QListWidgetItem *item=client_lw->takeItem(i);//删除该列表项delete item;//手工再释放该列表项占用的资源}client_list->takeAt(i);getSocket->deleteLater();//延时释放}}
}
client.h 客户端信息类
#ifndef CLIENT_H
#define CLIENT_H#include <QObject>
#include <QString>
#include <QTcpSocket>class Client
{// Q_OBJECT
public:explicit Client(QObject *parent = 0);QString id;QTcpSocket *msocket;};#endif // CLIENT_H
client.cpp 客户端信息类
#include "client.h"Client::Client(QObject *parent)
{Q_UNUSED(parent)//忽略编译器发出的警告,表明变量event未使用msocket = new QTcpSocket;
}
admsglist.h 信息记录模块
#ifndef ADMSGLIST_H
#define ADMSGLIST_H#include <QListWidget>
#include <QObject>
#include <QAction>
#include <QClipboard>
#include <QDebug>
#include <QApplication>
class AdMsgList : public QObject
{Q_OBJECT
public:AdMsgList(QListWidget *_mlistWidget, QObject *parent = 0);~AdMsgList();
private slots:void onCopyTriggered();// 触发Copy
private:QListWidget *mlistWidget;};#endif // ADMSGLIST_H
admsglist.cpp 信息记录模块
#include "admsglist.h"AdMsgList::AdMsgList(QListWidget *_mlistWidget, QObject *parent):QObject(parent)
{mlistWidget=_mlistWidget;//不显示行向滚动条,子项文本过长自动显示...mlistWidget->setHorizontalScrollBarPolicy(Qt::ScrollBarAlwaysOff);mlistWidget->setContextMenuPolicy(Qt::ActionsContextMenu);//设置右键菜单QAction *copyAction = new QAction("Copy", mlistWidget);copyAction->setShortcut(QKeySequence::Copy); //设置快捷键mlistWidget->addAction(copyAction);//连接action的triggered信号和槽函数connect(copyAction, SIGNAL(triggered()), this, SLOT(onCopyTriggered()));
}AdMsgList::~AdMsgList()
{}
// 触发Copy
void AdMsgList::onCopyTriggered()
{//获取当前actionQAction *action = qobject_cast<QAction*>(sender());if (action&&mlistWidget->currentItem()){//获取要复制的内容QString content = mlistWidget->currentItem()->toolTip();qDebug()<<mlistWidget->currentRow()<<"行,获取要复制的内容:"<<content;//将内容复制到剪贴板QClipboard *clipboard = QApplication::clipboard();clipboard->setText(content);}
}
weather.h 天气信息模块
#ifndef WEATHER_H
#define WEATHER_H#include <QObject>
#include <QLabel>
#include <QComboBox>class Weather : public QObject
{Q_OBJECT
public:explicit Weather(QLabel *_wlabel,QComboBox *_cityComboBox,QComboBox *_areaComboBox,QObject *parent = 0);signals:public slots:void showWeather(QString weather);private:QLabel *weather_label;QComboBox *city_comboBox;QComboBox *area_comboBox;
};#endif // WEATHER_H
weather.cpp 天气信息模块
#include "weather.h"Weather::Weather(QLabel *_wlabel,QComboBox *_cityComboBox,QComboBox *_areaComboBox,QObject *parent) :QObject(parent)
{weather_label = _wlabel;city_comboBox = _cityComboBox;area_comboBox = _areaComboBox;
}void Weather::showWeather(QString weather)
{weather_label->setText(weather);
}
ui
效果
源码
有道云:
难点
- QTcpSocket发送和接收使用 自定义 信息结构体,
结构体需要1字节对齐 ,参考Qt 利用TCP/IP socket通信 发送与接收结构体(简单通信协议解析)
- 发送
QByteArray sendTcpData;
//使用字节数组,将结构体转为字符数组,发送的是字符数组(数据在传输过程中都是byte类型的)
//直接sizeof(senddata)内存会变小,设置了对齐方式解决
sendTcpData.resize(sizeof(tcp_MSG));//将封装好的结构体转为QByteArray数组,因为传输都是Byte类型
memcpy(sendTcpData.data(),&msg,sizeof(tcp_MSG));socket->write(sendTcpData);
- 接收
//读取缓冲区数据
QByteArray buffer = readAll();tcp_MSG *msg=(tcp_MSG *)buffer.data(); //强转为结构体,需要用结构体指针接收
- 发送图片
QTcpSocket 的默认缓存区大小是 64KB(65536字节)
图片一般比较大,需要循环接收,校验发送长度和接收长度
因为QTcpSocket是一个基于字节流的套接字,它只能传输二进制数据。而图片文件是一种二进制文件,不能直接传输。因此,需要将图片文件转换为一种可传输的文本格式,如Base64编码。
Base64编码是一种将二进制数据转换为ASCII字符的编码方式。它将每3个字节转换为4个字符,因此可以将任何二进制数据转换为一种文本格式,方便传输
本项目发送图片,使用
先
服务器下发消息类型,客户端回复并开启图片接收; 服务器再
把图片发给 回复的客户端;
- 发送
*在adtcp.cpp的Ad_SendAction()中先下发消息类型
{QFileInfo file(path);QImage image(path);QByteArray byteArray;QBuffer buffer(&byteArray);buffer.open(QIODevice::WriteOnly);//获取文件的后缀名,并将其转换为大写字母image.save(&buffer,file.suffix().toUpper().toStdString().c_str()); //将图片保存为PNG/JPG等格式sendImage = byteArray.toBase64();msg.fileSize=sendImage.size();buffer.close();
}*在adtcp.cpp的read_back()中先下发消息类型
{// 返回此信号的 发送对象QTcpSocket *getSocket=qobject_cast<QTcpSocket *>(sender());//读取缓冲区数据QByteArray buffer = getSocket->readAll();//client_list->last()->msocket->readAll();// 读取最后客户端(也就是最新的)tcp_backMSG *msg=(tcp_backMSG *)buffer.data(); //强转为结构体,需要用结构体指针接收else if(msg->type==MsgType::Write_back&&msg->state==1){getSocket->write(sendImage);// 用于等待发送的数据被写入到网络套接字描述符中,这样就可以确保数据完全被发送出去,//该函数会阻塞程序继续执行,直到数据被完全发送出去if (getSocket->waitForBytesWritten()){getSocket->flush(); //释放socket缓存}}}
- 接收
* 在adsocket.cpp的readMsg()先回复并开启图片接收
{//读取缓冲区数据QByteArray buffer = readAll();tcp_MSG *msg=(tcp_MSG *)buffer.data(); //强转为结构体,需要用结构体指针接收needFileSize=msg->fileSize;// 需要接收图片大小QByteArray sendTcpData;//使用字节数组,将结构体转为字符数组,发送的是字符数组(数据在传输过程中都是byte类型的)//直接sizeof(senddata)内存会变小,设置了对齐方式解决sendTcpData.resize(sizeof(tcp_backMSG));tcp_backMSG backMsg={};strcpy(backMsg.id,id.toUtf8().data());backMsg.state=1;backMsg.type=MsgType::Write_back;//将封装好的结构体转为QByteArray数组,因为传输都是Byte类型memcpy(sendTcpData.data(),&backMsg,sizeof(tcp_backMSG));this->write(sendTcpData);// 回复
}* 在adsocket.cpp的readMsg()图片接收
{QByteArray buffer = readAll();qDebug()<<"需要接收大小:"<<needFileSize;currentReceiveSize+=buffer.size();currentReceiveByte+=buffer;//当前累计接收大小if(needFileSize==currentReceiveSize){qDebug()<<"图片接收完成";QByteArray Ret_bytearray = QByteArray::fromBase64(currentReceiveByte);QBuffer buffer(&Ret_bytearray);buffer.open(QIODevice::WriteOnly);QPixmap imageresult;imageresult.loadFromData(Ret_bytearray);QImage pic=imageresult.toImage();}
}
相关文章:

Qt广告机服务器(上位机)
目录功能结构adSever.promain.cpptcp_MSG.h 共用Tcp传输信息adsever.h 服务器adsever.cpp 服务器addate.h 时间处理addate.cpp 时间处理adtcp.h 客户端Socket处理adtcp.cpp 客户端Socket处理client.h 客户端信息类client.cpp 客户端信息类admsglist.h 信息记录模块admsglist.cp…...

SOA架构的理解
1. SOA概述 SOA(Service-Oriented Architecture,面向服务的架构)是一种在计算机环境中设计、开发、部署和管理离散模型的方法。SOA不是一种新鲜事物,它是在企业内部IT系统重复构建以及效率低下的背景下提出的。在SOA模型中&#x…...

如何选择一款数据库?
1主流数据库技术介绍常见的数据库模型主要分为SQL关系型数据库和NoSQL非关系型数据库。其中关系型数据库分为传统关系数据库和大数据数据库,非关系型数据库分为键值存储数据库、列存储数据库、面向文档数据库、图形数据库、时序数据库、搜索引擎存储数据库及其他&am…...

week2
蓝桥2 递归*树的遍历约数之和分形之城并查集亲戚连通块中点的数量*食物链银河英雄传说哈希笨拙的手指模拟散列表单调队列剪裁序列滑动窗口最大子序和KMP周期递归 *树的遍历 中序遍历: 遍历左子树,根节点,右子树 后序遍历:遍历左子树,右子树,根节点 一个二叉树,树中每个…...

JavaScript的学习
一、引言 1.1 JavaScript简介 JavaScript一种解释性脚本语言,是一种动态类型、弱类型、基于原型继承的语言,内置支持类型。它的解释器被称为JavaScript引擎,作为浏览器的一部分,广泛用于客户端的脚本语言,用来给HTML网…...

用gin写简单的crud后端API接口
提要使用gin框架(go的web框架)来创建简单的几个crud接口)使用技术: gin sqlite3 sqlx创建初始工程新建文件夹,创建三个子文件夹分别初始化工程 go mod如果没有.go文件,执行go mod tidy可能报错(warning: "all" matched no packages), 可以先不弄,只初始化模块就行(…...

CF大陆斗C战士(三)
文章目录[C. Good Subarrays](https://codeforces.com/problemset/problem/1398/C)题目大意题目分析code[C. Boboniu and Bit Operations](https://codeforces.com/problemset/problem/1395/C)题目大意题目分析code[C. Rings](https://codeforces.com/problemset/problem/1562/…...

TTS | 语音合成论文概述
综述系列2021_A Survey on Neural Speech Synthesis论文:2106.15561.pdf (arxiv.org)论文从两个方面对神经语音合成领域的发展现状进行了梳理总结(逻辑框架如图1所示):核心模块:分别从文本分析(textanalysi…...

HTML第5天 HTML新标签与特性
新标签与特性文档类型设定前端复习帮手W3Schoool常用新标签datalist标签,与input元素配合,定义选项列表fieldset元素新增input表单文档类型设定 document – HTML: 开发环境输入html:4s – XHTML: 开发环境输入html:xt – HTML5: 开发环境输入html:5 前…...

java ee 之进程
目录 1.进程的概念 2.进程管理 3.进程属性(pcb) 3.1pid 3.2内存指针 3.3文件描述符 3.4进程调度 3.4.1进程状态 3.4.2 进程的优先级 3.4.3进程的上下文 3.4.4进程的记账信息 5.进程间通信 1.进程的概念 一个运行起来的程序,就是进程 .exe是一个可执行文件(程序),双…...

Linux学习记录——십사 进程控制(1)
文章目录1、进程创建1、fork函数2、进程终止1、情况分类2、如何理解进程终止3、进程终止的方式3、进程等待1、进程创建 1、fork函数 fork函数从已存在进程中创建一个新进程,新进程为子进程,原进程为父进程。 #include <unistd.h> pid_t fork(vo…...

使用 create-react-app 脚手架搭建React项目
❀官网 1、安装脚手架:npm install -g create-react-app 2、查看版本:create-react-app -V !!!注意 Node版本必须是14以上,不然会报以下错误。 3、创建react项目(项目名不能包含大写字母&…...

inquirerjs
inquirerjs inquirerjs是一个用来实现命令行交互界面的工具集合。它帮助我们实现与用户的交互交流,比如给用户一个提醒,用户给我们一个答案,我们根据用户的答案来做一些事情,典型应用如plop等生成器工具。 npm install inquirer…...

[数据库]内置函数
●🧑个人主页:你帅你先说. ●📃欢迎点赞👍关注💡收藏💖 ●📖既选择了远方,便只顾风雨兼程。 ●🤟欢迎大家有问题随时私信我! ●🧐版权:本文由[你帅…...

shell基本知识
为什么学习和使用Shell编程 什么是Shell shell的起源 shell的功能 shell的分类 如何查看当前系统支持的shell? 如何查看当前系统默认shell? 驼峰语句 shell脚本的基本元素 shell脚本编写规范 shell脚本的执行方式 shell脚本的退出状态 …...

Http长连接和短连接
http1.0以前,默认使用的是短连接,客户端与服务器之间每进行一次http操作,就会建立一次连接,例如,打开一个网页,包括html文件,js,css,每获取一次资源,就需要进…...

[SQL Statements] 基本的SQL知识 之DDL针对表结构和表空间的基本操作
[SQL Statements] 基本的SQL知识 之DDL针对表结构和表空间的基本操作 什么是数据库的表以及表空间 在MySQL中,一个数据库可以包含多个表,每个表是由若干个列(column)和行(row)组成的。表是存储数据的基本…...

Git版本控制工具(详解)
Git版本控制工具 Git常见命令速查表 集中式版本控制 cvs和svn都是属于集中式版本控制系统 他们的主要特点是单一的集中管理服务器 保存所有文件的修订版本协同开发人员通过客户端连接到这台服务器 取出最新的文件或者提交更新 优点每个人都可以在一定程度上看到项目中的其他…...
408考研计算机之计算机组成与设计——知识点及其做题经验篇目2:指令系统
今天我们来讲一讲指令系统里面的知识点以及做题技巧 1、定义 考点1:指令定义 指令是指示计算机执行某种操作的命令,一台计算机的所有指令的集合构成该机的指令系统,也称为指令集。指令系统是指令集体系结构ISA中最核心的部分,ISA…...

Java语法中的方法引用::是个什么鬼?
1.函数式接口 函数式接口(Functional Interface)就是一个有且仅有一个抽象方法(通俗来说就是只有一个方法要去被实现,因此我们也能通过这个去动态推断参数类型),但是可以拥有多个非抽象方法的接口。函数式接…...

【使用vue init和vue create的区别以及搭建vue项目的教程】
vue init 是vue-cli2.x的初始化方式,可以使用github上面的一些模板来初始化项目 webpack是官方推荐的标准模板名 使用方式:vue init webpack 项目名称 例如使用github上面electron-vue的模板使用方式:vue init electron-vue 项目名称教程目…...

二、HTTP协议02
文章目录一、HTTP状态管理Cookie和Session二、HTTP协议之身份认证三、HTTP长连接与短连接四、HTTP中介之代理五、HTTP中介之网关六、HTTP之内容协商七、断点续传和多线程下载一、HTTP状态管理Cookie和Session HTTP的缺陷无状态。Cookie和Session就用来弥补这个缺陷的。 Cooki…...

免费Api接口汇总(亲测可用,可写项目)
免费Api接口汇总(亲测可用)1. 聚合数据2. 用友API3. 天行数据4. Free Api5. 购物商城6. 网易云音乐API7. 疫情API8. 免费Api合集1. 聚合数据 https://www.juhe.cn/ 2. 用友API http://iwenwiki.com/wapicovid19/ 3. 天行数据 https://www.tianapi.com…...

12.并发编程
1.并发并发:逻辑流在时间时重叠构造并发程序:进程:每个逻辑控制流是一个进程,由内核调度和维护进程有独立的虚拟地址空间,想要通信,控制流必须使用某种显式的进程间通信机制(IPC)I/O多路复用:程…...

C/C++指针与数组(一)
预备知识 1、数据的存储 2、基本内建类型 1)类型的大小 C offers a flexible standard with some guaranteed minimum sizes, which it takes from C: A short integer is at least 16 bits wide.An int integer is at least as big as short.A long integer is a…...

Android使用移动智能终端补充设备标识获取OAID
官网http://www.msa-alliance.cn/col.jsp?id120首先到官网注册账号,申请下载相关sdk和授权证书2.把 oaid_sdk_x.x.x.aar 拷贝到项目的 libs 目录,并设置依赖,其中x.x.x 代表版本号3.supplierconfig.json 拷贝到项目 assets 目录下࿰…...

极目智能与锐算科技达成战略合作,4D毫米波成像雷达助力智能驾驶落地
近日,智能驾驶方案提供商武汉极目智能技术有限公司(以下简称“极目智能”)宣布与毫米波成像雷达公司锐算(上海)科技有限公司(以下简称“锐算科技”)达成战略合作,双方将合作开发基于…...

OpenCV基础(一)
1.认识图像(彩色图中每一个像素点都包含三个颜色通道RGB,数值范围为0~255,0代表黑色,255代表白色) import cv2 #opencv 读取的格式为BGRimg cv2.imread(cat.png) #读取图像 cv2.imshow(cat, img) #显示图像img&#x…...

pinia 的使用(笔记)
文章目录1. Pinia 与 Vuex 的区别2. pinia 安装与搭建3. pinia 的使用3.1 基本使用3.2 订阅状态3.3 订阅 actions1. Pinia 与 Vuex 的区别 Pinia 是 Vue 的状态管理库,相当于 Vuex 取消了 mutations,取消了 Module 模块化命名空间现在的 pinia 采用的是…...

DolphinDB 机器学习在物联网行业的应用:实时数据异常率预警
数据异常率预警在工业安全生产中是一项重要工作,对于监控生产过程的稳定性,保障生产数据的有效性,维护生产设备的可靠性具有重要意义。随着大数据技术在生产领域的深入应用,基于机器学习的智能预警已经成为各大生产企业进行生产数…...