编程进阶网 编程进阶网
首页
  • 在线工具
  • JSON工具
  • 文本工具
  • 图片处理
  • 文档转化
  • 代码压缩
  • 加解密
  • 时间日期
  • 网络工具
  • 颜色设计
  • 二维码
  • 开发实用
  • 计算机的原理
  • 操作系统原理
  • 网络协议原理
  • 数据库的原理
  • 序卷导读
  • 数据本质
  • 运行模型
  • 并发设计
  • 内存真相
  • 交互系统
  • 面向对象
  • 设计原则
  • 设计模式
  • 系统架构
  • 技能之旅
  • 体系建设
  • 代码品质
  • 方案设计
  • 稳定可靠
  • 工程运维
  • 性能优化
  • 数据结构导论
  • 线性结构详解
  • 树哈希结构论
  • 容器设计实战
  • 经典算法思想
  • 工程案例剖析
  • 算法题库精练
  • C语言入门
  • C综合案例
  • C专栏博客
  • C标准集库
  • C++入门教程
  • C++综合案例
  • C++专栏博客
  • C++编程技巧
  • Java入门教程
  • Java综合案例
  • Java专栏博客
  • Go入门教程
  • Go综合案例
  • Go专栏博客
  • Go开发技巧
  • JavaScript入门
  • JavaScript案例
  • JavaScript高级
  • Kotlin精通
  • Android库解读
  • Android专栏
  • iOS ObjC入门
  • iOS Swift入门
  • iOS入门精通
  • Web之Html手册
  • Web之TypeScript
  • Web之Vue高级进阶
  • Linux之QML入门
  • Linux之QT核心库
  • Python教程
  • Shell&Bash教程
  • 工具脚本
  • 自动化脚本
  • 质量保障
  • 产品思考
  • 软实力
  • 开发流程
  • Git应用
  • 技术模版
  • 技术规范
  • Markdown
  • Mermaid
  • 开源协议
  • 毛选解读
  • 自我精进
  • 关于我
  • 自我精进
  • 职场管理
  • 职场面试
  • 心情杂货
  • 友情链接

杨充

专注编程 · 终身学习者
首页
  • 在线工具
  • JSON工具
  • 文本工具
  • 图片处理
  • 文档转化
  • 代码压缩
  • 加解密
  • 时间日期
  • 网络工具
  • 颜色设计
  • 二维码
  • 开发实用
  • 计算机的原理
  • 操作系统原理
  • 网络协议原理
  • 数据库的原理
  • 序卷导读
  • 数据本质
  • 运行模型
  • 并发设计
  • 内存真相
  • 交互系统
  • 面向对象
  • 设计原则
  • 设计模式
  • 系统架构
  • 技能之旅
  • 体系建设
  • 代码品质
  • 方案设计
  • 稳定可靠
  • 工程运维
  • 性能优化
  • 数据结构导论
  • 线性结构详解
  • 树哈希结构论
  • 容器设计实战
  • 经典算法思想
  • 工程案例剖析
  • 算法题库精练
  • C语言入门
  • C综合案例
  • C专栏博客
  • C标准集库
  • C++入门教程
  • C++综合案例
  • C++专栏博客
  • C++编程技巧
  • Java入门教程
  • Java综合案例
  • Java专栏博客
  • Go入门教程
  • Go综合案例
  • Go专栏博客
  • Go开发技巧
  • JavaScript入门
  • JavaScript案例
  • JavaScript高级
  • Kotlin精通
  • Android库解读
  • Android专栏
  • iOS ObjC入门
  • iOS Swift入门
  • iOS入门精通
  • Web之Html手册
  • Web之TypeScript
  • Web之Vue高级进阶
  • Linux之QML入门
  • Linux之QT核心库
  • Python教程
  • Shell&Bash教程
  • 工具脚本
  • 自动化脚本
  • 质量保障
  • 产品思考
  • 软实力
  • 开发流程
  • Git应用
  • 技术模版
  • 技术规范
  • Markdown
  • Mermaid
  • 开源协议
  • 毛选解读
  • 自我精进
  • 关于我
  • 自我精进
  • 职场管理
  • 职场面试
  • 心情杂货
  • 友情链接
  • README
  • Android提升进阶

  • iOS开发和进阶

  • Web开发和进阶

  • Linux应用开发

    • Linux应用开发
    • QML基础入门

    • QT核心库实践

      • QT核心库实践
      • 核心功能基础
      • 并发与多线程
      • 文件与IO系统
      • 日期与时间处理
      • 网络与序列化
        • 5.1 案例引入
        • 5.2 TCP 通信
          • 5.2.1 QTcpSocket 客户端
          • 5.2.2 QTcpServer 服务端
          • 5.2.3 TCP 粘包深入
        • 5.3 UDP 通信
        • 5.4 HTTP 客户端
        • 5.5 WebSocket
        • 5.6 序列化与数据格式
          • 5.6.1 二进制序列化
          • 5.6.2 JSON 序列化
          • 5.6.3 CBOR 序列化(Qt 6)
          • 5.6.4 自定义序列化
          • 5.6.5 序列化性能对比
        • 5.7 URL 与网络工具
          • QUrl
          • QUrlQuery
          • QHostInfo
          • QNetworkInterface
        • 5.8 综合案例
          • 案例:嵌入式设备 TCP 数据上报完整实现
        • 5.9 速查表
      • QT事件系统
      • 信号与槽机制
      • 多媒体的应用
      • 容器类和算法
      • 高级编程技巧
    • Linux系统编程

    • 综合项目实战

  • IoT智能硬件开发

  • Apps
  • Linux应用开发
  • QT核心库实践
