Qt广告机服务器(上位机)
功能
- 连接服务器(上位机)
- 广告图片播放模块
- 日期显示模块
- 天气显示模块
- 信息提示模块
有两张以上图片,自动滚动接收进度显示,删除提示(5s自动确认)
通过下发的地区获取天气信息
结构
adClient.pro
QT += core gui network
greaterThan(QT_MAJOR_VERSION, 4): QT += widgets
CONFIG += c++11
TARGET = adClient
TEMPLATE = app
SOURCES += main.cpp\
adclient.cpp \
addate.cpp \
adsocket.cpp \
weather.cpp \
rollmassege.cpp
HEADERS += adclient.h \
addate.h \
adsocket.h \
tcp_MSG.h \
weather.h \
rollmassege.h
FORMS += adclient.ui
RESOURCES += \
res.qrc
main.cpp
#include "adclient.h"
#include <QApplication>
#include <QDebug>
#include <QDir>
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
QString folderName = "pic";
//QString folderPath = QDir::currentPath() + "/" + folderName;
QString folderPath = QApplication::applicationDirPath() + "/" + folderName;
QDir folder(folderPath);
if (!folder.exists())
{
bool success = folder.mkpath("."); // 创建文件夹
if (success)
qDebug() << "文件夹创建成功";
else
qDebug() << "文件夹创建失败";
}
else
qDebug() << "文件夹已存在";
QDir::setCurrent(folderPath);// 设置文件生成路径
AdClient 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
adclient.h 客户端
#ifndef ADCLIENT_H
#define ADCLIENT_H
#include <QMainWindow>
#include "addate.h"
#include "adsocket.h"
#include "weather.h"
#include "rollmassege.h"
#include <QLabel>
#include <QProgressDialog>
#include "stdlib.h"
#include <QThread>
#include <QTimer>
#include <QFile>
#include <QMessageBox>
#include <QNetworkInterface>
#include <QTcpSocket>
#include <QHostAddress>
#include <QAbstractSocket>
#include <QLineEdit>
#include <QPushButton>
namespace Ui {
class AdClient;
}
class AdClient : public QMainWindow
{
Q_OBJECT
public:
explicit AdClient(QWidget *parent = 0);
~AdClient();
void saveFile();// 保存文件
void InitStatusBar();// 初始化底部状态栏
QString GetLocalIP();// 获取本地IP
public slots:
void showImage(QImage);// 图片显示
void showProgressBar(int currentProgress,int finish);//进度条显示
void autoPlayTimeOut();//自动播放
void GUI_WarningMsg(QString title,QString text,QString buttons,QString defaultButton);//设置警报
void connectToServer();// 链接到服务器
private:
Ui::AdClient *ui;
AdDate *date;
AdSocket *socket;
Weather *weater;
RollMassege *rollmsg;
QProgressDialog *progress;// 创建进度条
QTimer autoPlayTimer;
int index;
QLabel *mLocalIP;
QLabel *serverIP_lb;
QLineEdit *serverIP;
QLabel *serverPort_lb;
QLineEdit *serverPort;
QPushButton *connectTcp;
};
#endif // ADCLIENT_H
adclient.cpp 客户端
#include "adclient.h"
#include "ui_adclient.h"
#include <QDebug>
QMap<QString,QImage> qMapPicturePath;// 图片路径
//QTcpSocket 的默认缓存区大小是 64KB(65536字节)
AdClient::AdClient(QWidget *parent) :
QMainWindow(parent),
ui(new Ui::AdClient)
{
ui->setupUi(this);
this->setWindowIcon(QIcon(":/client.jpg"));
date = new AdDate(ui->date_lb);//时间
socket = new AdSocket;// TCP客户端
weater = new Weather(ui->weather_lb);// 接收天气信息绘制
rollmsg = new RollMassege(ui->msg_lb, ui->msg_lb);// 滚动信息
connect(socket, SIGNAL(sig_showWeather(QString,QString,QString)), weater, SLOT(showWeather(QString,QString,QString)));
connect(socket, SIGNAL(sig_showTxt(QString)), rollmsg, SLOT(showTxt(QString)));
connect(socket, SIGNAL(sig_showImage(QImage)), this, SLOT(showImage(QImage)));
connect(socket, SIGNAL(sig_showProgressBar(int,int)), this, SLOT(showProgressBar(int,int)),Qt::QueuedConnection);
connect(socket, SIGNAL(GUI_WarningSignal(QString,QString,QString,QString)), this, SLOT(GUI_WarningMsg(QString,QString,QString,QString)));
date->start();//更新时间
progress = nullptr;
autoPlayTimer.stop();
connect(&autoPlayTimer, SIGNAL(timeout()), this, SLOT(autoPlayTimeOut()));
InitStatusBar();// 初始化底部状态栏
}
AdClient::~AdClient()
{
delete socket;
delete ui;
}
// 保存文件
void AdClient::saveFile()
{
QString fileName=qMapPicturePath.lastKey();
qDebug()<<"lastKey:"<<fileName;
QImage image=qMapPicturePath.last();
image.save(fileName);
}
// 初始化底部状态栏
void AdClient::InitStatusBar()
{
mLocalIP=new QLabel(this);
mLocalIP->setMinimumWidth(230);
QString ip = GetLocalIP();
// mLocalIP->setText("本地IP:"+ip);
mLocalIP->setText("本地IP:"+tr("<font color=\"red\">%1</font>").arg(ip));
serverIP_lb=new QLabel(this);
serverIP_lb->setMinimumWidth(100);
serverIP_lb->setText("服务器IP:");
serverIP=new QLineEdit(this);
serverIP->setMinimumWidth(200);
serverIP->setInputMask("000.000.000.000");
serverPort_lb=new QLabel(this);
serverPort_lb->setMinimumWidth(60);
serverPort_lb->setText("Port:");
serverPort=new QLineEdit(this);
serverPort->setMinimumWidth(60);
serverPort->setPlaceholderText("8888");
connectTcp=new QPushButton("链接",this);
connectTcp->setMinimumWidth(100);
ui->statusBar->addWidget(mLocalIP);
ui->statusBar->addWidget(serverIP_lb);
ui->statusBar->addWidget(serverIP);
ui->statusBar->addWidget(serverPort_lb);
ui->statusBar->addWidget(serverPort);
ui->statusBar->addWidget(connectTcp);
connect(connectTcp, SIGNAL(clicked()), this, SLOT(connectToServer()));
}
// 获取本地IP
QString AdClient::GetLocalIP()
{
QList<QHostAddress> list=QNetworkInterface::allAddresses();
foreach(QHostAddress address,list)
{
if(address.protocol()==QAbstractSocket::IPv4Protocol)
{
qDebug()<<address.toString();
return address.toString();
}
}
return "";
}
// 图片显示
void AdClient::showImage(QImage image)
{
qDebug()<<"显示图片"<<__LINE__;
QPixmap pixmap=QPixmap::fromImage(image);
// 内容是否自动缩放,参数true自动缩放
ui->video_lb->setScaledContents(true);//显示图片的全部
// 将图片缩放到指定大小,参数ui->label->size()表示缩放的大小,Qt::KeepAspectRatio表示保持图片的宽高比,Qt::SmoothTransformation表示使用平滑缩放算法
ui->video_lb->setPixmap(pixmap.scaled(ui->video_lb->size(),Qt::KeepAspectRatio,Qt::SmoothTransformation));
qDebug()<<"图片数量:"<<qMapPicturePath.count();
qDebug()<<"定时器状态:"<<autoPlayTimer.isActive();
if(qMapPicturePath.count()>=2)
{
if(!autoPlayTimer.isActive()){
// 两张以上图片开启播放
autoPlayTimer.start(5000);
index=0;
}
}
else
{
autoPlayTimer.stop();
}
if(qMapPicturePath.count()>=1)
{
// 功能没问题,调试注释,避免多次保存
//saveFile();// 保存文件
}
}
//进度条显示
void AdClient::showProgressBar(int currentProgress, int finish)
{
qDebug()<<"进度条显示"<<__LINE__;
if (!progress)
{
//创建进度条
progress = new QProgressDialog("接收中....", "取消", 0, 100, this);
progress->setWindowModality(Qt::WindowModal);
progress->setFixedSize(this->width()*0.3,this->height()*0.3);
progress->setVisible(true);
}
//usleep(500);//在Windows环境下已被弃用
QThread::msleep(50);//休眠50毫秒
//更新进度条
progress->setValue(currentProgress * 100 / finish);
//如果任务已经完成,隐藏进度条
if (currentProgress == finish)
{
qDebug()<<"进度条显示完成"<<__LINE__;
progress->hide();
delete progress;
progress = nullptr;
}
}
//自动播放
void AdClient::autoPlayTimeOut()
{
QList<QString> keyList = qMapPicturePath.keys();//存放的就是QMap的key值
if(index>=qMapPicturePath.count())// 删除广告时有可能 >
index=0;
QImage image=qMapPicturePath.value(keyList.at(index));
qDebug()<<"自动播放:"<<keyList.at(index);
showImage(image);
index++;
}
//设置警报
void AdClient::GUI_WarningMsg(QString title, QString text, QString buttons, QString defaultButton)
{
Q_UNUSED(buttons)//忽略编译器发出的警告,表明变量event未使用
Q_UNUSED(defaultButton)
QMessageBox *box=new QMessageBox(QMessageBox::Warning,title,text,QMessageBox::Cancel,this);
QTimer::singleShot(5000,this,[=](){
if(!box->isHidden()&&box->isVisible())
{
box->accept();
}
});// 5s后必执行
box->exec();
return;
}
// 链接到服务器
void AdClient::connectToServer()
{
if(serverIP->text().isEmpty()||serverPort->text().isEmpty())
{
QMessageBox::warning(this,"提示","请输入IP或Port");
return;
}
socket->id=GetLocalIP();
socket->address=serverIP->text();
qDebug()<<"服务器IP:"<<socket->address;
socket->port=serverPort->text().toInt();
qDebug()<<"服务器Port:"<<socket->port;
qDebug()<<"是否已经连接:"<<socket->isValid();
if(socket->isValid())// 如果套接字有效并且可以使用,则返回true;否则返回false。
{
socket->disconnectFromHost();// 与服务器断开连接
// 无需再手动连接,只要更新IP和Port就行,AdSocket::conAgain会重新发起连接.
}
else
{
socket->connectToHost(QHostAddress(socket->address), socket->port);
}
qDebug()<<"是否已经连接:"<<socket->isValid();
}
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()));
}
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);
}
adsocket.h 客户端Socket处理
#ifndef ADSOCKET_H
#define ADSOCKET_H
#include <QTcpSocket>
#include <QHostAddress>
#include <QAbstractSocket>
#include <QAbstractSocket>
#include <QImage>
#include <QLabel>
#include <QBuffer>
#include <QTime>
#include "string.h"
#include "tcp_MSG.h"
class AdSocket : public QTcpSocket
{
Q_OBJECT
public:
explicit AdSocket(QObject *parent = 0);
~AdSocket();
QString id;
QString address;
int port;
int readMsgType;
signals:
void sig_showWeather(QString city,QString area,QString wt);// 发送天气信息
void sig_showTxt(QString tcp_Txt);// 发送文字信息
void sig_showImage(QImage tcp_image);// 发送文字信息
void sig_showProgressBar(int currentProgress, int finish);// 发送进度条信息
void GUI_WarningSignal(QString title,QString text,QString buttons,QString defaultButton);//设置警报
public slots:
void readMsg();// 接收信息
void conSuc(); // 成功建立连接
void conAgain();// 重新连接
void conAgain(QAbstractSocket::SocketError);// 重新连接
private:
int needFileSize;
int currentReceiveSize;
QByteArray currentReceiveByte;
int milsec;
QString fileName;
};
#endif // ADSOCKET_H
adsocket.cpp 客户端Socket处理
#include "adsocket.h"
#include <QDebug>
extern QMap<QString,QImage> qMapPicturePath;// 图片路径
// QTcpSocket会自动处理大小端问题
AdSocket::AdSocket(QObject *parent) :
QTcpSocket(parent)
{
//注册tcp_MSG类型
qRegisterMetaType<tcp_MSG>("tcp_MSG");
id = "0011";
/* 每当有新的输入数据时,就会发出这个信号。
请记住,新传入的数据只报告一次;如果您不读取所有数据,这个类会缓冲数据,您可以稍后读取它,但是除非新数据到达,否则不会发出信号。*/
connect(this, SIGNAL(readyRead()),this, SLOT(readMsg()));
//该信号在调用connectToHost()并成功建立连接之后发出。
connect(this, SIGNAL(connected()),this, SLOT(conSuc()));
// 该信号在套接字断开连接时发出
connect(this, SIGNAL(disconnected()),this, SLOT(conAgain()));
address="127.0.0.1";
port=8888;
//connectToHost(QHostAddress(address), port);// 默认连接127.0.0.1 8888
//该信号在错误发生后发出。socketError参数描述发生错误的类型
connect(this, SIGNAL(error(QAbstractSocket::SocketError)),this, SLOT(conAgain(QAbstractSocket::SocketError)));
readMsgType=MsgType::Init;
milsec=0;
}
AdSocket::~AdSocket()
{
}
// 接收信息
void AdSocket::readMsg()
{
//读取缓冲区数据
QByteArray buffer = readAll();
qDebug()<<"进来了"<<__LINE__;
if(readMsgType==MsgType::Init)
{
tcp_MSG *msg=(tcp_MSG *)buffer.data(); //强转为结构体,需要用结构体指针接收
qDebug()<<"进来了"<<__LINE__;
qDebug()<<"类型"<<msg->type;
switch (msg->type)
{
case WEATHER://天气
{
QString city=msg->city;
QString area=msg->area;
QString weather=msg->weather;
//weather="\t\t"+city+"\t"+area+"\n"+weather;
emit sig_showWeather(city,area,weather);// 发送天气信息
break;
}
case MASSEGE:// 留言
{
QString tcp_Txt=msg->txt;
qDebug()<<"文字信息:"<<tcp_Txt;
emit sig_showTxt(tcp_Txt);// 发送文字信息
break;
}
case VIDEO:// 视频
qDebug()<<"发送视频信息:";
break;
case AD_add://添加广告
{
qDebug()<<"添加广告"<<__LINE__;
qDebug()<<"文件名"<< QString(msg->fileName);
qDebug()<<"编号"<< msg->index;
qDebug()<<"总数"<< msg->allAd_Num;
needFileSize=msg->fileSize;
currentReceiveSize=0;
currentReceiveByte.clear();
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);// 回复
fileName.clear();
fileName=QString(msg->fileName);
readMsgType=MsgType::AD_add;
break;
}
case AD_delete://删除广告
{
qDebug()<<"删除广告"<<__LINE__;
qDebug()<<"文件名"<< QString(msg->fileName);
int success= qMapPicturePath.remove(QString(msg->fileName));
if(success)
{
if(qMapPicturePath.count()>=1)
{// 有图片
emit sig_showImage(qMapPicturePath.first());
}
else
{// 没有图片
QImage image(100, 100, QImage::Format_RGBA8888);
image.fill(QColor(0, 0, 0, 0));// 纯透明图片
emit sig_showImage(image);
}
emit GUI_WarningSignal("提示","删除广告成功",NULL,NULL);
}
else
{
emit GUI_WarningSignal("提示","删除广告失败",NULL,NULL);
}
break;
}
default:
qDebug()<<"";
break;
}
}
else if(readMsgType==MsgType::AD_add)
{
qDebug()<<"添加广告"<<__LINE__;
qDebug()<<"需要接收大小:"<<needFileSize;
// 记录开始时间
QTime startTime = QTime::currentTime();
currentReceiveSize+=buffer.size();
currentReceiveByte+=buffer;
qDebug()<<"当前接收大小:"<<currentReceiveSize;
emit sig_showProgressBar(currentReceiveSize,needFileSize);
// 记录结束时间
QTime endTime = QTime::currentTime();
// 计算运行时间
milsec += startTime.msecsTo(endTime);
if(needFileSize==currentReceiveSize)
{
qDebug()<<"图片接收完成";
startTime = QTime::currentTime();
QByteArray Ret_bytearray = QByteArray::fromBase64(currentReceiveByte);
QBuffer buffer(&Ret_bytearray);
buffer.open(QIODevice::WriteOnly);
QPixmap imageresult;
imageresult.loadFromData(Ret_bytearray);
qMapPicturePath.insert(fileName,imageresult.toImage());// 先插入键值
emit sig_showImage(imageresult.toImage());
readMsgType=MsgType::Init;
endTime = QTime::currentTime();
milsec += startTime.msecsTo(endTime);
qDebug()<<"接收消耗时间"<<milsec<<"毫秒";
}
}
}
// 成功建立连接
void AdSocket::conSuc()
{
QByteArray sendTcpData;
//使用字节数组,将结构体转为字符数组,发送的是字符数组(数据在传输过程中都是byte类型的)
//直接sizeof(senddata)内存会变小,设置了对齐方式解决
sendTcpData.resize(sizeof(tcp_backMSG));
tcp_backMSG msg={};
strcpy(msg.id,id.toUtf8().data());
msg.state=0;
msg.type=MsgType::Init;
//将封装好的结构体转为QByteArray数组,因为传输都是Byte类型
memcpy(sendTcpData.data(),&msg,sizeof(tcp_backMSG));
this->write(sendTcpData);
qDebug()<<"connect success";
}
// 重新连接
void AdSocket::conAgain()
{
qDebug()<<"套接字断开连接时重新连接";
abort();// 终止当前连接并重置套接字
connectToHost(QHostAddress(address), port);
}
// 重新连接
void AdSocket::conAgain(QAbstractSocket::SocketError error)
{
qDebug()<<"连接失败:"<<error;
readMsgType=MsgType::Init;
abort();// 终止当前连接并重置套接字
if(error==QAbstractSocket::ConnectionRefusedError)
{
connectToHost(QHostAddress(address), port);
}
}
weather.h 天气信息处理
#ifndef WEATHER_H
#define WEATHER_H
#include <QObject>
#include <QLabel>
#include <QDebug>
class Weather : public QObject
{
Q_OBJECT
public:
explicit Weather( QLabel *_label,QObject *parent = 0);
signals:
public slots:
void showWeather(QString city,QString area,QString weather);
private:
QLabel *label;
QString _city;
QString _area;
};
#endif // WEATHER_H
weather.cpp 天气信息处理
#include "weather.h"
Weather::Weather( QLabel *_label, QObject *parent) :
QObject(parent)
{
label = _label;
}
void Weather::showWeather(QString city,QString area,QString weather)
{
_city=city;
qDebug()<<"城市:"<<_city;
_area=area;
qDebug()<<"地区:"<<_area;
weather="\t\t"+_city+"\t"+_area+"\n\t"+weather;
label->setText(weather);
}
rollmassege.h 滚动信息处理
#ifndef ROLLMASSEGE_H
#define ROLLMASSEGE_H
#include <QLabel>
#include <QEvent>
#include <QTimer>
#include <QRect>
#include <QPainter>
#include <QFont>
class RollMassege : public QLabel
{
Q_OBJECT
public:
explicit RollMassege(QWidget *parent = 0);
explicit RollMassege(QLabel *_label, QWidget *parent = 0);
void paintEvent(QPaintEvent *event);
signals:
public slots:
void updateMsg();
void showTxt(QString tcp_Txt);
private:
QString txt;
QRect rect;
int offset;//偏移量
QTimer *timer;
};
#endif // ROLLMASSEGE_H
rollmassege.cpp 滚动信息处理
#include "rollmassege.h"
#include <QDebug>
RollMassege::RollMassege(QWidget *parent) :
QLabel(parent)
{
}
RollMassege::RollMassege(QLabel *_label, QWidget *parent) :
QLabel(parent)
{
rect = _label->geometry();
rect.setWidth(798);
rect.setHeight(80);
setGeometry(rect);
show();
qDebug()<<rect.width()<<" "<<rect.height();
offset = rect.width();//偏移量
txt = "暂无信息";
timer = new QTimer;
connect(timer, SIGNAL(timeout()), this, SLOT(updateMsg()));
timer->start(50);
}
// 绘画事件
void RollMassege::paintEvent(QPaintEvent *event)
{
Q_UNUSED(event); //忽略编译器发出的警告,表明变量event未使用
QPainter painter(this);
QFont font;
font.setPointSize(16);
painter.setFont(font);
painter.drawText(rect.x()+offset, rect.y()+30, txt);// 绘制文字,x+偏移量
}
void RollMassege::updateMsg()
{
offset--;
if(offset<0) offset = rect.width();
update();// 刷新,触发绘画事件paintEvent
}
void RollMassege::showTxt(QString tcp_Txt)
{
txt =tcp_Txt;
update();
}
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();
}
}