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通信的案例有很多,
这里就不过多描述,只简单介绍下常用的方法或信号。
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();//收到数据,触发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数据直接的快速转换,这就涉及到把数据按固定字节位数处理。
涉及到字节对齐。
定义一个包含 设备标识,请求标识,处理类型,处理的状态 的结构体,以及剩下的消息体,
其中的标识为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个字节固定,后面剩下的所有数据都可以作为消息体,只要前面的消息头数据符合规范,那这里面的数据就肯定是有效的。
将一个结构体转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数据 转成结构体数据
就可以直接使用 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;//! 设备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::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)
信号绑定日志。
这样一来就整个客户端与服务端自定义消息头、消息体通信就完成了,测试通过。
自此总结完…
国庆马上到了,祝各位国庆快乐,假期快乐…