当前位置: 首页 > news >正文

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

最近项目中用到了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通信的案例有很多,
这里就不过多描述,只简单介绍下常用的方法或信号。

  • TCP客户端 QTcpSocket类

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();//收到数据,触发readyReadQObject::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数据直接的快速转换,这就涉及到把数据按固定字节位数处理。
涉及到字节对齐。

  • 首先 定义一个TcpHeader结构体

定义一个包含 设备标识请求标识处理类型处理的状态 的结构体,以及剩下的消息体,
其中的标识为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];		   //! 请求标识 yyyyMMddHHmmsszzzint               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));//添加消息体 data
const 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)));

接口封装源码 与 界面效果

  • 使用Pimpl模式 定义 服务端代码接口 源码

在界面上直接使用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找到对应客户端发送相关信息;

  • 使用Pimpl模式 定义 客户端代码接口 源码

封装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;//! 设备IDQString 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.dtcms.com/a/426593.html

相关文章:

  • YDWE编辑器系列教程一:编辑器界面
  • 外贸网站怎么找客户名城建设有限公司网站
  • Linux 系统基础配置:主机名、IP、主机映射、防火墙
  • AI 重构实体经济:2025 传统产业的智能转型革命
  • 【金仓数据库产品体验官】KingbaseES-Oracle兼容性体验
  • 初探 ansible 部署 devops 持续集成持续交付
  • 【VBA】点击按钮,实现将Excel表A数据按格式填入表B
  • 微硕WST8205A双N沟MOSFET,汽车阅读灯静音负载开关
  • LabVIEW与PLC 汽车驻车制动自动调整
  • 【办公类-115-01】20250920职称资料上传01——多个jpg转同名PDF(如:荣誉证书)并自动生成单一文件夹
  • 基于Kafka+ElasticSearch+MongoDB+Redis+XXL-Job日志分析系统(学习)
  • 【龙泽科技】智能网联汽车智能传感器测试装调仿真教学软件
  • JAVA:Spring Boot 集成 BouncyCastle 实现加密算法
  • 石家庄住房和城乡建设局官方网站app模板下载网站
  • gRPC从0到1系列【9】
  • IDEA 2024 中创建 Maven 项目的详细步骤
  • 2025 AI 图景:从工具革命到生态重构的五大趋势
  • 网站开发者模式下载视频wordpress如何添加备案号
  • UNIX下C语言编程与实践22-UNIX 文件其他属性获取:stat 结构与 localtime 函数的使用
  • UNIX下C语言编程与实践15-UNIX 文件系统三级结构:目录、i 节点、数据块的协同工作机制
  • 青浦做网站的公司网站开发语言html5 php
  • 【分布式中间件】RabbitMQ 功能详解与高可靠实现指南
  • SOME/IP-SD报文结构和交互详解
  • 给贾维斯加“手势控制”:从原理到落地,打造多模态交互的本地智能助
  • 电商数据分析优化清理大师
  • 论文阅读:《Self-Supervised Continual Graph Learning in Adaptive Riemannian Spaces》
  • Qt事件处理全解析
  • 深入理解 LLM 分词器:BPE、WordPiece 与 Unigram
  • 【大模型评估】大模型评估的五类数据
  • 3-2 Windows 安全设置