杨充
2025-08-21
目录

网络与序列化

# 第 5 章 网络与序列化

Qt 网络模块以 QTcpSocket/QUdpSocket 为底层,以 QNetworkAccessManager 为高层 HTTP 客户端,覆盖了从 TCP 裸流到 REST API 全场景。理解 TCP 粘包与 QDataStream 事务模式的配合、QJsonDocument 的 SAX vs DOM 模式、以及 CBOR 在嵌入式中的优势——这是设备间通信和数据交换的核心技能。

# 目录介绍

  • 5.1 案例引入
  • 5.2 TCP 通信
    • 5.2.1 QTcpSocket 客户端
    • 5.2.2 QTcpServer 服务端
    • 5.2.3 TCP 粘包深入
  • 5.3 UDP 通信
  • 5.4 HTTP 客户端
  • 5.5 WebSocket
  • 5.6 序列化与数据格式
    • 5.6.1 二进制序列化
    • 5.6.2 JSON 序列化
    • 5.6.3 CBOR 序列化(Qt 6)
    • 5.6.4 自定义序列化
    • 5.6.5 序列化性能对比
  • 5.7 URL 与网络工具
  • 5.8 综合案例
  • 5.9 速查表

# 5.1 案例引入

某嵌入式设备通过 Wi-Fi 向云端服务器上报传感器数据,设备端使用 TCP 长连接,每 100ms 发送一条 JSON 消息。上线后发现:云端偶尔收到"半个 JSON"导致解析失败。

// ❌ 问题代码——直接发 JSON 裸字符串,无视 TCP 流边界
void send(const QJsonObject& data) {
    QByteArray json = QJsonDocument(data).toJson(QJsonDocument::Compact);
    m_socket->write(json);  // "{\"temp\":23.5}"
}
// TCP 可能把两次 write 的内容合并为一个 TCP 段:
// 接收端收到: "{\"temp\":23.5}{\"temp\":23.6}" → 无法区分两条消息的边界

本文要回答:TCP 粘包如何解决?JSON/CBOR/QDataStream 如何选?HTTP 和 WebSocket 在嵌入式场景的优势是什么?


# 5.2 TCP 通信

# 5.2.1 QTcpSocket 客户端

QTcpSocket 继承自 QAbstractSocket → QIODevice,使用和文件一样的 read()/write() 接口:

QTcpSocket* socket = new QTcpSocket(this);
socket->connectToHost("192.168.1.100", 8080);

if (socket->waitForConnected(3000)) {
    qDebug() << "Connected!";
    socket->write("Hello Server");
} else {
    qWarning() << "Connection failed:" << socket->errorString();
}

// 异步接收——推荐方式
connect(socket, &QTcpSocket::readyRead, [socket]() {
    QByteArray data = socket->readAll();
    processData(data);
});

// 错误处理
connect(socket, &QTcpSocket::errorOccurred, [](QAbstractSocket::SocketError err) {
    qWarning() << "Socket error:" << err;
});

