完整教程:QT示例 使用QTcpSocket和QTcpServer类实现TCP的自定义消息头、消息体通信示例

news/2025/10/1 19:09:37/文章来源:https://www.cnblogs.com/wzzkaifa/p/19122709

最近项目中用到了TCP通信实时交互数据,在之前只是简单了解过,于是拿项目练手的同时又仔细的研究了一下,这里简单做个总结:
在实现QTcpSocket客户端QTcpServer服务端数据交互的时候,大多数都是使用JSON或者XML字符串然后解析成结构体获取数据,
并没有像Modbus协议那样使用Modbus消息头来规范数据头,
于是我就想着自己定义一个结构体作为TCP通信数据的消息头,剩下的数据作为消息体,
也就是固定格式报文数据…

目录导读

    • QT TCP通信
      • TCP客户端 QTcpSocket类
      • TCP服务端 QTcpServer类
    • 自定义消息头结构
      • 首先 定义一个TcpHeader结构体
      • TcpHeader结构体 转QByteArray数据
      • QByteArray数据转TcpHeader结构体
    • 接口封装源码 与 界面效果
      • 使用Pimpl模式 定义 服务端代码接口 源码
        • 定义TCPEDIServer.h
        • 私有类TCPEDIServerPrivate 实现
        • 实现TCPEDIServer.cpp
      • 服务端界面效果
      • 使用Pimpl模式 定义 客户端代码接口 源码
        • 定义TCPEDIClient.h
        • 私有类TCPEDIClientPrivate 实现
        • 实现TCPEDIClient.cpp
      • 客户端界面效果

QT TCP通信

以前我了解TCP的时候描述里面说
连接建立:3次握手
连接关闭:4次挥手
数据交互:建立连接后可以进行多次数据收发之类的

这些都用不到,都封装好了,只需要声明两个类,
通过IP地址和端口建立连接,然后通过Write写入数据交互就行了。
Qt 使用QTcpSocket类和QTcpServer类实现TCP通信的案例有很多,
这里就不过多描述,只简单介绍下常用的方法或信号。

QTcpSocket类用于与服务端链接通信,在使用时只需要绑定一些信号调用方法就能与服务端进行数据交互
实际使用主要需要以下信号或方法:
QTcpSocket::readyRead() 当网络数据到达socket缓冲区并可供读取时触发信号
QTcpSocket::disconnected 当连接被关闭或断开时触发信号
QTcpSocket::connected 当成功建立TCP连接后触发信号
connectToHost(const QHostAddress &address, quint16 port, OpenMode mode = ReadWrite) 开始建立链接.
bool waitForConnected(int msecs = 30000) 等待远程链接完成

QTcpSocket* TcpClient=new QTcpSocket();
//! 获取服务器传来的数据
QObject::connect(TcpClient, &QTcpSocket::readyRead, [this] (){});
//! 远程服务端断开或者本地断开连接
QObject::connect(TcpClient, &QTcpSocket::disconnected, [this](){});
//! 与远程服务端建立连接时
QObject::connect(TcpClient, &QTcpSocket::connected, [this](){});
//! 地址转换
const QHostAddress AddressHost = QHostAddress(服务端ip地址);
const unsigned short port = Port.toInt();
//连接服务器
TcpClient->connectToHost(AddressHost, port);
//! 等待建立连接
if (!TcpClient->waitForConnected(OutTime))
return false;
if(!TcpClient->isValid())
return false;
return true;
  • TCP服务端 QTcpServer类

QTcpServer类 本身在实现时,并不能直接与 QTcpSocket客户端收发数据,
是通过获取到建立的QTcpSocket类变量,保存到列表表中进行数据交互的
主要用到QTcpServer::newConnection 信号获取到新链接的QTcpSocket客户端
bool listen(const QHostAddress &address = QHostAddress::Any, quint16 port = 0);监听ip地址端口,建立服务。

  • 代码示例:
TcpServer=new QTcpServer();
//监听到新的客户端连接请求
QObject::connect(TcpServer, &QTcpServer::newConnection, [this]() {
while (TcpServer->hasPendingConnections())
{
//nextPendingConnection返回下一个挂起的连接作为已连接的QTcpSocket对象
QTcpSocket* socket = TcpServer->nextPendingConnection();
//收到数据,触发readyRead
QObject::connect(socket, &QTcpSocket::readyRead, [this, socket] {
});
//错误信息
QObject::connect(socket, &QAbstractSocket::errorOccurred, [this, socket](QAbstractSocket::SocketError type) {
});
//连接断开,销毁socket对象,这是为了开关server时socket正确释放
QObject::connect(socket, &QTcpSocket::disconnected, [this, socket] {
});
// 将连接的客户端保存到队列
// TcpSocketList.append(socket) ;
}
});
QHostAddress* address = new QHostAddress(QHostAddress::Any);
bool bResult = TcpServer->listen(*address, TcpPort);//监听所有ip和端口

