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

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

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

  • 首先 定义一个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];		   //! 请求标识 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));
  //添加消息体 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);
  //关联相关操作的信号槽
  //收到数据,触发readyRead
  QObject::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;
//! 设备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) 信号绑定日志。
这样一来就整个客户端与服务端自定义消息头、消息体通信就完成了,测试通过。
自此总结完…


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

posted @ 2025-10-01 19:06  wzzkaifa  阅读(40)  评论(0)    收藏  举报