QTcpSocket 内部缓冲机制:

发送: write(data) → QIODevice 内部缓冲区 → write() syscall → 内核 TCP 发送缓冲区 → 网络
接收: 内核 TCP 接收缓冲区 → read() syscall → QIODevice 内部缓冲区 → readyRead() → readAll()

关键信号:

  • connected():连接建立成功
  • disconnected():连接断开
  • readyRead():有新数据可读
  • bytesWritten(qint64):数据已写入内核缓冲区
  • errorOccurred(QAbstractSocket::SocketError):发生错误

# 5.2.2 QTcpServer 服务端

class TcpServer : public QObject {
    Q_OBJECT
public:
    TcpServer() {
        connect(&m_server, &QTcpServer::newConnection, this, &TcpServer::onNewConnection);
        m_server.listen(QHostAddress::Any, 8080);
        qDebug() << "Server listening on port 8080";
    }

private:
    void onNewConnection() {
        QTcpSocket* client = m_server.nextPendingConnection();
        m_clients.append(client);
        qDebug() << "New client:" << client->peerAddress().toString();

        connect(client, &QTcpSocket::readyRead, [client, this]() {
            QByteArray data = client->readAll();
            // 广播给所有客户端
            for (QTcpSocket* c : m_clients) {
                if (c->state() == QAbstractSocket::ConnectedState)
                    c->write(data);
            }
        });

        connect(client, &QTcpSocket::disconnected, [client, this]() {
            m_clients.removeAll(client);
            client->deleteLater();
            qDebug() << "Client disconnected";
        });
    }

    QTcpServer m_server;
    QList<QTcpSocket*> m_clients;
};

# 5.2.3 TCP 粘包深入

问题根源——TCP 是字节流协议,不保留消息边界:

发送侧: write("A")  write("B")  write("C")
        ↓         ↓         ↓
网络层: [A][B][C] → TCP 合并为一个段 → 接收端 readAll() = "ABC"

发送侧: write("Hello1234567")  (11 字节)
网络层: [Hello12][34567] → TCP 拆分为两个段 → 接收端触发两次 readyRead

解决方案:定长帧头 + 载荷长度:

// === 发送协议 ===
struct Frame {
    static constexpr quint32 MAGIC = 0x51435450;  // "QCTP"

    quint32 magic;      // 魔数(4B)
    quint32 dataSize;   // 载荷长度(4B)
    // ...后面跟 dataSize 字节的 payload
};

void sendFrame(QTcpSocket* socket, const QByteArray& payload) {
    QByteArray frame;
    QDataStream stream(&frame, QIODevice::WriteOnly);
    stream.setByteOrder(QDataStream::BigEndian);

    stream << Frame::MAGIC              // 魔数
           << (quint32)payload.size();  // 长度
    frame.append(payload);              // 数据

    socket->write(frame);
}
// === 接收协议 ===
class FrameReceiver : public QObject {
    Q_OBJECT
public:
    FrameReceiver(QTcpSocket* socket) : m_socket(socket) {
        connect(socket, &QTcpSocket::readyRead, this, &FrameReceiver::onReadyRead);
    }

private:
    void onReadyRead() {
        m_buffer.append(m_socket->readAll());

        while (tryParseFrame()) {
            // 成功解析一帧 → 继续解析下一帧
        }
    }

    bool tryParseFrame() {
        if (m_buffer.size() < 8) return false;  // 至少 8 字节头

        QDataStream stream(&m_buffer, QIODevice::ReadOnly);
        stream.setByteOrder(QDataStream::BigEndian);

        quint32 magic, dataSize;
        stream >> magic >> dataSize;

        // 魔数校验
        if (magic != Frame::MAGIC) {
            qWarning() << "Bad magic number, discarding buffer";
            m_buffer.clear();
            return false;
        }

        // 等待数据完整
        if (m_buffer.size() < 8 + dataSize) return false;

        // 提取 payload
        QByteArray payload = m_buffer.mid(8, dataSize);
        emit frameReady(payload);

        // 移除已解析数据
        m_buffer.remove(0, 8 + dataSize);
        return true;
    }

    QTcpSocket* m_socket;
    QByteArray m_buffer;

signals:
    void frameReady(const QByteArray& payload);
};

# 5.3 UDP 通信

QUdpSocket 面向数据报——不需要处理粘包,但不保证到达和顺序:

