第二十五章:Qt网络编程:TCP通信、UDP通信与HTTP请求

网络编程,说白了就是让程序跟别的地方的程序说话。我刚开始学Qt网络模块时,觉得这东西挺玄乎的——数据怎么就从一台机器跑到另一台机器上了?后来亲手写了个聊天程序,才真正明白底层的套路。

这一章,我们重点聊三个东西:TCP(面向连接的可靠通信)、UDP(无连接的快速通信)、HTTP(基于请求-响应的Web通信)。在Qt里,它们分别对应 QTcpSocket/QTcpServerQUdpSocketQNetworkAccessManager

Qt 网络编程三大模块 TCP 通信 面向连接 · 可靠 QTcpServer └─ listen() └─ nextPendingConnection() QTcpSocket └─ connectToHost() └─ readyRead / bytesWritten └─ write() / read() UDP 通信 无连接 · 快速 QUdpSocket └─ bind() └─ writeDatagram() └─ readDatagram() └─ readyRead └─ 广播 / 组播 HTTP 请求 请求-响应 · REST QNetworkAccessManager └─ get() / post() └─ put() / deleteResource() QNetworkReply └─ finished / readyRead └─ readAll() / error() └─ 异步回调模式 ▲ 三种通信方式各有适用场景,实际项目中常组合使用

25.1 TCP通信:QTcpSocket 与 QTcpServer

TCP 是什么?你可以把它想象成打电话。先拨号(建立连接),然后说话(收发数据),最后挂断(断开连接)。整个过程是有序的、可靠的——你说了什么,对方一定能按顺序收到。

在 Qt 里,TCP 通信分两端:服务端QTcpServer 监听连接,客户端 用 QTcpSocket 发起连接。

25.1.1 服务端:QTcpServer

服务端的套路很固定:创建一个 QTcpServer,调用 listen() 绑定端口,然后等着 newConnection 信号。一旦有客户端连上来,就用 nextPendingConnection() 拿到对应的 QTcpSocket 对象。

// 服务端示例
QTcpServer *server = new QTcpServer(this);
if (!server->listen(QHostAddress::Any, 8888)) {
    qDebug() << "监听失败:" << server->errorString();
    return;
}
qDebug() << "服务已启动,端口: 8888";

connect(server, &QTcpServer::newConnection, this, [=]() {
    QTcpSocket *clientSocket = server->nextPendingConnection();
    qDebug() << "新客户端:" << clientSocket->peerAddress().toString();

    connect(clientSocket, &QTcpSocket::readyRead, this, [=]() {
        QByteArray data = clientSocket->readAll();
        qDebug() << "收到:" << data;
        clientSocket->write("已收到你的消息");
    });

    connect(clientSocket, &QTcpSocket::disconnected, this, [=]() {
        qDebug() << "客户端断开";
        clientSocket->deleteLater();
    });
});
我的经验:记得在 disconnected 信号里调用 deleteLater() 释放 socket 对象。我曾经忘了这步,结果服务端跑了一天后内存暴涨——每个断开的客户端都留下了一个僵尸 socket。

25.1.2 客户端:QTcpSocket

客户端更简单:创建一个 QTcpSocket,调用 connectToHost() 连上去,然后收发数据。

// 客户端示例
QTcpSocket *socket = new QTcpSocket(this);
socket->connectToHost("127.0.0.1", 8888);

connect(socket, &QTcpSocket::connected, this, [=]() {
    qDebug() << "连接成功";
    socket->write("Hello Server!");
});

connect(socket, &QTcpSocket::readyRead, this, [=]() {
    QByteArray response = socket->readAll();
    qDebug() << "服务端回复:" << response;
});

connect(socket, &QTcpSocket::disconnected, this, [=]() {
    qDebug() << "连接断开";
});
注意:TCP 是流式协议,没有消息边界。你发送两次 write("A")write("B"),对方可能一次收到 "AB",也可能分两次收到。我建议你在应用层自己定义消息格式,比如用「长度头+数据体」的方式分包。

25.2 UDP通信:QUdpSocket

UDP 就像寄明信片——你写好地址扔进邮筒,但不保证对方一定能收到,也不保证顺序。好处是快,没有连接建立的开销。

在 Qt 里,QUdpSocket 同时支持收发。你不需要区分服务端和客户端,只需要 bind() 到一个端口,然后用 writeDatagram()readDatagram() 收发数据报。

// UDP 接收端
QUdpSocket *udpSocket = new QUdpSocket(this);
udpSocket->bind(8888);

connect(udpSocket, &QUdpSocket::readyRead, this, [=]() {
    while (udpSocket->hasPendingDatagrams()) {
        QByteArray datagram;
        datagram.resize(udpSocket->pendingDatagramSize());
        QHostAddress sender;
        quint16 senderPort;
        udpSocket->readDatagram(datagram.data(), datagram.size(), &sender, &senderPort);
        qDebug() << "来自" << sender.toString() << ":" << datagram;
    }
});

