第18章 网络编程:TCP/UDP通信、QNetworkAccessManager、HTTP请求、WebSocket、网络下载管理器

网络编程,说白了就是让程序跟外界说话。我刚开始学Qt网络模块时,总觉得这东西很神秘——数据怎么就从一台机器跑到另一台机器上了?后来做项目多了,发现其实没那么玄乎。Qt把底层网络细节都封装好了,我们只需要调用几个类,就能实现各种网络通信。

这一章我会带你走一遍Qt网络编程的核心内容。从最基础的TCP/UDP,到HTTP请求,再到WebSocket实时通信,最后做一个实用的网络下载管理器。嗯,这些都是我在实际项目中反复用到的技术。

18.1 TCP通信:可靠的连接

TCP就像打电话。你先拨号,对方接听,然后你们开始对话。整个过程是可靠的,数据不会丢,顺序也不会乱。

Qt里实现TCP通信,主要用两个类:QTcpServerQTcpSocket。前者负责监听端口、接受连接,后者负责收发数据。

服务端代码示例

// 服务端:监听连接
QTcpServer *server = new QTcpServer(this);
if (!server->listen(QHostAddress::Any, 8888)) {
    qDebug() << "监听失败:" << server->errorString();
    return;
}
connect(server, &QTcpServer::newConnection, this, [=]() {
    QTcpSocket *socket = server->nextPendingConnection();
    qDebug() << "新客户端连接:" << socket->peerAddress().toString();
    
    // 读取数据
    connect(socket, &QTcpSocket::readyRead, this, [=]() {
        QByteArray data = socket->readAll();
        qDebug() << "收到数据:" << data;
        socket->write("已收到: " + data);
    });
});

客户端代码示例

// 客户端:连接服务端
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;
});

我的经验:TCP通信一定要处理好粘包问题。我早期做项目时,客户端一次性发了两条消息,服务端一次readAll全读出来了,结果解析出错。后来我习惯在每条消息前加4字节的长度头,这样接收方先读长度,再读指定字节的数据,就不会乱了。

18.2 UDP通信:快但不可靠

UDP就像发短信。你发出去就完了,对方收没收到你不知道,顺序也可能乱。但好处是快,没有连接建立的开销。

Qt里用QUdpSocket实现UDP通信。不需要建立连接,直接发数据就行。

// UDP发送端
QUdpSocket *udpSocket = new QUdpSocket(this);
QByteArray data = "Hello UDP!";
udpSocket->writeDatagram(data, QHostAddress::Broadcast, 9999);

// UDP接收端
QUdpSocket *udpReceiver = new QUdpSocket(this);
udpReceiver->bind(9999);
connect(udpReceiver, &QUdpSocket::readyRead, this, [=]() {
    while (udpReceiver->hasPendingDatagrams()) {
        QByteArray datagram;
        datagram.resize(udpReceiver->pendingDatagramSize());
        udpReceiver->readDatagram(datagram.data(), datagram.size());
        qDebug() << "收到UDP数据:" << datagram;
    }
});

注意:UDP数据报有大小限制,一般不超过65507字节。如果你要发大文件,别用UDP,老老实实用TCP或者自己分包。我曾经试过用UDP发一张图片,结果数据被截断了,图片显示不出来——嗯,那是个教训。

18.3 QNetworkAccessManager:HTTP请求的瑞士军刀

做网络应用,HTTP请求是家常便饭。Qt提供了QNetworkAccessManager,这个类封装了GET、POST、PUT、DELETE等所有常见HTTP操作。

我个人习惯把QNetworkAccessManager设计成单例,整个程序只用一个实例。这样连接复用效率更高。

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 responseData = reply->readAll();
        qDebug() << "请求成功:" << responseData;
    } else {
        qDebug() << "请求失败:" << reply->errorString();
    }
    reply->deleteLater();
});

POST请求示例

// 发送JSON数据
QJsonObject json;
json["name"] = "Qt";
json["version"] = 6.5;

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

QNetworkReply *reply = manager->post(request, QJsonDocument(json).toJson());
connect(reply, &QNetworkReply::finished, this, [=]() {
    // 处理响应...
});

核心要点:记得在请求完成后调用reply->deleteLater()释放资源。不然内存泄漏会让你头疼。我见过一个同事的程序跑了三天后崩溃,查了半天发现是网络请求的reply对象没释放,堆了上万个。