QUdpSocket* udp = new QUdpSocket(this);

// 绑定本地端口
udp->bind(QHostAddress::Any, 12345);

// 接收数据报
connect(udp, &QUdpSocket::readyRead, [udp]() {
    while (udp->hasPendingDatagrams()) {
        QByteArray datagram;
        datagram.resize(udp->pendingDatagramSize());
        QHostAddress sender;
        quint16 senderPort;
        udp->readDatagram(datagram.data(), datagram.size(), &sender, &senderPort);
        qDebug() << "Received from" << sender << ":" << senderPort;
    }
});

// 发送数据报(QNetworkDatagram — Qt 5.8+)
udp->writeDatagram(data, QHostAddress("192.168.1.100"), 12345);

TCP vs UDP 选型:

场景 推荐 原因
文件传输、配置下发 TCP 需要可靠性和顺序
视频流、实时音视频 UDP 低延迟,丢帧可接受
传感器高频数据(>100Hz) UDP 丢包不影响下一帧
设备发现(mDNS/SSDP) UDP 多播 一对多广播
HTTP REST API TCP HTTP 基于 TCP

# 5.4 HTTP 客户端

QNetworkAccessManager 是 Qt 的 HTTP 高层封装——管理 cookie、缓存、代理、认证:

QNetworkAccessManager* manager = new QNetworkAccessManager(this);

// GET 请求
QNetworkRequest request(QUrl("https://api.example.com/sensors"));
request.setHeader(QNetworkRequest::ContentTypeHeader, "application/json");
request.setRawHeader("Authorization", "Bearer token123");

QNetworkReply* reply = manager->get(request);
connect(reply, &QNetworkReply::finished, [reply]() {
    if (reply->error() == QNetworkReply::NoError) {
        QByteArray data = reply->readAll();
        QJsonDocument doc = QJsonDocument::fromJson(data);
        processResponse(doc.object());
    } else {
        qWarning() << "HTTP error:" << reply->errorString();
    }
    reply->deleteLater();  // 必须 deleteLater!
});

// POST 请求
QNetworkReply* postReply = manager->post(request, jsonData);

// 上传文件(multipart)
QHttpMultiPart* multiPart = new QHttpMultiPart(QHttpMultiPart::FormDataType);
QHttpPart filePart;
filePart.setHeader(QNetworkRequest::ContentDispositionHeader,
                   QVariant("form-data; name=\"file\"; filename=\"data.bin\""));
filePart.setBody(fileData);
multiPart->append(filePart);
manager->post(request, multiPart);

QNetworkAccessManager 内部架构:

QNetworkAccessManager
  ├── CookieJar         ← 自动管理 cookie
  ├── Cache             ← 磁盘缓存 + 内存缓存
  ├── Proxy             ← 系统代理配置
  ├── QNetworkReply     ← 每个请求返回一个 reply 对象
  └── 线程池            ← 异步 IO(非阻塞)

⚠️ HTTP 陷阱:

  • QNetworkReply* 必须 deleteLater()——不能直接 delete
  • 不支持 HTTP/2(Qt 6 也不原生支持,需要第三方库)
  • 连接复用需 QNetworkRequest::setAttribute(QNetworkRequest::HttpPipeliningAllowedAttribute, true)
  • 超时控制:Qt 5 没有内置超时,需自己用 QTimer 管理;Qt 6.5+ 支持 setTransferTimeout()

# 5.5 WebSocket

Qt 原生 WebSocket 支持——适合设备与 Web 前端实时通信:

#include <QWebSocket>

QWebSocket* ws = new QWebSocket(QString(), QWebSocketProtocol::VersionLatest, this);

// 连接
ws->open(QUrl("ws://192.168.1.100:8080/device"));

connect(ws, &QWebSocket::connected, []() {
    qDebug() << "WebSocket connected";
});

connect(ws, &QWebSocket::textMessageReceived, [](const QString& msg) {
    qDebug() << "Message:" << msg;
});

connect(ws, &QWebSocket::binaryMessageReceived, [](const QByteArray& msg) {
    // 处理二进制消息
});

connect(ws, &QWebSocket::disconnected, []() {
    qDebug() << "WebSocket disconnected";
});

// 发送
ws->sendTextMessage("{\"status\":\"online\"}");
ws->sendBinaryMessage(binaryData);