接下来开始上正菜了…


自定义消息头结构

要自定义消息头,消息体通信,最重要的是消息头这个结构体与
QByteArray数据直接的快速转换,这就涉及到把数据按固定字节位数处理。
涉及到字节对齐。

定义一个包含 设备标识请求标识处理类型处理的状态 的结构体,以及剩下的消息体,
其中的标识为char类型指定字节大小,
处理类型都固定位int类型占4字节,
再使用#pragma pack(push, 1) #pragma pack( pop ) 对齐字节

#pragma pack(push, 1)
//! TCP 传递数据时 默认前67字节的数据格式
typedef struct
{
//! 设备标识 guid 或者其他字符串
char   TcpDeviceId[37];        //! 设备标识
char     Timestamp[18];		   //! 请求标识 yyyyMMddHHmmsszzz
int               type;        //! 处理类型
int             reType;        //! 处理的状态  0失败 1成功
//QString         Node;        //! 剩余字节为文本内容字符 如有需要换成JSON也行
}TcpHeader;
#pragma pack( pop )
#define TN(_V_) (_V_==TCPOK?"TCPOK":"TCPNG")
enum TcpreType
{
TCPNG=0,
TCPOK
};
#define TD(_V_) (_V_==NOTARIZE?"NOTARIZE":"DOCUMENT")
enum Tcptype
{
DEFAULT=0,
NOTARIZE,  //! 通信确认
DOCUMENT   //! 文本内容
};

前面67个字节固定,后面剩下的所有数据都可以作为消息体,只要前面的消息头数据符合规范,那这里面的数据就肯定是有效的。

  • TcpHeader结构体 转QByteArray数据

将一个结构体转QByteArray数据
除了要注意编码格式固定(这里固定Utf-8)外,
还需要
使用memset(&header, 0, sizeof(TcpHeader)); 或者
ZeroMemory(&header, sizeof(TcpHeader));
对整个结构体数据的置零:
要不然混杂的数据可能导致数据解析失败!

  • 代码示例:
//! 初始化消息头
QString identify=QDateTime::currentDateTime().toString("yyyyMMddhhmmsszzz");
TcpHeader header;
memset(&header, 0, sizeof(TcpHeader));
memcpy(header.TcpDeviceId, DriveID.toUtf8().constData(), 37);
header.TcpDeviceId[36] = '\0';
memcpy(header.Timestamp, identify.toUtf8().constData(), 18);
header.Timestamp[17] = '\0';
header.reType = TCPOK;
header.type = type;
//! TcpHeader结构体 转QByteArray数据
QByteArray packet(reinterpret_cast<const char*>(&header), sizeof(TcpHeader));//添加消息体 dataconst QByteArray send_data = data.toUtf8();packet.append(send_data);//! 写入客户端//TcpClient->write(packet);
  • QByteArray数据转TcpHeader结构体

QByteArray数据 转成结构体数据
就可以直接使用 reinterpret_cast 强转结构体.

  • 代码示例:
//!QByteArray data;
const TcpHeader* header = reinterpret_cast<const TcpHeader*>(data.constData());qDebug()<<QString("获取到服务器传来数据  TCPHeader -> <br/>""DeviceId ->%1 <br/> ""identify ->%2 <br/> ""type     ->%3 <br/> ""reType   ->%4 <br/> ""Node     ->%5 <br/> ").arg(QString::fromUtf8(QByteArray(header->TcpDeviceId))).arg(QString::fromUtf8(QByteArray(header->Timestamp))).arg(TN(header->type)).arg(TD(header->reType)).arg(QString::fromUtf8(data.mid(sizeof(TcpHeader), -1)));

接口封装源码 与 界面效果

在界面上直接使用QTcpServer变量显得捞,
这里对QTcpServer服务端整体进行封装
使用QT的Pimpl设计模式:

  • 定义TCPEDIServer.h

对外的接口和方法需要的不多,主要还是内部实现的封装
完整代码:

