Qt广告机服务器(上位机)

news/2024/12/29 17:21:08/

目录

  • 功能
  • 结构
  • 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广告机客户端(下位机)

功能

  1. 客户端列表(下位机)
  2. 广告图片广播
  3. 天气信息多选点播
  4. 消息提醒广播
  5. 日期显示模块
    在这里插入图片描述

可以显示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

在这里插入图片描述

效果

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

源码

有道云:

难点

  1. 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();        //强转为结构体,需要用结构体指针接收
  1. 发送图片

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();}
}

http://www.ppmy.cn/news/31102.html

相关文章

【Java开发】JUC进阶 05:函数式接口、ForkJoin

1 四大函数式接口函数式接口&#xff1a;只有一个抽象方法的接口&#xff0c;只要是函数式接口&#xff0c;就可以用lambda表达式简化例如Runnable&#xff1a;FunctionalInterface public interface Runnable {public abstract void run(); }框架底层大量应用函数式接口&#…

7天收割10个offer,软件测试面试题 (项目经验问题+回答)(超级全细)

目录&#xff1a;导读前言一、Python编程入门到精通二、接口自动化项目实战三、Web自动化项目实战四、App自动化项目实战五、一线大厂简历六、测试开发DevOps体系七、常用自动化测试工具八、JMeter性能测试九、总结&#xff08;尾部小惊喜&#xff09;前言 1、简单介绍下最近做…

【动态规划】

动态规划1引言题目509. 斐波那契数70. 爬楼梯746. 使用最小花费爬楼梯小结53. 最大子数组和结语引言 蓝桥杯快开始了啊&#xff0c;自从报名后还没认真学过算法有(>﹏<)′&#xff0c;临时抱一下佛脚&#xff0c;一起学学算法。 题目 509. 斐波那契数 斐波那契数 &am…

【数据结构】单链表:头部操作我很行,插入也不用增容!!!

单链表 文章目录单链表1.链表1.1链表的概念和结构1.2链表的分类2.单链表的模拟实现2.1单链表的打印2.2单链表的尾插2.3单链表的头插2.4单链表的尾删2.5单链表的头删2.6单链表的查找2.7单链表的中间插入(在结点前插入)2.8单链表的中间删除(删除该结点)2.9单链表的中间插入(在结点…

机智云目前我用过最便捷的物联网快速开发方案

GE211 MINI DTU上手来看&#xff0c;是一款尺寸比较小巧的模块&#xff0c;适合放置在几乎所有白色家电中&#xff0c;通过ph2.0端子&#xff08;注意不要买错&#xff09;引出了5v、gnd、tx、rx。可以说是非常方便了。下面正式开始我们的接入流程&#xff1a;首先注册一个机智…

Elasticsearch 核心技术(六):内置的 8 种分词器详解 + 代码示例

❤️ 博客主页&#xff1a;水滴技术 &#x1f680; 支持水滴&#xff1a;点赞&#x1f44d; 收藏⭐ 留言&#x1f4ac; &#x1f338; 订阅专栏&#xff1a;大数据核心技术从入门到精通 文章目录一、内置分词器1. Standard&#xff08;标准分词器&#xff09;英文示例中文示例…

xcode14安装swift package设置github账户token

这里写目录标题登录github账户,复制token打开xcode添加github账户选择swift package登录github账户,复制token 登录github点击上面菜单自己的头像,settings->Developer settings->Personal access tokens->Tokens (classic)->Generate new token (classic) Note名…

剑指 Offer 16. 数值的整数次方

剑指 Offer 16. 数值的整数次方 难度&#xff1a;middle\color{orange}{middle}middle 题目描述 实现 pow(x, n) &#xff0c;即计算 x 的 n 次幂函数&#xff08;即&#xff0c;x^{n}&#xff09;。不得使用库函数&#xff0c;同时不需要考虑大数问题。 示例 1&#xff1a;…