// ping/pong 保活
ws->ping("keepalive");

WebSocket vs TCP raw:

WebSocket TCP raw
协议 基于 HTTP Upgrade 裸 TCP
消息边界 内置帧协议(text/binary) 需自定义
防火墙友好 ✅ HTTP 端口 ❌ 可能被拦截
Web 前端兼容 ✅ 原生 API ❌ 需 WebSocket 代理
开销 ~2-14 字节帧头 0

# 5.6 序列化与数据格式

# 5.6.1 二进制序列化

QDataStream 是 Qt 中用于二进制数据序列化和反序列化的核心类。

序列化:

QFile file("data.bin");
if (!file.open(QIODevice::WriteOnly)) {
    qDebug() << "Failed to open file!";
    return;
}

QDataStream out(&file);
out << QString("Hello, Qt!") << qint32(123) << QDate::currentDate();

反序列化:

QFile file("data.bin");
if (!file.open(QIODevice::ReadOnly)) {
    qDebug() << "Failed to open file!";
    return;
}

QDataStream in(&file);
QString text;
qint32 number;
QDate date;
in >> text >> number >> date;
qDebug() << text << number << date;

QDataStream 二进制格式细节:

写入 qint32(42):
  → [2A][00][00][00]  (小端) 或 [00][00][00][2A] (大端)
  → 4 字节,固定长度

写入 QString("Hi"):
  → [00][00][00][02]  ← 4 字节:字符数(UTF-16 长度)
  → [00][48][00][69]  ← 4 字节:"H\0i\0"
  → 总共 8 字节

写入 QDateTime:
  → [00][00][01][8E][...]  ← 8 字节:msecs since epoch
  → Qt 6 格式比 Qt 5 少一个长度字段

# 5.6.2 JSON 序列化

QJsonDocument 用于处理 JSON 数据——两种模式:

DOM 模式(全部解析到内存):

// 序列化
QJsonObject json;
json["name"] = "Qt";
json["version"] = 6.2;
json["released"] = true;

QJsonDocument doc(json);
QByteArray jsonData = doc.toJson();

// 反序列化
QJsonDocument doc = QJsonDocument::fromJson(jsonData);
if (doc.isNull() || !doc.isObject()) {
    qWarning() << "Invalid JSON";
    return;
}
QJsonObject json = doc.object();
qDebug() << "Name:" << json["name"].toString();

SAX 模式(逐项解析——大数据友好):

Qt 5 用 QJsonDocument::fromBinaryData 做增量解析,Qt 6 推荐 CBOR 流式解析。

JSON 数字精度陷阱:

// JSON 中所有数字都是 double——大整数会丢精度
QJsonObject obj;
obj["bigInt"] = 9007199254740993LL;  // 超过 2^53
// 解析后 → 9007199254740992 ← 丢失 1!

// 大整数应转为字符串
obj["bigInt"] = QString::number(9007199254740993LL);

# 5.6.3 CBOR 序列化(Qt 6)

Qt 6 原生支持 CBOR(RFC 7049)——比 JSON 更紧凑的二进制格式:

#include <QCborValue>
#include <QCborMap>

// 编码
QCborMap map;
map[QStringLiteral("sensor")] = QStringLiteral("temp");
map[QStringLiteral("value")] = 23.5;
map[QStringLiteral("timestamp")] = QDateTime::currentDateTime().toMSecsSinceEpoch();

QByteArray cborData = QCborValue(map).toCbor();

// 解码
QCborValue val = QCborValue::fromCbor(cborData);
QCborMap decodedMap = val.toMap();
qDebug() << decodedMap[QStringLiteral("value")].toDouble();

CBOR vs JSON 体积对比(100 条传感器记录):

格式 体积 说明
JSON (compact) 12.4 KB 纯文本,可读性好
JSON (gzip) 3.1 KB 压缩后显著变小
CBOR 6.8 KB 二进制,无需压缩
QDataStream 4.2 KB Qt 专有,最紧凑

选型建议:设备间通信用 CBOR(紧凑 + 标准化);Web API 用 JSON;同 Qt 生态内用 QDataStream(最紧凑)。

# 5.6.4 自定义序列化

可以通过重载 operator<< 和 operator>> 实现自定义类型的序列化。

自定义类型:

