Qt 利用TCP/IP socket通信 发送与接收结构体(简单通信协议解析)

Qt 利用TCP/IP socket通信 发送与接收结构体(简单通信协议解析)

  • [Chapter1 Qt 利用TCP/IP socket通信 发送与接收结构体(简单通信协议解析)](#Chapter1 Qt 利用TCP/IP socket通信 发送与接收结构体(简单通信协议解析))
  • [Chapter2 自定义协议方案](#Chapter2 自定义协议方案)

Chapter1 Qt 利用TCP/IP socket通信 发送与接收结构体(简单通信协议解析)

原文链接

简述:

在Qt里利用TCP/IP协议,socket套接字设计实现结构体的收发,类似实现简单的自定义通信协议。

描述:

发送的结构体包含帧头header(占两字节)、数据长度dataLength(占一字节)、数据my_data(不多于64字节)、校验和check_sum(前面所有数据所占字节和,本身只占一个字节)。

发送方的结构体:
这里要特别注意== #pragma pack(1) ==的使用,涉及到结构体内存对齐,使用这行可以设置结构体对齐方式为1字节,这点特别重要,我在这个坑里绕了好久才走出来!!这样设置主要是因为后面要使用到结构体的大小sizeof(senddata)。

Chapter2 自定义协议方案

我们设计一个混合方案:使用QDataStream序列化结构体数据,然后将得到的QByteArray转换为Base64编码的字符串,放入JSON中。

这样,我们可以利用JSON的可读性和扩展性,同时保证结构体中浮点数等二进制数据的精度。

步骤:

定义结构体,并使用QDataStream进行序列化和反序列化。

将序列化后的QByteArray转换为Base64字符串,以便放入JSON。

构建JSON对象,包含必要的字段,其中结构体数据部分为Base64字符串。

通过TCP传输JSON字符串。

接收方解析JSON字符串,提取Base64字符串,解码为QByteArray,然后反序列化为结构体。

注意:由于JSON是文本格式,不能直接嵌入二进制数据,所以需要Base64编码。

以下为详细代码示例:

首先,定义结构体(以SensorData为例)和其序列化/反序列化方法:

QT TCP传输JSON格式数据方案(含结构体二进制数据)

一、总体架构设计

1.1 数据结构定义

cpp 复制代码
// 原始结构体定义
#pragma pack(push, 1)
struct SensorData {
    qint64 timestamp;      // 时间戳(毫秒)
    float temperature;     // 温度
    float humidity;        // 湿度
    double pressure;       // 气压
    quint8 sensor_id;      // 传感器ID
};
#pragma pack(pop)

// JSON消息结构
struct JsonMessage {
    QString message_type;  // 消息类型:data/command/response
    QString device_id;     // 设备标识
    quint32 sequence;      // 序列号
    QByteArray binary_data;// 结构体二进制数据
    QString checksum;      // 校验和
    qint64 send_time;      // 发送时间戳
};

二、核心实现方案

2.1 混合序列化类

cpp 复制代码
class HybridSerializer {
public:
    // 结构体转QDataStream二进制数据
    static QByteArray structToBinary(const SensorData& data) {
        QByteArray byteArray;
        QDataStream stream(&byteArray, QIODevice::WriteOnly);
        
        // 设置高精度模式
        stream.setVersion(QDataStream::Qt_5_15);
        stream.setFloatingPointPrecision(QDataStream::DoublePrecision);
        
        // 写入结构体数据
        stream << data.timestamp
               << data.temperature
               << data.humidity
               << data.pressure
               << data.sensor_id;
        
        return byteArray;
    }
    
    // 二进制数据转结构体
    static SensorData binaryToStruct(const QByteArray& binaryData) {
        SensorData data;
        QDataStream stream(binaryData);
        stream.setVersion(QDataStream::Qt_5_15);
        stream.setFloatingPointPrecision(QDataStream::DoublePrecision);
        
        stream >> data.timestamp
               >> data.temperature
               >> data.humidity
               >> data.pressure
               >> data.sensor_id;
        
        return data;
    }
    
    // 生成JSON消息(包含二进制数据)
    static QByteArray createJsonMessage(const JsonMessage& msg) {
        QJsonObject jsonObj;
        
        // 添加文本字段
        jsonObj["message_type"] = msg.message_type;
        jsonObj["device_id"] = msg.device_id;
        jsonObj["sequence"] = static_cast<qint64>(msg.sequence);
        jsonObj["checksum"] = msg.checksum;
        jsonObj["send_time"] = msg.send_time;
        
        // 二进制数据转为Base64编码
        if (!msg.binary_data.isEmpty()) {
            QString base64Data = QString::fromLatin1(msg.binary_data.toBase64());
            jsonObj["binary_data"] = base64Data;
            jsonObj["data_size"] = msg.binary_data.size();
            jsonObj["data_format"] = "qdatastream_binary";
        }
        
        // 添加版本信息
        jsonObj["protocol_version"] = "1.0";
        jsonObj["encoding"] = "utf-8";
        
        QJsonDocument doc(jsonObj);
        return doc.toJson(QJsonDocument::Compact);
    }
    
    // 解析JSON消息
    static JsonMessage parseJsonMessage(const QByteArray& jsonData) {
        JsonMessage msg;
        QJsonParseError parseError;
        QJsonDocument doc = QJsonDocument::fromJson(jsonData, &parseError);
        
        if (parseError.error != QJsonParseError::NoError) {
            msg.message_type = "error";
            return msg;
        }
        
        QJsonObject jsonObj = doc.object();
        
        // 解析文本字段
        msg.message_type = jsonObj["message_type"].toString();
        msg.device_id = jsonObj["device_id"].toString();
        msg.sequence = static_cast<quint32>(jsonObj["sequence"].toInt());
        msg.checksum = jsonObj["checksum"].toString();
        msg.send_time = static_cast<qint64>(jsonObj["send_time"].toDouble());
        
        // 解析二进制数据(Base64解码)
        if (jsonObj.contains("binary_data")) {
            QString base64Str = jsonObj["binary_data"].toString();
            msg.binary_data = QByteArray::fromBase64(base64Str.toLatin1());
        }
        
        return msg;
    }
    
    // 生成校验和(CRC32)
    static QString calculateChecksum(const QByteArray& data) {
        quint32 crc = 0xFFFFFFFF;
        for (int i = 0; i < data.size(); ++i) {
            crc ^= static_cast<quint8>(data.at(i));
            for (int j = 0; j < 8; ++j) {
                crc = (crc >> 1) ^ ((crc & 1) ? 0xEDB88320 : 0);
            }
        }
        crc ^= 0xFFFFFFFF;
        return QString("%1").arg(crc, 8, 16, QChar('0')).toUpper();
    }
};

2.2 TCP通信管理器

cpp 复制代码
class TcpJsonClient : public QObject {
    Q_OBJECT
    
public:
    explicit TcpJsonClient(QObject *parent = nullptr) : QObject(parent) {
        socket = new QTcpSocket(this);
        
        connect(socket, &QTcpSocket::connected, this, &TcpJsonClient::onConnected);
        connect(socket, &QTcpSocket::readyRead, this, &TcpJsonClient::onReadyRead);
        connect(socket, &QTcpSocket::disconnected, this, &TcpJsonClient::onDisconnected);
        connect(socket, QOverload<QAbstractSocket::SocketError>::of(&QAbstractSocket::error),
                this, &TcpJsonClient::onError);
    }
    
    // 发送结构体数据
    void sendSensorData(const SensorData& sensorData) {
        // 1. 结构体转二进制
        QByteArray binaryData = HybridSerializer::structToBinary(sensorData);
        
        // 2. 创建JSON消息
        JsonMessage jsonMsg;
        jsonMsg.message_type = "sensor_data";
        jsonMsg.device_id = "sensor_001";
        jsonMsg.sequence = ++currentSequence;
        jsonMsg.binary_data = binaryData;
        jsonMsg.send_time = QDateTime::currentMSecsSinceEpoch();
        
        // 3. 计算校验和(对二进制数据)
        jsonMsg.checksum = HybridSerializer::calculateChecksum(binaryData);
        
        // 4. 生成JSON字符串
        QByteArray jsonData = HybridSerializer::createJsonMessage(jsonMsg);
        
        // 5. 添加帧头(长度信息)
        QByteArray frame;
        QDataStream frameStream(&frame, QIODevice::WriteOnly);
        frameStream.setVersion(QDataStream::Qt_5_15);
        frameStream << static_cast<quint32>(jsonData.size());
        frame.append(jsonData);
        
        // 6. 发送数据
        if (socket->state() == QAbstractSocket::ConnectedState) {
            socket->write(frame);
            socket->flush();
            emit dataSent(jsonMsg.sequence, jsonData.size());
        }
    }
    
    // 接收数据处理
    void onReadyRead() {
        buffer.append(socket->readAll());
        
        while (buffer.size() >= 4) {  // 至少有长度信息
            // 读取消息长度
            quint32 messageLength;
            QDataStream lengthStream(buffer);
            lengthStream.setVersion(QDataStream::Qt_5_15);
            lengthStream >> messageLength;
            
            if (buffer.size() < messageLength + 4) {
                return;  // 数据不完整,等待
            }
            
            // 提取JSON数据
            QByteArray jsonData = buffer.mid(4, messageLength);
            buffer.remove(0, messageLength + 4);
            
            // 解析JSON消息
            JsonMessage jsonMsg = HybridSerializer::parseJsonMessage(jsonData);
            
            if (jsonMsg.message_type == "error") {
                emit parseError("JSON解析错误");
                continue;
            }
            
            // 验证校验和
            QString calculatedChecksum = HybridSerializer::calculateChecksum(jsonMsg.binary_data);
            if (calculatedChecksum != jsonMsg.checksum) {
                emit checksumError(jsonMsg.sequence);
                continue;
            }
            
            // 处理不同类型的消息
            processMessage(jsonMsg);
        }
    }
    
private:
    void processMessage(const JsonMessage& msg) {
        if (msg.message_type == "sensor_data") {
            // 二进制数据转结构体
            SensorData sensorData = HybridSerializer::binaryToStruct(msg.binary_data);
            
            // 发送信号处理数据
            emit sensorDataReceived(sensorData, msg.device_id, msg.send_time);
            
            // 可选:发送确认响应
            sendAckResponse(msg.sequence);
            
        } else if (msg.message_type == "ack") {
            emit acknowledgmentReceived(msg.sequence);
        }
    }
    
    void sendAckResponse(quint32 sequence) {
        JsonMessage ackMsg;
        ackMsg.message_type = "ack";
        ackMsg.device_id = "client_001";
        ackMsg.sequence = sequence;
        ackMsg.send_time = QDateTime::currentMSecsSinceEpoch();
        
        QByteArray jsonData = HybridSerializer::createJsonMessage(ackMsg);
        
        // 添加长度头
        QByteArray frame;
        QDataStream frameStream(&frame, QIODevice::WriteOnly);
        frameStream << static_cast<quint32>(jsonData.size());
        frame.append(jsonData);
        
        socket->write(frame);
    }
    
signals:
    void dataSent(quint32 sequence, qint64 size);
    void sensorDataReceived(const SensorData& data, const QString& deviceId, qint64 timestamp);
    void acknowledgmentReceived(quint32 sequence);
    void parseError(const QString& error);
    void checksumError(quint32 sequence);
    
private:
    QTcpSocket* socket;
    QByteArray buffer;
    quint32 currentSequence = 0;
};

2.3 服务器端实现

cpp 复制代码
class TcpJsonServer : public QTcpServer {
    Q_OBJECT
    
protected:
    void incomingConnection(qintptr socketDescriptor) override {
        ClientThread* thread = new ClientThread(socketDescriptor, this);
        connect(thread, &ClientThread::sensorDataReceived,
                this, &TcpJsonServer::processSensorData);
        connect(thread, &ClientThread::finished,
                thread, &QObject::deleteLater);
        thread->start();
    }
    
private slots:
    void processSensorData(const SensorData& data, const QString& clientId) {
        // 处理接收到的数据
        qDebug() << "Received data from" << clientId
                 << "Temperature:" << data.temperature
                 << "Humidity:" << data.humidity
                 << "Pressure:" << data.pressure;
    }
};

// 客户端线程处理
class ClientThread : public QThread {
    Q_OBJECT
    
public:
    ClientThread(qintptr socketDescriptor, QObject* parent = nullptr)
        : QThread(parent), socketDescriptor(socketDescriptor) {}
    
    void run() override {
        QTcpSocket socket;
        if (!socket.setSocketDescriptor(socketDescriptor)) {
            emit error(socket.error());
            return;
        }
        
        QByteArray buffer;
        
        while (socket.state() == QAbstractSocket::ConnectedState) {
            if (socket.waitForReadyRead(1000)) {
                buffer.append(socket.readAll());
                
                // 消息处理逻辑(与客户端类似)
                while (buffer.size() >= 4) {
                    quint32 messageLength;
                    QDataStream lengthStream(buffer);
                    lengthStream >> messageLength;
                    
                    if (buffer.size() < messageLength + 4) {
                        break;
                    }
                    
                    QByteArray jsonData = buffer.mid(4, messageLength);
                    buffer.remove(0, messageLength + 4);
                    
                    JsonMessage jsonMsg = HybridSerializer::parseJsonMessage(jsonData);
                    
                    if (jsonMsg.message_type == "sensor_data") {
                        SensorData sensorData = HybridSerializer::binaryToStruct(jsonMsg.binary_data);
                        emit sensorDataReceived(sensorData, jsonMsg.device_id);
                    }
                }
            }
        }
        
        socket.disconnectFromHost();
        socket.waitForDisconnected();
    }
    
signals:
    void sensorDataReceived(const SensorData& data, const QString& clientId);
    void error(QAbstractSocket::SocketError socketError);
    
private:
    qintptr socketDescriptor;
};

三、数据格式示例

3.1 JSON消息示例

javascript 复制代码
{
  "message_type": "sensor_data",
  "device_id": "sensor_001",
  "sequence": 12345,
  "checksum": "A1B2C3D4",
  "send_time": 1677654321000,
  "binary_data": "AAECAwQFBgcICQoLDA0ODxAREhMUFRYXGBkaGxwdHh8gISIjJCUmJygpKissLS4vMDEyMzQ1Njc4OTo7PD0+P0BBQkNERUZHSElKS0xNTk9QUVJTVFVWV1hZWltcXV5fYGFiY2RlZmdoaWprbG1ub3BxcnN0dXZ3eHl6e3x9fn+AgYKDhIWGh4iJiouMjY6PkJGSk5SVlpeYmZqbnJ2en6ChoqOkpaanqKmqq6ytrq+wsbKztLW2t7i5uru8vb6/wMHCw8TFxsfIycrLzM3Oz9DR0tPU1dbX2Nna29zd3t/g4eLj5OXm5+jp6uvs7e7v8PHy8/T19vf4+fr7/P3+/w==",
  "data_size": 29,
  "data_format": "qdatastream_binary",
  "protocol_version": "1.0",
  "encoding": "utf-8"
}

四、优化和错误处理

待进一步完善。

相关推荐
平安的平安1 小时前
【OpenHarmony】React Native鸿蒙实战:NetInfo 网络状态详解
网络·react native·harmonyos
少云清1 小时前
【接口测试】4_Dubbo接口 _xx健康项目接口代码实现
网络·dubbo·接口测试·代码实现
byzh_rc2 小时前
[深度学习网络从入门到入土] 残差网络ResNet
网络·人工智能·深度学习
麦麦大数据2 小时前
F065_基于机器学习的KDD CUP 99网络入侵检测系统实战
网络·人工智能·机器学习·网络安全·入侵检测
Boxsc_midnight2 小时前
【MCP+ComfyUI+CherryStudio+Ollama】实现对话式智能批量生成图片(或视频)的方案,硬件友好方案!
网络·人工智能
专注方法攻略分享2 小时前
网站显示503 Service Unavailable错误怎么办
网络·html
8125035332 小时前
计算机网络全栈连载计划
linux·网络·网络协议·计算机网络
清水白石00811 小时前
突破并行瓶颈:Python 多进程开销全解析与 IPC 优化实战
开发语言·网络·python
崎岖Qiu11 小时前
【计算机网络 | 第十二篇】「网络层」概述与服务模型
网络·笔记·计算机网络