class TCPEDI_EXPORT TCPEDIServerPrivate;
//! Tcp服务端
class TCPEDI_EXPORT TCPEDIServer:public QObject
{
Q_OBJECT
public:
TCPEDIServer(QObject* parent=nullptr);
~TCPEDIServer();
//! 建立连接
bool Init(QString Port);
void Unit();
bool isListening();
//! 向客户端发送消息
bool WriteDataTo(QString DriveId,QString text);
Q_SIGNALS:
//! 输出日志
void SendMess(QString mess,int type);
//! Tcp的连接列表发生改变 QPair<设备ID,IP地址>void TcpClientListUpdate(QPair<QString,QString> tcpclients,bool Connected);//! 服务的开始/结束void ServiceInitiated(bool bol);private:QScopedPointer<TCPEDIServerPrivate> d_ptr;Q_DECLARE_PRIVATE(TCPEDIServer)};
  • 私有类TCPEDIServerPrivate 实现

私有类包含了QTcpServer服务与链接的QTcpSocket客户端列表,
将具体的调用和功能实现都放在了私有类,避免接口的复杂性,同时不可见。
完整代码:

//! TCPEDIServer私有类 用于实现相关方法内容
class TCPEDIServerPrivate
{
TCPEDIServer* q_ptr;
Q_DECLARE_PUBLIC(TCPEDIServer)
public:
TCPEDIServerPrivate();
~TCPEDIServerPrivate();
//! 建立连接
bool Init();
void Unit();
//! 解析数据
void Analysis_Data(QTcpSocket*,QByteArray data);
//! 写入数据
bool Write_Data(int type,QString data,QString TDriveid);
bool isListening();
private:
//! tcp客户端列表
QMap<QString,QTcpSocket*> TcpClientMap;//! tcp服务端QTcpServer* TcpServer=nullptr;int TcpPort;};TCPEDIServerPrivate::TCPEDIServerPrivate(){TcpServer=new QTcpServer();//监听到新的客户端连接请求QObject::connect(TcpServer, &QTcpServer::newConnection, [this]() {while (TcpServer->hasPendingConnections()){//nextPendingConnection返回下一个挂起的连接作为已连接的QTcpSocket对象//套接字是作为服务器的子级创建的,这意味着销毁QTcpServer对象时会自动删除该套接字。//最好在完成处理后显式删除该对象,以避免浪费内存。//返回的QTcpSocket对象不能从另一个线程使用,如有需要可重写incomingConnection().QTcpSocket* socket = TcpServer->nextPendingConnection();emit q_ptr->SendMess(QString(" %1 TcpSocket Connected!  ->").arg(socket->peerAddress().toString()), TCPOK);//关联相关操作的信号槽//收到数据,触发readyReadQObject::connect(socket, &QTcpSocket::readyRead, [this, socket] {//没有可读的数据就返回if (socket->bytesAvailable() <= 0)return;QByteArray networkData = socket->readAll();if (networkData.size() < sizeof(TcpHeader)){emit q_ptr->SendMess(QString("从地址[%1:%2] 传入数据格式小于%3字节!不处理跳过!").arg(socket->peerAddress().toString()).arg(socket->peerPort()).arg(sizeof(TcpHeader)), TCPOK);return;}Analysis_Data(socket, networkData);});//错误信息QObject::connect(socket, &QAbstractSocket::errorOccurred, [this, socket](QAbstractSocket::SocketError type) {QMetaEnum metaEnum = QMetaEnum::fromType<QAbstractSocket::SocketError>();emit q_ptr->SendMess(QString("%2 %1 TcpSocket ErrorOccurred!  ->").arg(metaEnum.valueToKey(type)).arg(socket->peerAddress().toString()), TCPNG);});//连接断开,销毁socket对象,这是为了开关server时socket正确释放QObject::connect(socket, &QTcpSocket::disconnected, [this, socket] {emit q_ptr->SendMess(QString("%1 TcpSocket Disconnected!  ->").arg(socket->peerAddress().toString()), TCPNG);socket->deleteLater();emit q_ptr->TcpClientListUpdate(QPair<QString,QString>(socket->property("TcpToken").toString(),socket->peerAddress().toString()),false);TcpClientMap.remove(socket->property("TcpToken").toString());});// TcpSocketList.append(socket)0;}});qRegisterMetaType<QPair<QString,QString>>("QPair<QString,QString>");}TCPEDIServerPrivate::~TCPEDIServerPrivate(){}bool TCPEDIServerPrivate::isListening(){return TcpServer->isListening();}bool TCPEDIServerPrivate::Init(){//! 初始化QHostAddress* address = new QHostAddress(QHostAddress::Any);bool bResult = TcpServer->listen(*address, TcpPort);//监听所有ip和6677端口if (!bResult)return TCPNG;emit q_ptr->ServiceInitiated(true);return TCPOK;}void TCPEDIServerPrivate::Unit(){//停止服务TcpServer->close();//! 清理客户端列表QStringList TDriveIdKeys=TcpClientMap.keys();foreach (QString key, TDriveIdKeys) {TcpClientMap[key]->disconnectFromHost();// TcpClientMap[key]->close();if(!IsNotNull(TcpClientMap[key]))continue;if (TcpClientMap[key]->state() != QAbstractSocket::UnconnectedState) {TcpClientMap[key]->abort();}}TcpClientMap.clear();emit q_ptr->ServiceInitiated(false);}void TCPEDIServerPrivate::Analysis_Data(QTcpSocket* tcpchlient,QByteArray data){const TcpHeader* netStruct = reinterpret_cast<const TcpHeader*>(data.constData());emit q_ptr->SendMess(QString("获取到数据:  TCPHeader -> <br/>""DeviceId ->%1 <br/> ""identify ->%2 <br/> ""type     ->%3 <br/> ""reType   ->%4 <br/> ""Node     ->%5 <br/> ").arg(QString::fromUtf8(QByteArray(netStruct->TcpDeviceId))).arg(QString::fromUtf8(QByteArray(netStruct->Timestamp))).arg(TN(netStruct->type)).arg(TD(netStruct->reType)).arg(QString::fromUtf8(data.mid(sizeof(TcpHeader)),-1)), TCPOK);if (netStruct->type == NOTARIZE){QString DrivatID=QString::fromUtf8(QByteArray(netStruct->TcpDeviceId)).toUpper();if (tcpchlient->property("TcpToken").isNull()){tcpchlient->setProperty("TcpToken", DrivatID);TcpClientMap[DrivatID]=tcpchlient;emit q_ptr->TcpClientListUpdate(QPair<QString,QString>(DrivatID,tcpchlient->peerAddress().toString()),true);}// TcpHeader header;// memset(&header, 0, sizeof(TcpHeader));// memcpy(header.TcpDeviceId, netStruct->TcpDeviceId, 37);// header.TcpDeviceId[36] = '\0';// memcpy(header.Timestamp, netStruct->Timestamp, 18);// header.Timestamp[17] = '\0';// header.reType = TCPOK;// header.type = NOTARIZE;// QByteArray packet(reinterpret_cast<const char*>(&header), sizeof(TcpHeader));// tcpchlient->write(packet);Write_Data(NOTARIZE,"",DrivatID);}}bool TCPEDIServerPrivate::Write_Data(int type,QString data,QString TDriveid){if(TDriveid!=""){if(TcpClientMap.contains(TDriveid) && TcpClientMap[TDriveid]!=nullptr){if(TcpClientMap[TDriveid]->isValid()){QString identify=QDateTime::currentDateTime().toString("yyyyMMddhhmmsszzz");TcpHeader header;memset(&header, 0, sizeof(TcpHeader));memcpy(header.TcpDeviceId, TDriveid.toUtf8().constData(), 37);header.TcpDeviceId[36] = '\0';memcpy(header.Timestamp, identify.toUtf8().constData(), 18);header.Timestamp[17] = '\0';header.reType = TCPOK;header.type = NOTARIZE;QByteArray packet(reinterpret_cast<const char*>(&header), sizeof(TcpHeader));emit q_ptr->SendMess(QString("向客户端发送数据:  TCPHeader -> <br/>""DeviceId ->%1 <br/> ""identify ->%2 <br/> ""type     ->%3 <br/> ""reType   ->%4 <br/> ""Node     ->%5 <br/> ").arg(QString::fromUtf8(QByteArray(header.TcpDeviceId))).arg(QString::fromUtf8(QByteArray(header.Timestamp))).arg(TN(header.type)).arg(TD(header.reType)).arg(data), TCPOK);//将发送区文本发送给客户端const QByteArray send_data = data.toUtf8();packet.append(send_data);TcpClientMap[TDriveid]->write(packet);return true;}}}else{QStringList TDriveIdKeys=TcpClientMap.keys();foreach (QString key, TDriveIdKeys) {QString identify=QDateTime::currentDateTime().toString("yyyyMMddhhmmsszzz");TcpHeader header;memset(&header, 0, sizeof(TcpHeader));memcpy(header.TcpDeviceId, key.toUtf8().constData(), 37);header.TcpDeviceId[36] = '\0';memcpy(header.Timestamp, identify.toUtf8().constData(), 18);header.Timestamp[17] = '\0';header.reType = TCPOK;header.type = NOTARIZE;QByteArray packet(reinterpret_cast<const char*>(&header), sizeof(TcpHeader));emit q_ptr->SendMess(QString("向客户端发送数据:  TCPHeader -> <br/>""DeviceId ->%1 <br/> ""identify ->%2 <br/> ""type     ->%3 <br/> ""reType   ->%4 <br/> ""Node     ->%5 <br/> ").arg(QString::fromUtf8(QByteArray(header.TcpDeviceId))).arg(QString::fromUtf8(QByteArray(header.Timestamp))).arg(TN(header.type)).arg(TD(header.reType)).arg(data), TCPOK);//将发送区文本发送给客户端const QByteArray send_data = data.toUtf8();packet.append(send_data);TcpClientMap[key]->write(packet);return true;}}return false;}
  • 实现TCPEDIServer.cpp

具体实现方法,对私有类进行调用。
由于具体方法和变量都在私有类中实现,所以这部分代码显得特别清爽…
完整代码:

TCPEDIServer::TCPEDIServer(QObject* parent)
:QObject(parent),d_ptr(new TCPEDIServerPrivate)
{
d_ptr->q_ptr=this;
}
TCPEDIServer::~TCPEDIServer()
{
}
bool TCPEDIServer::Init(QString Port)
{
d_ptr->TcpPort=Port.toInt();
return d_ptr->Init();
}
void TCPEDIServer::Unit()
{
d_ptr->Unit();
}
bool TCPEDIServer::WriteDataTo(QString DriveId,QString text)
{
return d_ptr->Write_Data(DOCUMENT,text,DriveId);
}
bool TCPEDIServer::isListening()
{
return d_ptr->isListening();
}
  • 服务端界面效果

具体界面效果展示:
在这里插入图片描述
服务端 通过TcpServer->listen(*address, TcpPort);
固定本地的IP地址和指定的端口启动监听服务…
将获取到的客户端通过void TcpClientListUpdate(QPair<QString,QString> tcpclients,bool Connected);信号绑定到界面上,
同时保存客户端类型和设备ID到QMap<QString,QTcpSocket*> TcpClientMap变量
通过bool WriteDataTo(QString DriveId,QString text)方法 根据设备ID找到对应客户端发送相关信息;

封装QTcpSocket 客户端的具体实现,只提供固定的接口和信号以供调用
同样使用QT的Pimpl设计模式:
自从学会Pimpl模式,写什么类接口都想用,魔怔了,,

