第二十五章:Qt网络编程:TCP通信、UDP通信与HTTP请求
网络编程,说白了就是让程序跟别的地方的程序说话。我刚开始学Qt网络模块时,觉得这东西挺玄乎的——数据怎么就从一台机器跑到另一台机器上了?后来亲手写了个聊天程序,才真正明白底层的套路。
这一章,我们重点聊三个东西:TCP(面向连接的可靠通信)、UDP(无连接的快速通信)、HTTP(基于请求-响应的Web通信)。在Qt里,它们分别对应 QTcpSocket/QTcpServer、QUdpSocket 和 QNetworkAccessManager。
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() << "连接断开";
});
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 以内。
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();
});
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.dll 和 ssleay32.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 线程里阻塞等待网络数据,用好信号和槽,你的程序才会流畅。