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

最近项目中用到了TCP通信实时交互数据,在之前只是简单了解过,于是拿项目练手的同时又仔细的研究了一下,这里简单做个总结:

在实现QTcpSocket客户端QTcpServer服务端 数据交互的时候,大多数都是使用JSON或者XML字符串 然后解析成结构体获取数据,

并没有像Modbus协议 那样使用Modbus消息头 来规范数据头,

于是我就想着自己定义一个结构体作为TCP通信数据的消息头,剩下的数据作为消息体,

也就是固定格式报文数据...

目录导读

    • [QT TCP通信](#QT TCP通信)
      • [TCP客户端 QTcpSocket类](#TCP客户端 QTcpSocket类)
      • [TCP服务端 QTcpServer类](#TCP服务端 QTcpServer类)
    • 自定义消息头结构
      • [首先 定义一个TcpHeader结构体](#首先 定义一个TcpHeader结构体)
      • [TcpHeader结构体 转QByteArray数据](#TcpHeader结构体 转QByteArray数据)
      • QByteArray数据转TcpHeader结构体
    • [接口封装源码 与 界面效果](#接口封装源码 与 界面效果)

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) 等待远程链接完成

  • 代码示例:
cpp 复制代码
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地址端口,建立服务。

  • 代码示例:
cpp 复制代码
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 ) 对齐字节

  • 代码示例:
cpp 复制代码
#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));

对整个结构体数据的置零:

要不然混杂的数据可能导致数据解析失败!

  • 代码示例:
cpp 复制代码
//! 初始化消息头
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 强转结构体.

  • 代码示例:
cpp 复制代码
//!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

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

cpp 复制代码
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客户端列表,

将具体的调用和功能实现都放在了私有类,避免接口的复杂性,同时不可见。
完整代码:

cpp 复制代码
//! 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

具体实现方法,对私有类进行调用。

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

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 事务锁,等待服务端发送数据解锁.
完整代码:

cpp 复制代码
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客户端实例功能,同时解析与服务端传来的数据,通过信号槽传出
完整代码:

cpp 复制代码
//! 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

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

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) 信号绑定日志。

这样一来就整个客户端与服务端自定义消息头、消息体通信就完成了,测试通过。
自此总结完...


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

相关推荐
茉莉玫瑰花茶6 小时前
Qt 界面优化 --- 绘图
开发语言·数据库·qt
掘根6 小时前
【Qt】容器类控件——QTabWidget
开发语言·qt
hqwest6 小时前
QT肝8天07--连接数据库
开发语言·数据库·c++·qt·sqlite·上位机·qt开发
海涛高软16 小时前
qt使用opencv的imread读取图像为空
qt·opencv·webpack
jjjxxxhhh12319 小时前
【QT】-QT为啥喜欢用类而不是结构体
开发语言·qt
小-黯1 天前
VSCode+QT开发环境配置
ide·vscode·qt
AI+程序员在路上1 天前
QT6中QGraphicsView功能与应用
开发语言·c++·qt
郝学胜-神的一滴1 天前
深入理解 Qt 元对象系统:QMetaEnum 的应用与实践
开发语言·c++·qt·软件工程
ao_lang1 天前
Qt事件处理全解析
开发语言·qt