18.4 WebSocket:实时双向通信

WebSocket让服务器可以主动给客户端发消息。这在聊天、实时数据推送场景下特别有用。

Qt的WebSocket模块用起来很直观。客户端用QWebSocket,服务端用QWebSocketServer

// WebSocket客户端
QWebSocket *webSocket = new QWebSocket();
webSocket->open(QUrl("ws://localhost:12345"));

connect(webSocket, &QWebSocket::connected, this, [=]() {
    qDebug() << "WebSocket连接成功";
    webSocket->sendTextMessage("Hello Server!");
});

connect(webSocket, &QWebSocket::textMessageReceived, 
        this, [=](const QString &message) {
    qDebug() << "收到服务端消息:" << message;
});
// WebSocket服务端
QWebSocketServer *server = new QWebSocketServer("MyServer", 
    QWebSocketServer::NonSecureMode, this);

if (server->listen(QHostAddress::Any, 12345)) {
    connect(server, &QWebSocketServer::newConnection, 
            this, [=]() {
        QWebSocket *socket = server->nextPendingConnection();
        connect(socket, &QWebSocket::textMessageReceived, 
                this, [=](const QString &message) {
            qDebug() << "收到:" << message;
            socket->sendTextMessage("已收到: " + message);
        });
    });
}

避坑指南:我曾经在WebSocket的onClose事件里忘记清理socket对象,导致内存泄漏。建议在disconnected信号里调用socket->deleteLater()。另外,生产环境一定要用wss://加密连接,不然数据明文传输,太危险了。

18.5 网络下载管理器

最后,我们来做一个实用的下载管理器。它支持断点续传、进度显示、并发下载。

核心思路是用QNetworkAccessManager发起GET请求,设置Range头实现断点续传,用downloadProgress信号更新进度。

class DownloadManager : public QObject {
    Q_OBJECT
public:
    void startDownload(const QUrl &url, const QString &savePath) {
        QNetworkRequest request(url);
        
        // 检查本地文件,实现断点续传
        QFile *file = new QFile(savePath);
        if (file->exists()) {
            qint64 existingSize = file->size();
            request.setRawHeader("Range", 
                QString("bytes=%1-").arg(existingSize).toUtf8());
        }
        
        QNetworkReply *reply = manager->get(request);
        
        connect(reply, &QNetworkReply::readyRead, this, [=]() {
            file->write(reply->readAll());
        });
        
        connect(reply, &QNetworkReply::downloadProgress, 
                this, [=](qint64 received, qint64 total) {
            int percent = (total > 0) ? (received * 100 / total) : 0;
            emit progressChanged(url.toString(), percent);
        });
        
        connect(reply, &QNetworkReply::finished, this, [=]() {
            file->close();
            reply->deleteLater();
            emit downloadFinished(url.toString());
        });
    }

signals:
    void progressChanged(const QString &url, int percent);
    void downloadFinished(const QString &url);

private:
    QNetworkAccessManager *manager;
};

实用技巧:并发下载时,建议限制最大连接数。我一般设3-5个并发,太多会占用带宽,反而拖慢整体速度。另外,下载过程中要定期保存已下载的字节数,这样程序崩溃后还能续传。

知识体系总览

下面这张图总结了本章的核心知识点和它们之间的关系。你看一眼就能明白Qt网络编程的全貌。

Qt网络编程核心 TCP通信 QTcpServer / QTcpSocket 可靠连接 · 粘包处理 UDP通信 QUdpSocket 无连接 · 速度快 HTTP请求 QNetworkAccessManager GET/POST · JSON交互 WebSocket QWebSocket 实时双向 · 推送 网络下载管理器 断点续传 · 进度显示 · 并发控制 基于Qt Network模块,覆盖主流网络通信场景

你看,Qt网络编程其实就这几个核心模块。TCP和UDP是底层通信,HTTP和WebSocket是应用层协议,下载管理器则是综合应用。掌握了这些,大部分网络需求都能搞定。

实际开发中,我建议你从HTTP请求入手,因为用得最多。等需要实时通信了,再研究WebSocket。TCP和UDP嘛,做底层工具或游戏时才会用到。嗯,循序渐进就好。


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