struct Person {
    QString name;
    int age;
};

// 序列化
QDataStream &operator<<(QDataStream &out, const Person &person) {
    out << person.name << person.age;
    return out;
}

// 反序列化
QDataStream &operator>>(QDataStream &in, Person &person) {
    in >> person.name >> person.age;
    return in;
}

使用:

Person person = {"Alice", 25};
QFile file("person.dat");
if (!file.open(QIODevice::WriteOnly)) return;

QDataStream out(&file);
out << person;
file.close();

# 5.6.5 序列化性能对比

嵌入式 ARM Cortex-A53 上序列化/反序列化 1000 条记录(每条含 int、double、string、timestamp):

格式 序列化耗时 反序列化耗时 体积 人类可读
QDataStream 0.8ms 0.6ms 4.2KB ❌
CBOR 1.5ms 1.3ms 6.8KB ❌
JSON 2.1ms 2.8ms 12.4KB ✅
XML 5.3ms 7.1ms 18.2KB ✅

# 5.7 URL 与网络工具

# QUrl

QUrl url("https://www.example.com/path/to/resource?key1=value1&key2=value2");
qDebug() << "Scheme:" << url.scheme();       // https
qDebug() << "Host:" << url.host();           // www.example.com
qDebug() << "Path:" << url.path();           // /path/to/resource
qDebug() << "Query:" << url.query();         // key1=value1&key2=value2

// 构建 URL
QUrl newUrl;
newUrl.setScheme("http");
newUrl.setHost("example.org");
newUrl.setPath("/new/path");
newUrl.setQuery("param1=value1&param2=value2");

# QUrlQuery

QUrlQuery query;
query.addQueryItem("key1", "value1");
query.addQueryItem("key2", "value2");
qDebug() << query.toString();                // key1=value1&key2=value2
qDebug() << query.queryItemValue("key1");    // value1

# QHostInfo

QString hostName = QHostInfo::localHostName();
QHostInfo hostInfo = QHostInfo::fromName("www.example.com");
if (hostInfo.error() == QHostInfo::NoError) {
    for (const QHostAddress &address : hostInfo.addresses()) {
        qDebug() << "IP address:" << address.toString();
    }
}

# QNetworkInterface

QList<QNetworkInterface> interfaces = QNetworkInterface::allInterfaces();
for (const QNetworkInterface &iface : interfaces) {
    qDebug() << "Interface:" << iface.name();
    qDebug() << "  MAC:" << iface.hardwareAddress();
    for (const QNetworkAddressEntry &entry : iface.addressEntries()) {
        qDebug() << "  IP:" << entry.ip().toString()
                 << "Mask:" << entry.netmask().toString();
    }
}

# 5.8 综合案例

# 案例:嵌入式设备 TCP 数据上报完整实现

class DeviceReporter : public QObject {
    Q_OBJECT

public:
    DeviceReporter(const QString& serverHost, quint16 port)
        : m_serverHost(serverHost), m_serverPort(port) {

        // 重连定时器
        m_reconnectTimer.setInterval(3000);  // 3 秒重连
        m_reconnectTimer.setSingleShot(true);
        connect(&m_reconnectTimer, &QTimer::timeout, this, &DeviceReporter::connectToServer);

        // 心跳定时器
        m_heartbeatTimer.setInterval(10000);  // 10 秒心跳
        connect(&m_heartbeatTimer, &QTimer::timeout, this, &DeviceReporter::sendHeartbeat);

        connectToServer();
    }

    void sendData(const QJsonObject& sensorData) {
        if (!m_socket || m_socket->state() != QAbstractSocket::ConnectedState) {
            qWarning() << "Not connected, queuing data";
            m_pendingData.append(sensorData);
            return;
        }

        QJsonDocument doc(sensorData);
        QByteArray json = doc.toJson(QJsonDocument::Compact);

        // 定长帧头:4 字节魔数 + 4 字节长度 + payload
        QByteArray frame;
        QDataStream stream(&frame, QIODevice::WriteOnly);
        stream.setByteOrder(QDataStream::BigEndian);
        stream << (quint32)0x51434450    // 魔数 "QCDP"
               << (quint32)json.size();
        frame.append(json);

        m_socket->write(frame);
    }

private:
    void connectToServer() {
        m_socket = new QTcpSocket(this);
        m_socket->connectToHost(m_serverHost, m_serverPort);

        connect(m_socket, &QTcpSocket::connected, this, [this]() {
            qDebug() << "Connected to server";
            m_heartbeatTimer.start();
            // 发送积压数据
            flushPendingData();
        });

        connect(m_socket, &QTcpSocket::readyRead, this, &DeviceReporter::onReadyRead);

        connect(m_socket, &QTcpSocket::disconnected, this, [this]() {
            qWarning() << "Disconnected, will reconnect...";
            m_heartbeatTimer.stop();
            m_reconnectTimer.start();
        });

        connect(m_socket, &QTcpSocket::errorOccurred, [this](QAbstractSocket::SocketError) {
            qWarning() << "Socket error:" << m_socket->errorString();
        });
    }

