目录
- 功能
- 结构
- 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();}
}