Qt - QDataStream 详细介绍

QDataStream 是 Qt 中用于序列化和反序列化二进制数据的类。它可以将各种数据类型(包括基本类型、字符串、容器等)转换为字节序列,以便存储或传输,并且可以重新构造这些数据。

主要特点:

  1. 支持多种数据类型:包括整型、浮点型、字符串、字节数组等。

  2. 自动处理字节序:可以设置字节序(大端或小端)。

  3. 版本控制:可以设置版本号以处理数据结构的演变。

  4. 简单易用:使用流操作符(<< 和 >>)进行读写。

在聊天程序中,我们可以使用 QDataStream 来构建和解析消息,从而避免手动拼接和解析字符串。

下面是一个简单的示例,展示如何使用 QDataStream 序列化和反序列化一个包含消息类型、发送者、接收者和内容的消息。

假设我们的消息结构如下:

  • 消息类型(quint8)

  • 发送者(QString)

  • 接收者(QString)

  • 内容(QString)

序列化(写入)示例:

cpp
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;
}

反序列化(读取)示例:

cpp
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个字节表示消息长度,再发送消息内容。

发送方:

cpp
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构建文本消息:

cpp
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构建:

cpp
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; // 文本消息类型为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);

    // 文本内容(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默认就是大端字节序,但我们可以显式设置:

cpp
stream.setByteOrder(QDataStream::BigEndian);

这样,客户端和服务器端就可以使用相同的字节序来解析整数了。

总结:

  1. 使用QDataStream可以简化客户端的消息构建和解析。

  2. 调整协议,为可变长度的字段加上长度前缀。

  3. 设置QDataStream为网络字节序(大端),以便与C语言服务器交互。

由于时间关系,这里只给出文本消息的构建和解析示例,文件消息类似。

希望这能帮助您改进网络传输方式。

 

QDataStream 详细介绍

一、QDataStream 是什么?

QDataStream 是 Qt 框架提供的用于二进制数据序列化和反序列化的类。它提供了一种简单、类型安全的方式来读写二进制数据,自动处理平台差异(如字节序、数据类型大小等)。

二、主要特点

1. 类型安全

cpp
QDataStream stream(&data, QIODevice::WriteOnly);
stream << QString("Hello") << qint32(100) << qreal(3.14);
// 自动知道每种类型的大小和格式

2. 自动字节序处理

cpp
stream.setByteOrder(QDataStream::LittleEndian);   // 小端
stream.setByteOrder(QDataStream::BigEndian);      // 大端(网络字节序,默认)

3. 版本控制

cpp
stream.setVersion(QDataStream::Qt_5_15);  // 设置序列化版本
// 不同版本的Qt可能有不同的序列化格式

4. 支持多种数据类型

  • 基本类型:qint8quint16qint32qint64floatdoublebool

  • Qt类型:QStringQByteArrayQListQMapQVariantQDateTimeQPixmapQImage

三、基本用法

1. 序列化(写入)

cpp
// 创建字节数组
QByteArray data;
QDataStream out(&data, QIODevice::WriteOnly);
out.setVersion(QDataStream::Qt_5_15);  // 设置版本

// 写入数据
out << QString("张三")       // 自动写入字符串长度+内容
    << qint32(25)            // 4字节整数
    << QDateTime::currentDateTime();  // 日期时间

// 现在data包含了序列化的二进制数据

2. 反序列化(读取)

cpp
// 从字节数组读取
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() << "读取数据失败";
}

四、在网络传输中的应用

消息结构设计

cpp
// 消息头
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;
}

消息解析

cpp
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
类型安全 ✅ 自动 ❌ 手动处理 ✅ 有类型
字节序处理 ✅ 自动 ❌ 手动处理 ✅ 无此问题
平台兼容性 ✅ 优秀 ❌ 差 ✅ 优秀
性能 ✅ 高 ✅ 高 ❌ 较低
可读性 ❌ 二进制 ❌ 二进制 ✅ 文本
数据大小 ✅ 紧凑 ✅ 紧凑 ❌ 较大
扩展性 ✅ 好(版本控制) ❌ 差 ✅ 好
posted @ 2025-12-29 18:58  [BORUTO]  阅读(5)  评论(0)    收藏  举报