最近项目中用到了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;
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个字节固定,后面剩下的所有数据都可以作为消息体,只要前面的消息头数据符合规范,那这里面的数据就肯定是有效的。
将一个结构体转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数据 转成结构体数据
就可以直接使用 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设计模式:
对外的接口和方法需要的不多,主要还是内部实现的封装
完整代码:
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)};
私有类包含了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::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模式,写什么类接口都想用,魔怔了,,
同样将具体的变量和功能实现放到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)};
实现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::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)
信号绑定日志。
这样一来就整个客户端与服务端自定义消息头、消息体通信就完成了,测试通过。
自此总结完…
国庆马上到了,祝各位国庆快乐,假期快乐…