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"
}
四、优化和错误处理
待进一步完善。