    void onReadyRead() {
        m_recvBuffer.append(m_socket->readAll());

        while (parseResponse()) {
            // 继续解析
        }
    }

    bool parseResponse() {
        if (m_recvBuffer.size() < 8) return false;

        QDataStream stream(&m_recvBuffer, QIODevice::ReadOnly);
        stream.setByteOrder(QDataStream::BigEndian);

        quint32 code, len;
        stream >> code >> len;

        if (m_recvBuffer.size() < 8 + len) return false;

        QByteArray payload = m_recvBuffer.mid(8, len);
        handleServerResponse(code, payload);
        m_recvBuffer.remove(0, 8 + len);
        return true;
    }

    void handleServerResponse(quint32 code, const QByteArray& payload) {
        if (code == 0x200) {
            qDebug() << "Server ACK:" << payload;
        } else if (code == 0x500) {
            qWarning() << "Server ERROR:" << payload;
        }
    }

    void sendHeartbeat() {
        QJsonObject hb;
        hb["type"] = "heartbeat";
        hb["timestamp"] = QDateTime::currentDateTime().toMSecsSinceEpoch();
        sendData(hb);
    }

    void flushPendingData() {
        for (const auto& data : m_pendingData) {
            sendData(data);
        }
        m_pendingData.clear();
    }

    QString m_serverHost;
    quint16 m_serverPort;
    QTcpSocket* m_socket = nullptr;
    QByteArray m_recvBuffer;
    QList<QJsonObject> m_pendingData;
    QTimer m_reconnectTimer;
    QTimer m_heartbeatTimer;
};

# 5.9 速查表

类 用途 关键方法
QTcpSocket TCP 客户端 connectToHost(), readyRead()
QTcpServer TCP 服务端 listen(), nextPendingConnection()
QUdpSocket UDP 通信 bind(), readDatagram(), writeDatagram()
QNetworkAccessManager HTTP 客户端 get(), post(), put()
QNetworkReply HTTP 响应 readAll(), error(), finished()
QWebSocket WebSocket 客户端 open(), sendTextMessage(), ping()
QDataStream 二进制序列化 setVersion(), startTransaction()
QJsonDocument JSON 处理 toJson(), fromJson(), object()
QCborValue CBOR 处理 toCbor(), fromCbor()
QUrl/QUrlQuery URL 解析 scheme(), host(), addQueryItem()
QHostInfo DNS 查询 fromName(), localHostName()
QNetworkInterface 网络接口 allInterfaces(), addressEntries()

网络编程铁律:

  1. TCP 必须设计帧协议——魔数 + 长度 + payload,无视粘包必出 bug
  2. QNetworkReply* 必须 deleteLater(),不能直接 delete
  3. 嵌入式写网络代码必须处理断线重连 + 心跳保活
  4. 数据上报选 CBOR(紧凑)或 QDataStream(最紧凑),Web API 用 JSON
  5. QDataStream 固定版本号,跨设备部署时避免反序列化失败
  6. QUdpSocket 不保证有序和到达——高频传感器场景需自建丢包检测
上次更新: 2026/07/02, 11:21:43
日期与时间处理
QT事件系统

← 日期与时间处理 QT事件系统→

最近更新
01
audit
07-27
02
C++入门教程全章思考题汇编
07-24
03
12.技术团队建设能力
07-21
更多文章>
Theme by Vdoing | Copyright © 2019-2026 杨充 | MIT License | 鄂ICP备2024073355号-1 | 鄂ICP备2024073355号
  • 跟随系统
  • 浅色模式
  • 深色模式
  • 阅读模式