// UDP 发送端
QUdpSocket *sendSocket = new QUdpSocket(this);
QByteArray msg = "Hello UDP!";
sendSocket->writeDatagram(msg, QHostAddress::Broadcast, 8888);  // 广播发送

你想想看,UDP 最适合什么场景?视频通话、游戏同步、日志收集——这些场景丢几个包无所谓,但延迟必须低。我在做一个实时数据采集项目时,就用 UDP 广播来分发传感器数据,延迟控制在 1ms 以内。

广播与组播:UDP 支持广播(发给局域网所有设备)和组播(发给特定组)。广播地址是 255.255.255.255 或子网广播地址。组播需要加入组播组,用 joinMulticastGroup()

25.3 HTTP请求:QNetworkAccessManager

现在很多应用都要跟 Web API 打交道——登录验证、获取天气、上传文件。Qt 提供了 QNetworkAccessManager,专门用来发 HTTP 请求。

它的核心思想是异步。你发一个请求,它返回一个 QNetworkReply 对象,然后通过信号通知你结果。整个过程不会阻塞 UI 线程。

// GET 请求示例
QNetworkAccessManager *manager = new QNetworkAccessManager(this);
QNetworkRequest request(QUrl("https://api.example.com/data"));
request.setHeader(QNetworkRequest::ContentTypeHeader, "application/json");

QNetworkReply *reply = manager->get(request);

connect(reply, &QNetworkReply::finished, this, [=]() {
    if (reply->error() == QNetworkReply::NoError) {
        QByteArray data = reply->readAll();
        qDebug() << "响应数据:" << data;
    } else {
        qDebug() << "请求失败:" << reply->errorString();
    }
    reply->deleteLater();
});

// POST 请求示例(发送 JSON)
QJsonObject json;
json["name"] = "Qt";
json["version"] = 6;
QByteArray postData = QJsonDocument(json).toJson();

QNetworkRequest postRequest(QUrl("https://api.example.com/submit"));
postRequest.setHeader(QNetworkRequest::ContentTypeHeader, "application/json");

QNetworkReply *postReply = manager->post(postRequest, postData);
connect(postReply, &QNetworkReply::finished, this, [=]() {
    // 处理响应...
    postReply->deleteLater();
});
避坑指南:我曾经在循环里连续发了几百个 GET 请求,结果程序卡死了。后来发现 QNetworkAccessManager 默认有并发限制(通常是 6 个)。解决办法是:要么用队列控制并发数,要么设置 QNetworkRequest::setTransferTimeout() 避免死等。

25.3.1 常用 HTTP 方法

方法 QNetworkAccessManager 接口 用途
GET get(request) 获取资源
POST post(request, data) 提交数据(创建资源)
PUT put(request, data) 更新资源
DELETE deleteResource(request) 删除资源
HEAD head(request) 获取响应头(不获取体)

25.3.2 处理 HTTPS 和 SSL

现在的 API 基本都是 HTTPS。Qt 默认支持 SSL,但你需要确保系统里安装了 OpenSSL 库。在 Windows 上,我建议把 libeay32.dllssleay32.dll 放在可执行文件旁边。

如果遇到 SSL 错误,可以这样处理:

connect(reply, &QNetworkReply::sslErrors, this, [=](const QList<QSslError> &errors) {
    qWarning() << "SSL 错误:";
    for (const QSslError &e : errors) {
        qWarning() << e.errorString();
    }
    // 注意:生产环境不要忽略 SSL 错误!
    // reply->ignoreSslErrors();
});
安全提醒:千万不要在生产环境里调用 ignoreSslErrors()。我见过有人为了省事直接忽略所有 SSL 错误,结果被中间人攻击窃取了用户密码。正确的做法是:检查证书是否可信,或者使用证书固定(Certificate Pinning)。

25.4 三种通信方式的选择

最后,我帮你总结一下什么时候该用哪种:

  • TCP:需要可靠传输、数据顺序重要、文件传输、聊天消息、远程控制。说白了,丢一个包都不行的时候用 TCP。
  • UDP:实时性要求高、可以容忍丢包、视频流、游戏状态同步、日志收集。延迟比可靠性更重要时用 UDP。
  • HTTP:跟 Web 服务交互、REST API、上传下载文件、获取远程配置。只要是走 HTTP 协议的,就用 QNetworkAccessManager

嗯,网络编程这块内容不少,但核心就这几个类。你动手写一个简单的聊天室或者天气查询工具,很快就能上手。记住一点:异步编程是 Qt 网络模块的灵魂——别在 UI 线程里阻塞等待网络数据,用好信号和槽,你的程序才会流畅。


公众号:蓝海资料掘金营,微信deep3321
公众号:蓝海资料掘金营,微信deep3321