下位机: Qt广告机客户端(下位机)
功能
- 客户端列表(下位机)
- 广告图片广播
- 天气信息多选点播
- 消息提醒广播
- 日期显示模块
时间显示模块:使用自定义的AdDate类实现时钟的显示,支持动态更新时间;客户端管理模块:使用自定义的AdTcp类实现客户端管理(即下位机),新增客户端时自动添加到客户端列表中,在客户端断开连接时自动删除客户端,支持广播消息和多选单独发送消息;
天气查询模块:使用第三方API实现通过城市编码获取天气信息,通过Weather类实现,并将获取的天气信息显示在界面的一个标签中;
广告管理模块:使用QListWidget实现广告列表,支持拖拽添加、删除广告,支持双击播放广告,使用QPixmap显示图片,支持缩放,自适应窗口大小变化;
广告命令发送模块:当客户端需要更新广告列表时,可以发送特定的命令请求,主机收到后会根据命令类型执行相应的操作,如添加、删除广告等,通过Ad_SendAction函数实现。
可以显示jpg、jpeg、png、bmp。可以从电脑上拖动图到窗口并显示出来或者打开文件选择重载实现dragEnterEvent(拖拽)、dropEvent(拖拽放下)、resizeEvent(窗口大小改变)
发送消息历史记录及右键复制消息
结构
adSever.pro
QT += core gui network
greaterThan(QT_MAJOR_VERSION, 4): QT += widgets
CONFIG += c++11
TARGET = adSever
TEMPLATE = app
SOURCES += main.cpp\
addate.cpp \
admsglist.cpp \
adsever.cpp \
adtcp.cpp \
client.cpp \
weather.cpp
HEADERS += adsever.h \
addate.h \
admsglist.h \
adtcp.h \
client.h \
tcp_MSG.h \
weather.h
FORMS += adsever.ui
RESOURCES += \
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_LINUX
qDebug() << "Current OS is Linux";
#elif defined(Q_OS_WIN)
qDebug() << "Current OS is Windows";
#else
qDebug() << "Unknown OS";
#endif
AdSever 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_OBJECT
public:
explicit AdSever(QWidget *parent = 0);
~AdSever();
void dragEnterEvent(QDragEnterEvent *event)override;//拖进事件
void dropEvent(QDropEvent *event)override;// 拖进放下事件
void resizeEvent(QResizeEvent *event)override;//用于在窗口大小改变时处理事件
QString GetLocalIP();// 获取本地IP
void 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数量==1
if(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();//读取客户端上传ID
void 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()
{
//获取当前action
QAction *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
效果
源码
Gitee:08Qt-Advertising-Machine Qt广告机
难点
- 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();
}
}