QDataStream 是 Qt 中用于序列化和反序列化二进制数据的类。它可以将各种数据类型(包括基本类型、字符串、容器等)转换为字节序列,以便存储或传输,并且可以重新构造这些数据。
主要特点:
-
支持多种数据类型:包括整型、浮点型、字符串、字节数组等。
-
自动处理字节序:可以设置字节序(大端或小端)。
-
版本控制:可以设置版本号以处理数据结构的演变。
-
简单易用:使用流操作符(<< 和 >>)进行读写。
在聊天程序中,我们可以使用 QDataStream 来构建和解析消息,从而避免手动拼接和解析字符串。
下面是一个简单的示例,展示如何使用 QDataStream 序列化和反序列化一个包含消息类型、发送者、接收者和内容的消息。
假设我们的消息结构如下:
-
消息类型(quint8)
-
发送者(QString)
-
接收者(QString)
-
内容(QString)
序列化(写入)示例:
QByteArray serializeMessage(quint8 type, const QString &sender, const QString &receiver, const QString &content) {QByteArray byteArray;QDataStream stream(&byteArray, QIODevice::WriteOnly);stream.setVersion(QDataStream::Qt_5_15); // 设置版本,确保兼容性// 写入数据stream << type << sender << receiver << content;return byteArray;
}
反序列化(读取)示例:
bool parseMessage(const QByteArray &byteArray, quint8 &type, QString &sender, QString &receiver, QString &content) {QDataStream stream(byteArray);stream.setVersion(QDataStream::Qt_5_15);// 读取数据stream >> type >> sender >> receiver >> content;// 检查是否读取成功return stream.status() == QDataStream::Ok;
}
在网络上传输时,我们可以在消息前面加上长度前缀,以便接收方知道消息的边界。例如,先发送4个字节表示消息长度,再发送消息内容。
发送方:
QByteArray message = serializeMessage(type, sender, receiver, content);
quint32 messageSize = message.size();// 将消息长度转换为网络字节序(大端)
QByteArray sizeArray;
QDataStream sizeStream(&sizeArray, QIODevice::WriteOnly);
sizeStream.setVersion(QDataStream::Qt_5_15);
sizeStream << messageSize;// 先发送长度,再发送消息
socket->write(sizeArray);
socket->write(message);
接收方:
需要先读取4个字节得到消息长度,然后根据长度读取消息内容。
注意:QDataStream 在写入字符串时,会在字符串前面加上长度(quint32),所以整个消息的长度并不是简单的字符串长度之和。
在服务器端(C语言)解析这种格式可能会比较麻烦,因为需要按照QDataStream的格式来解析。如果服务器是C语言写的,可能需要考虑使用更简单的格式,或者使用相同的解析方式(C语言解析QDataStream格式)。
但是,如果服务器也是用Qt写的,那么使用QDataStream就会非常方便。
考虑到您的服务器是用C语言写的,我建议使用一种更简单的格式,例如:
-
消息类型(1字节)
-
发送者账号(固定8字节)
-
接收者账号(固定8字节)
-
消息内容(可变长度,前面加上4字节长度)
这样,服务器端解析起来会简单很多。
不过,既然您已经用C语言实现了服务器,并且之前的协议是定长的账号(8字节)和可变的消息内容,那么我们可以继续使用这种格式,只是使用QDataStream来帮助构建和解析消息。
对于文本消息,我们可以这样定义:
-
消息类型:1字节('1')
-
发送者账号:8字节(固定长度,不足补空格)
-
接收者账号:8字节(固定长度,不足补空格)
-
文本内容:可变长度,前面加上4字节长度(使用QDataStream写入)
使用QDataStream构建文本消息:
QByteArray buildTextMessage(const QString &sender, const QString &receiver, const QString &text) {QByteArray byteArray;QDataStream stream(&byteArray, QIODevice::WriteOnly);stream.setVersion(QDataStream::Qt_5_15);// 消息类型char msgType = '1';stream << msgType;// 发送者和接收者账号(固定8字节)QByteArray senderBytes = sender.toLatin1().leftJustified(8, ' ', true);QByteArray receiverBytes = receiver.toLatin1().leftJustified(8, ' ', true);stream.writeRawData(senderBytes.constData(), 8);stream.writeRawData(receiverBytes.constData(), 8);// 文本内容(带长度前缀)stream << text;return byteArray;
}
注意:使用QDataStream写入字符串(stream << text)时,会先写入一个quint32表示字符串长度,然后是字符串内容。这样,接收方就可以先读取长度,然后读取内容。
但是,这样构建的消息格式与之前协议中定义的格式(文本内容直接跟在接收者账号后面,没有长度前缀)不一致。因此,我们需要调整协议,为文本内容也加上长度前缀,这样解析起来会更方便。
调整后的协议:
-
文本消息:类型(1)+发送者(8)+接收者(8)+文本内容长度(4)+文本内容
-
文件信息:类型(2)+发送者(8)+接收者(8)+文件名长度(2)+文件名+文件大小(8)
-
文件数据:类型(3)+发送者(8)+接收者(8)+块序号(4)+数据大小(4)+文件数据
-
文件结束:类型(4)+发送者(8)+接收者(8)
这样,每个部分都有明确的长度,解析起来就方便多了。
对于文本消息,使用QDataStream构建:
QByteArray buildTextMessage(const QString &sender, const QString &receiver, const QString &text) {QByteArray byteArray;QDataStream stream(&byteArray, QIODevice::WriteOnly);stream.setVersion(QDataStream::Qt_5_15);// 消息类型quint8 msgType = 1; // 文本消息类型为1stream << msgType;// 发送者和接收者账号(固定8字节)QByteArray senderBytes = sender.toLatin1().leftJustified(8, ' ', true);QByteArray receiverBytes = receiver.toLatin1().leftJustified(8, ' ', true);stream.writeRawData(senderBytes.constData(), 8);stream.writeRawData(receiverBytes.constData(), 8);// 文本内容(UTF-8编码)QByteArray textBytes = text.toUtf8();quint32 textLength = textBytes.size();stream << textLength;stream.writeRawData(textBytes.constData(), textLength);return byteArray;
}
解析时,使用QDataStream按照相同的顺序读取即可。
但是,请注意,使用QDataStream写入整数(如textLength)时,默认使用大端字节序(网络字节序)。所以,在C语言服务器端解析时,需要将4字节长度从网络字节序转换为主机字节序。
同样,在C语言服务器端构建消息时,也需要将整数转换为网络字节序。
考虑到服务器端是C语言,我们可以在客户端使用QDataStream,并确保使用大端字节序(网络字节序)。QDataStream默认就是大端字节序,但我们可以显式设置:
stream.setByteOrder(QDataStream::BigEndian);
这样,客户端和服务器端就可以使用相同的字节序来解析整数了。
总结:
-
使用QDataStream可以简化客户端的消息构建和解析。
-
调整协议,为可变长度的字段加上长度前缀。
-
设置QDataStream为网络字节序(大端),以便与C语言服务器交互。
由于时间关系,这里只给出文本消息的构建和解析示例,文件消息类似。
希望这能帮助您改进网络传输方式。
QDataStream 详细介绍
一、QDataStream 是什么?
QDataStream 是 Qt 框架提供的用于二进制数据序列化和反序列化的类。它提供了一种简单、类型安全的方式来读写二进制数据,自动处理平台差异(如字节序、数据类型大小等)。
二、主要特点
1. 类型安全
QDataStream stream(&data, QIODevice::WriteOnly);
stream << QString("Hello") << qint32(100) << qreal(3.14);
// 自动知道每种类型的大小和格式
2. 自动字节序处理
stream.setByteOrder(QDataStream::LittleEndian); // 小端
stream.setByteOrder(QDataStream::BigEndian); // 大端(网络字节序,默认)
3. 版本控制
stream.setVersion(QDataStream::Qt_5_15); // 设置序列化版本
// 不同版本的Qt可能有不同的序列化格式
4. 支持多种数据类型
-
基本类型:
qint8,quint16,qint32,qint64,float,double,bool -
Qt类型:
QString,QByteArray,QList,QMap,QVariant,QDateTime,QPixmap,QImage等
三、基本用法
1. 序列化(写入)
// 创建字节数组
QByteArray data;
QDataStream out(&data, QIODevice::WriteOnly);
out.setVersion(QDataStream::Qt_5_15); // 设置版本// 写入数据
out << QString("张三") // 自动写入字符串长度+内容<< qint32(25) // 4字节整数<< QDateTime::currentDateTime(); // 日期时间// 现在data包含了序列化的二进制数据
2. 反序列化(读取)
// 从字节数组读取
QDataStream in(data);
in.setVersion(QDataStream::Qt_5_15); // 必须与写入时相同QString name;
qint32 age;
QDateTime datetime;// 按相同顺序读取
in >> name >> age >> datetime;if (in.status() != QDataStream::Ok) {qDebug() << "读取数据失败";
}
四、在网络传输中的应用
消息结构设计
// 消息头
struct MessageHeader {quint32 magicNumber = 0x12345678; // 魔数,验证消息有效性quint32 totalSize = 0; // 消息总大小quint8 messageType = 0; // 消息类型quint32 sequenceId = 0; // 序列号(可选)
};// 使用QDataStream序列化
QByteArray serializeMessage(MessageType type, const QByteArray &payload)
{MessageHeader header;header.messageType = static_cast<quint8>(type);header.totalSize = sizeof(MessageHeader) + payload.size();QByteArray data;QDataStream stream(&data, QIODevice::WriteOnly);stream.setVersion(QDataStream::Qt_5_15);// 写入消息头stream << header.magicNumber<< header.totalSize<< header.messageType<< header.sequenceId;// 写入消息体stream.writeRawData(payload.constData(), payload.size());return data;
}
消息解析
MessageType parseMessage(const QByteArray &data, QByteArray &payload)
{if (data.size() < sizeof(MessageHeader)) {return MessageType::Invalid;}QDataStream stream(data);stream.setVersion(QDataStream::Qt_5_15);MessageHeader header;stream >> header.magicNumber>> header.totalSize>> header.messageType>> header.sequenceId;// 验证魔数if (header.magicNumber != 0x12345678) {return MessageType::Invalid;}// 验证消息长度if (header.totalSize > data.size()) {return MessageType::Invalid; // 数据不完整}// 读取消息体int payloadSize = header.totalSize - sizeof(MessageHeader);payload.resize(payloadSize);stream.readRawData(payload.data(), payloadSize);return static_cast<MessageType>(header.messageType);
}
五、QDataStream vs QByteArray vs JSON
| 特性 | QDataStream | QByteArray (手动) | JSON |
|---|---|---|---|
| 类型安全 | ✅ 自动 | ❌ 手动处理 | ✅ 有类型 |
| 字节序处理 | ✅ 自动 | ❌ 手动处理 | ✅ 无此问题 |
| 平台兼容性 | ✅ 优秀 | ❌ 差 | ✅ 优秀 |
| 性能 | ✅ 高 | ✅ 高 | ❌ 较低 |
| 可读性 | ❌ 二进制 | ❌ 二进制 | ✅ 文本 |
| 数据大小 | ✅ 紧凑 | ✅ 紧凑 | ❌ 较大 |
| 扩展性 | ✅ 好(版本控制) | ❌ 差 | ✅ 好 |