  • 定义TCPEDIClient.h

同样将具体的变量和功能实现放到TCPEDIClientPrivate私有类中,
TCPEDIClient类只提供对外的接口和信号.
需要注意的客户端使用了QEventLoop事务锁,等待服务端发送数据解锁.
完整代码:

class TCPEDI_EXPORT TCPEDIClientPrivate;
//! Tcp客户端
class TCPEDI_EXPORT TCPEDIClient:public QObject
{
Q_OBJECT
public:
TCPEDIClient(QObject* parent=nullptr);
~TCPEDIClient();
//! 建立连接
bool Init(QString ServerIp,QString Port,int outTime=5000);
void Unit();
//! 向服务器写入数据
void WriteToServer(int type,QString mess);
//! 获取设备ID 默认用guid生成
QString GetDriveid();
///! 数据是否正常交互
bool DataInteraction(int OutTime=-1);
Q_SIGNALS:
//! 输出日志
void SendMess(QString mess,int type);
//! 是否链接到服务器 或者从服务器连接断开
void ConnectedServer(bool);
private:
QScopedPointer<TCPEDIClientPrivate> d_ptr;Q_DECLARE_PRIVATE(TCPEDIClient)};
  • 私有类TCPEDIClientPrivate 实现

实现QTcpSocket客户端实例功能,同时解析与服务端传来的数据,通过信号槽传出
完整代码:

//! TCPEDIClient私有类 用于实现相关方法内容
class TCPEDIClientPrivate
{
TCPEDIClient* q_ptr;
Q_DECLARE_PUBLIC(TCPEDIClient)
public:
TCPEDIClientPrivate();
~TCPEDIClientPrivate();
//! 开始连接
bool Init();
void UnInit();
//! 解析数据
void Analysis_Data(QByteArray data);
//! 写入数据
void Write_Data(int type,QString data);
//! 监控 等到服务端发来数据交互
bool DataInteraction(int OutTime=-1);
private:
QString ServerIp;
QString Port;
//! 链接超时时间
int OutTime;
//! 设备ID
QString DriveID;
//! Tcp客户端
QTcpSocket* TcpClient=nullptr;
//! 事务锁
QEventLoop* Loop=nullptr;
bool IsSuccessed=false;
};
TCPEDIClientPrivate::TCPEDIClientPrivate()
{
DriveID=QUuid::createUuid().toString(QUuid::WithoutBraces).toUpper();
TcpClient=new QTcpSocket();
//! 获取返回结果 信号
QObject::connect(TcpClient, &QTcpSocket::readyRead, [this] (){
//没有可读的数据就返回
if (TcpClient->bytesAvailable() <= 0)
{
q_ptr->SendMess("没有可读的数据!跳过!!!", TCPNG);
return;
}
QByteArray array = TcpClient->readAll();
if (array.size() < sizeof(TcpHeader))
{
q_ptr->SendMess("当前数据格式不符合规范!跳过解析!!!",TCPNG);
qDebug() << "当前数据格式不符合规范!";
return;
}
Analysis_Data(array);
});
QObject::connect(TcpClient, &QTcpSocket::disconnected, [this](){
emit q_ptr->SendMess(QString("%1 与远程服务器链接断开...").arg(TcpClient->peerAddress().toString()), TCPNG);
emit q_ptr->ConnectedServer(false);
});
QObject::connect(TcpClient, &QTcpSocket::connected, [this](){
emit q_ptr->SendMess(QString("%1 链接远程服务器...").arg(TcpClient->peerAddress().toString()), TCPNG);
emit q_ptr->ConnectedServer(true);
});
Loop=new QEventLoop();
}
TCPEDIClientPrivate::~TCPEDIClientPrivate()
{
}
bool TCPEDIClientPrivate::Init()
{
const QHostAddress AddressHost = QHostAddress(ServerIp);
const unsigned short port = Port.toInt();
//连接服务器
TcpClient->connectToHost(AddressHost, port);
//! 等待建立连接
if (!TcpClient->waitForConnected(OutTime))
return false;
if(!TcpClient->isValid())
return false;
return true;
}
void TCPEDIClientPrivate::UnInit()
{
if(TcpClient!=nullptr)
TcpClient->abort();
}
void TCPEDIClientPrivate::Analysis_Data(QByteArray data)
{
const TcpHeader* header = reinterpret_cast<const TcpHeader*>(data.constData());emit q_ptr->SendMess(QString("获取到服务器传来数据  TCPHeader -> <br/>""DeviceId ->%1 <br/> ""identify ->%2 <br/> ""type     ->%3 <br/> ""reType   ->%4 <br/> ""Node     ->%5 <br/> ").arg(QString::fromUtf8(QByteArray(header->TcpDeviceId))).arg(QString::fromUtf8(QByteArray(header->Timestamp))).arg(TN(header->type)).arg(TD(header->reType)).arg(QString::fromUtf8(data.mid(sizeof(TcpHeader), -1))), TCPOK);if(header->type==NOTARIZE){IsSuccessed=(header->reType==TCPOK)?true:false;Loop->quit();}}void TCPEDIClientPrivate::Write_Data(int type,QString data){QString identify=QDateTime::currentDateTime().toString("yyyyMMddhhmmsszzz");TcpHeader header;memset(&header, 0, sizeof(TcpHeader));memcpy(header.TcpDeviceId, DriveID.toUtf8().constData(), 37);header.TcpDeviceId[36] = '\0';memcpy(header.Timestamp, identify.toUtf8().constData(), 18);header.Timestamp[17] = '\0';header.reType = TCPOK;header.type = type;QByteArray packet(reinterpret_cast<const char*>(&header), sizeof(TcpHeader));//将发送区文本发送给客户端const QByteArray send_data = data.toUtf8();packet.append(send_data);TcpClient->write(packet);emit q_ptr->SendMess(QString("对服务器写入数据:  TCPHeader -> <br/>""DeviceId ->%1 <br/> ""identify ->%2 <br/> ""type     ->%3 <br/> ""reType   ->%4 <br/> ""Node     ->%5 <br/> ").arg(QString::fromUtf8(QByteArray(header.TcpDeviceId))).arg(QString::fromUtf8(QByteArray(header.Timestamp))).arg(TN(header.type)).arg(TD(header.reType)).arg(data), TCPOK);}bool TCPEDIClientPrivate::DataInteraction(int OutTime){IsSuccessed=false;if(OutTime!=-1){QTimer::singleShot(OutTime, [this]() {Loop->quit();});}Write_Data(NOTARIZE,"");Loop->exec();return IsSuccessed;}
  • 实现TCPEDIClient.cpp

将接口方法与私有类中的方法绑定,隐藏实现细节。
完整代码:

TCPEDIClient::TCPEDIClient(QObject* parent)
:QObject(parent),d_ptr(new TCPEDIClientPrivate)
{
d_ptr->q_ptr=this;
}
TCPEDIClient::~TCPEDIClient()
{
}
bool TCPEDIClient::Init(QString _ServerIp,QString _Port,int _outTime)
{
d_ptr->ServerIp=_ServerIp;
d_ptr->Port=_Port;
d_ptr->OutTime=_outTime;
return d_ptr->Init();
}
void TCPEDIClient::Unit()
{
d_ptr->UnInit();
}
void TCPEDIClient::WriteToServer(int type,QString mess)
{
d_ptr->Write_Data(type,mess);
}
QString TCPEDIClient::GetDriveid()
{
return d_ptr->DriveID;
}
bool TCPEDIClient::DataInteraction(int OutTime)
{
return d_ptr->DataInteraction(OutTime);
}
  • 客户端界面效果

调用接口 界面效果展示:

通过调用TCPEDIClient接口的bool Init(QString ServerIp,QString Port,int outTime=5000)与服务端建立链接。
通过 void WriteToServer(int type,QString mess) 方法向服务端写入数据。
通过 void SendMess(QString mess,int type) 信号绑定日志。
这样一来就整个客户端与服务端自定义消息头、消息体通信就完成了,测试通过。
自此总结完…


国庆马上到了,祝各位国庆快乐,假期快乐…

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/924175.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

企业网站建设方案论文自己做网站用花钱吗

1、什么是接口mock 主要是针对单元测试的应用&#xff0c;它可以很方便的解除单元测试中各种依赖&#xff0c;大大的降低了编写单元测试的难度 2、什么是mock server 正常情况下&#xff1a;测试客户端——测试——> 被测系统 ——依赖——>外部服务依赖 在被测系统和…

东莞网站忧化wordpress素锦模板

今天没有早八&#xff0c;八点之钱起床了&#xff0c;上午背了半小时的单词&#xff0c;然后就在写top100&#xff0c;目前中等和简单写了30题&#xff0c;基本上都没有看题解。我自己也整理下&#xff0c;每一题的思路&#xff0c;这样子&#xff0c;也会让我至少拥有做模板题…

温州网站建设设计公司网络营销推广的力度

前言 在管理端会遇到多分类时&#xff0c;要求有层次展示出来&#xff0c;并且每个分类有额外的操作。例如&#xff1a;添加分类、编辑分类、删除、拖到分类等。 下面将会记录这样的一个需求实习过程。 了解需求 分类展示按层级展示分类根据特定的参数展示可以操作的按钮&a…

【c++】深入理解string类(3):典型OJ题 - 指南

【c++】深入理解string类(3):典型OJ题 - 指南pre { white-space: pre !important; word-wrap: normal !important; overflow-x: auto !important; display: block !important; font-family: "Consolas", …

取印度孟买指数(SENSEX)实时行情API对接指南

获取印度孟买指数(SENSEX)实时行情API对接指南本文详细介绍如何通过API获取印度孟买敏感30指数(SENSEX)的实时行情数据,包含多种数据获取方式和代码示例概述 印度孟买敏感30指数(SENSEX)是印度孟买证券交易所的主要股…

网站推广存在的问题wordpress on.7主题

背景&#xff1a; 纯虚类(抽象类) 是只至少拥有一个纯虚函数的类&#xff0c;这种类可以有成员变量&#xff0c;但是不能进行单独的实例化(new&#xff0c;局部变量&#xff0c;智能指针构造等等)。其根本原因是由于纯虚类提供了未实现的成员函数&#xff0c;所以编译器无法知…

京东物流网站建设特点潜江58同城

CSS进阶 目标&#xff1a;掌握复合选择器作用和写法&#xff1b;使用background属性添加背景效果 01-复合选择器 定义&#xff1a;由两个或多个基础选择器&#xff0c;通过不同的方式组合而成。 作用&#xff1a;更准确、更高效的选择目标元素&#xff08;标签&#xff09;。…

企业建站网站认证企业的网站推广意义

目录 一、配置接口的全球单播地址 二、配置接口本地链路地址 三、配置接口任播地址 四、配置接口PMTU 配置静态PMTU&#xff1a; 配置动态PMTU&#xff1a; 五、接口配置IPV6地址示例&#xff1a; 一、配置接口的全球单播地址 全球单播地址类似于IPv4公网地址&#xff0…

网站流量推广网站1996年推广

前言 之前文章简单介绍了如何运行ginvue的前后端分离开源项目&#xff0c;该项目是学习了Gin实践教程后结合vue-element-admin写的&#xff0c;该教程讲得很详细&#xff0c;适合入门Gin。本篇文章将介绍ginvue的前后端分离开源项目中如何使用gin-jwt对API进行权限验证。 安装g…

2025青海视频号运营优质公司推荐榜:专业服务与创新策略口碑

2025氧化镁优质厂家权威推荐榜:品质卓越与技术实力深度解析 一、行业背景 氧化镁作为一种重要的无机化工产品,在众多领域都有着广泛的应用。它具有高熔点、高硬度、良好的化学稳定性等特性,被广泛应用于耐火材料、橡…

2025 年发泡陶瓷厂家 TOP 企业品牌推荐排行榜,发泡陶瓷线条 / 构件 / 装饰构件 / 空心砖 / 窗套线 / 浮雕 / 装饰线条推荐这十家公司

在建筑装饰材料行业蓬勃发展的当下,发泡陶瓷凭借其轻质、防火、耐候性强等优势,在各类建筑项目中得到广泛应用。然而,随着市场需求的不断增长,发泡陶瓷厂家数量日益增多,产品质量却参差不齐。部分厂家为追求短期利…

Future相关并发类使用

Future相关并发类使用 目录Future相关并发类使用一、Callable&Future&FutureTask 详解1. 基础组件对比:Runnable vs Callable2. Future 接口:任务管理工具3. FutureTask:Runnable 与 Future 的结合体4. Fut…

医药网站模板做网站哪家服务器好

在人工智能(AI)的浩瀚宇宙中&#xff0c;大模型以其强大的学习能力和广泛的适用性&#xff0c;正逐步成为推动技术进步和产业革新的核心动力。在这股浪潮中&#xff0c;通用大模型与垂直大模型如同两颗璀璨的星辰&#xff0c;各自散发着独特的光芒&#xff0c;共同照亮了AI发展…

东莞网站免费制作emlog to wordpress

文章目录 openssl3.2 - 测试程序的学习 - test\aesgcmtest.c概述笔记能学到的流程性内容END openssl3.2 - 测试程序的学习 - test\aesgcmtest.c 概述 openssl3.2 - 测试程序的学习 aesgcmtest.c 工程搭建时, 发现没有提供 test_get_options(), cleanup_tests(), 需要自己补上…

2025 年传感器厂家 TOP 企业品牌推荐排行榜,磁致伸缩 / 防爆 / 防水 / 隔爆 / 线性 / 矿用 / 直线 / 油缸位移传感器 / 液位传感器公司推荐!

引言当前传感器行业发展迅速,市场上品牌与产品种类繁多,给企业和采购者带来了不小的选择难题。一方面,不同品牌的传感器在技术水平、产品质量、服务能力等方面存在较大差异,部分产品难以满足工业生产中对高精度、稳…

2025 年热转印花膜厂家 TOP 企业品牌推荐排行榜,硅胶 / 五金 / 塑胶 / ABS / 涂料桶 / PP / 水杯 / 温变 / 冰变热转印花膜加工厂推荐

引言在热转印行业蓬勃发展的当下,热转印花膜作为关键材料,其市场需求日益增长。然而,行业内却存在着诸多问题,让采购者面临不少困扰。一方面,部分厂家为追求利润,在生产过程中偷工减料,导致产品质量参差不齐,有…

2025 年生物除臭设备厂家 TOP 品牌企业推荐排行榜揭晓:印染厂污水 / 食品厂污水 / 污水处理厂 / 污水泵站 / 污水站 / 餐厨垃圾 / 屠宰场 / 厨余垃圾生物除臭设备公司推荐

引言随着环保意识的不断提升,各行业对臭气治理的需求日益迫切。污水处理厂、垃圾中转站、畜牧养殖场、食品加工厂等领域,因生产经营过程中产生的恶臭气体,不仅影响周边居民生活环境,还可能对工作人员的身体健康造成…

JUC:读写锁

无锁 => 独占锁 => 读写锁 => 邮戳锁 ReentrantLock, ReentrantReadWriteLock, StampedLock 4.12.1 面试题Java有哪些锁? 对于读写锁,锁饥饿问题是什么? 有没有比读写锁更快的锁?邮戳锁 StampedLock知道码…

2025 年舞台厂家 TOP 品牌企业权威推荐榜单,铝合金舞台、活动舞台、快装舞台、舞台架、折叠舞台、演出舞台、演唱会舞台桁架、舞台设计公司推荐

引言当前舞台行业发展迅速,各类演出、展览、庆典活动对舞台设备的需求日益增长,但行业内却存在诸多问题。许多舞台厂家缺乏完整的产业链,依赖外部采购原材料和配件,不仅难以把控产品质量,还容易受供应链波动影响,…

大连网站设计九即问仟亿科技在电脑上做二建题 哪个网站好

Hi,大家好我是tom,I am back.今天要给大家讲讲linux系统一些性能相关命令。 1.fdisk 磁盘管理 是一个强大的危险命令&#xff0c;所有涉及磁盘的操作都由该命令完成&#xff0c;包括&#xff1a;新增磁盘、增删改磁盘分区等。 1.fdisk -l 查看磁盘分区情况 Disk /dev/sda: 27.8…