跳到主要内容

网络编程

Qt 提供了强大的网络编程支持,包括 HTTP 请求、TCP/UDP 通信、WebSocket 等功能。本章将详细介绍 Qt 网络编程的各个方面。

网络编程概述

Qt 的网络功能主要包含在 Qt Network 模块中,提供了从底层 Socket 编程到高层 HTTP 请求的完整解决方案。选择合适的网络 API 取决于你的需求:

需求推荐方案复杂度
HTTP/HTTPS 请求QNetworkAccessManager
实时双向通信QWebSocket
自定义协议QTcpSocket/QUdpSocket
文件传输QNetworkAccessManager + 分片

QNetworkAccessManager(HTTP 请求)

QNetworkAccessManager 是 Qt 提供的高层网络 API,用于发送 HTTP 请求和处理响应。它内部管理连接池、缓存和 Cookie,是进行 HTTP 通信的首选方式。

基本概念

整个网络访问 API 围绕 QNetworkAccessManager 构建:

  • QNetworkAccessManager:管理网络请求的核心类,持有代理、缓存等配置
  • QNetworkRequest:封装请求信息(URL、请求头等)
  • QNetworkReply:封装响应信息(数据、响应头、状态码等)

一个应用程序通常只需要一个 QNetworkAccessManager 实例,它会自动管理连接复用和请求队列。对于桌面平台上的 HTTP 协议,每个主机/端口组合最多并行执行 6 个请求。

GET 请求

GET 请求用于从服务器获取数据:

#include <QNetworkAccessManager>
#include <QNetworkReply>
#include <QNetworkRequest>

class HttpClient : public QObject
{
Q_OBJECT

public:
HttpClient(QObject *parent = nullptr) : QObject(parent)
{
manager = new QNetworkAccessManager(this);
}

void get(const QString &url)
{
QNetworkRequest request(QUrl(url));

// 设置请求头
request.setHeader(QNetworkRequest::ContentTypeHeader, "application/json");
request.setRawHeader("User-Agent", "MyApp/1.0");

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

// 连接信号处理响应
connect(reply, &QNetworkReply::finished, this, [=]() {
handleReply(reply);
});
}

private:
void handleReply(QNetworkReply *reply)
{
if (reply->error() == QNetworkReply::NoError) {
// 获取响应数据
QByteArray data = reply->readAll();
int statusCode = reply->attribute(
QNetworkRequest::HttpStatusCodeAttribute).toInt();

qDebug() << "状态码:" << statusCode;
qDebug() << "响应:" << QString::fromUtf8(data);

// 获取响应头
QString contentType = reply->header(
QNetworkRequest::ContentTypeHeader).toString();
qDebug() << "Content-Type:" << contentType;
} else {
// 处理错误
qDebug() << "请求失败:" << reply->errorString();
qDebug() << "错误码:" << reply->error();
}

// 释放 reply 对象
reply->deleteLater();
}

QNetworkAccessManager *manager;
};

// 使用示例
HttpClient client;
client.get("https://api.example.com/data");

POST 请求

POST 请求用于向服务器提交数据:

// 发送 JSON 数据
void postJson(const QString &url, const QJsonObject &json)
{
QNetworkRequest request(QUrl(url));
request.setHeader(QNetworkRequest::ContentTypeHeader, "application/json");

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

QNetworkReply *reply = manager->post(request, data);

connect(reply, &QNetworkReply::finished, this, [=]() {
if (reply->error() == QNetworkReply::NoError) {
QByteArray response = reply->readAll();
QJsonDocument responseDoc = QJsonDocument::fromJson(response);
QJsonObject responseObject = responseDoc.object();
// 处理响应...
}
reply->deleteLater();
});
}

// 使用示例
QJsonObject loginData;
loginData["username"] = "admin";
loginData["password"] = "123456";
postJson("https://api.example.com/login", loginData);

表单数据提交

提交表单数据(application/x-www-form-urlencoded):

void postForm(const QString &url)
{
QNetworkRequest request(QUrl(url));
request.setHeader(QNetworkRequest::ContentTypeHeader,
"application/x-www-form-urlencoded");

// 构建表单数据
QUrlQuery params;
params.addQueryItem("username", "张三");
params.addQueryItem("email", "[email protected]");
params.addQueryItem("age", "25");

QByteArray data = params.toString(QUrl::FullyEncoded).toUtf8();

QNetworkReply *reply = manager->post(request, data);
// ... 处理响应
}

文件上传(Multipart)

上传文件需要使用 QHttpMultiPart

#include <QHttpMultiPart>
#include <QHttpPart>
#include <QFile>

void uploadFile(const QString &url, const QString &filePath)
{
QNetworkRequest request(QUrl(url));

// 创建 multipart 对象
QHttpMultiPart *multiPart = new QHttpMultiPart(QHttpMultiPart::FormDataType);

// 添加普通字段
QHttpPart textPart;
textPart.setHeader(QNetworkRequest::ContentDispositionHeader,
QVariant("form-data; name=\"description\""));
textPart.setBody("文件描述内容");
multiPart->append(textPart);

// 添加文件
QFile *file = new QFile(filePath);
if (!file->open(QIODevice::ReadOnly)) {
delete file;
delete multiPart;
return;
}

QHttpPart filePart;
QString fileName = QFileInfo(filePath).fileName();
filePart.setHeader(QNetworkRequest::ContentDispositionHeader,
QVariant(QString("form-data; name=\"file\"; filename=\"%1\"").arg(fileName)));
filePart.setHeader(QNetworkRequest::ContentTypeHeader,
QVariant("application/octet-stream"));
filePart.setBodyDevice(file);
file->setParent(multiPart); // 文件随 multiPart 一起删除
multiPart->append(filePart);

// 发送请求
QNetworkReply *reply = manager->post(request, multiPart);
multiPart->setParent(reply); // multiPart 随 reply 一起删除

connect(reply, &QNetworkReply::finished, this, [=]() {
if (reply->error() == QNetworkReply::NoError) {
qDebug() << "上传成功";
} else {
qDebug() << "上传失败:" << reply->errorString();
}
reply->deleteLater();
});

// 上传进度
connect(reply, &QNetworkReply::uploadProgress, this,
[](qint64 bytesSent, qint64 bytesTotal) {
if (bytesTotal > 0) {
int percent = static_cast<int>((bytesSent * 100) / bytesTotal);
qDebug() << "上传进度:" << percent << "%";
}
});
}

文件下载

下载文件并显示进度:

void downloadFile(const QString &url, const QString &savePath)
{
QNetworkRequest request(QUrl(url));
QNetworkReply *reply = manager->get(request);

QFile *file = new QFile(savePath);
if (!file->open(QIODevice::WriteOnly)) {
qDebug() << "无法创建文件:" << savePath;
delete file;
reply->deleteLater();
return;
}

// 接收数据时写入文件
connect(reply, &QNetworkReply::readyRead, this, [=]() {
file->write(reply->readAll());
});

// 下载完成
connect(reply, &QNetworkReply::finished, this, [=]() {
file->close();
delete file;

if (reply->error() == QNetworkReply::NoError) {
qDebug() << "下载完成:" << savePath;
emit downloadFinished(savePath, true);
} else {
qDebug() << "下载失败:" << reply->errorString();
// 删除未完成的文件
QFile::remove(savePath);
emit downloadFinished(savePath, false);
}

reply->deleteLater();
});

// 下载进度
connect(reply, &QNetworkReply::downloadProgress, this,
[](qint64 bytesReceived, qint64 bytesTotal) {
if (bytesTotal > 0) {
int percent = static_cast<int>((bytesReceived * 100) / bytesTotal);
qDebug() << "下载进度:" << percent << "%"
<< "(" << bytesReceived << "/" << bytesTotal << "bytes)";
}
});
}

超时设置

Qt 6 提供了内置的超时设置功能:

// 方式一:全局设置(Qt 6.5+)
manager->setTransferTimeout(30000); // 30秒超时

// 方式二:单个请求设置(Qt 6+)
QNetworkRequest request(QUrl("https://api.example.com/data"));
request.setTransferTimeout(30000); // 30秒超时

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

// 方式三:使用定时器(兼容旧版本)
QTimer::singleShot(30000, reply, [reply]() {
if (reply->isRunning()) {
reply->abort();
qDebug() << "请求超时";
}
});

超时时间单位为毫秒,默认值是 30 秒。设置为 0 或负数表示禁用超时。

重定向处理

Qt 6 提供了灵活的重定向策略:

// 设置重定向策略
manager->setRedirectPolicy(QNetworkRequest::NoLessSafeRedirectPolicy);

// 可用的重定向策略:
// - QNetworkRequest::ManualRedirectPolicy: 手动处理重定向(默认)
// - QNetworkRequest::NoLessSafeRedirectPolicy: 允许同级别或更安全的重定向
// (http->http, http->https, https->https)
// - QNetworkRequest::SameOriginRedirectPolicy: 仅允许同源重定向
// - QNetworkRequest::UserVerifiedRedirectPolicy: 需要用户确认

// 单个请求设置
QNetworkRequest request(QUrl("https://example.com/old"));
request.setAttribute(QNetworkRequest::RedirectPolicyAttribute,
QNetworkRequest::NoLessSafeRedirectPolicy);

// 获取重定向后的 URL
connect(reply, &QNetworkReply::finished, this, [=]() {
QUrl redirectUrl = reply->attribute(
QNetworkRequest::RedirectionTargetAttribute).toUrl();
if (!redirectUrl.isEmpty()) {
qDebug() << "重定向到:" << redirectUrl.toString();
}
});

代理设置

可以通过代理服务器发送请求:

// 设置全局代理
QNetworkProxy proxy;
proxy.setType(QNetworkProxy::HttpProxy);
proxy.setHostName("proxy.example.com");
proxy.setPort(8080);
proxy.setUser("username"); // 可选
proxy.setPassword("password"); // 可选

manager->setProxy(proxy);

// 使用系统代理
manager->setProxy(QNetworkProxy::applicationProxy());

// 使用代理工厂(动态选择代理)
class MyProxyFactory : public QNetworkProxyFactory
{
public:
QList<QNetworkProxy> queryProxy(const QNetworkProxyQuery &query) override
{
QList<QNetworkProxy> proxies;

QString host = query.peerHostName();
if (host.endsWith(".local")) {
// 内网地址不使用代理
proxies << QNetworkProxy(QNetworkProxy::NoProxy);
} else {
// 外网地址使用代理
proxies << QNetworkProxy(QNetworkProxy::HttpProxy,
"proxy.example.com", 8080);
}

return proxies;
}
};

manager->setProxyFactory(new MyProxyFactory);

认证处理

处理需要认证的请求:

// 处理服务器认证
connect(manager, &QNetworkAccessManager::authenticationRequired,
this, [](QNetworkReply *reply, QAuthenticator *authenticator) {
// 检查是哪个请求需要认证
QUrl url = reply->url();
qDebug() << "需要认证:" << url.toString();

// 提供认证信息
authenticator->setUser("admin");
authenticator->setPassword("secret");
});

// 处理代理认证
connect(manager, &QNetworkAccessManager::proxyAuthenticationRequired,
this, [](const QNetworkProxy &proxy, QAuthenticator *authenticator) {
qDebug() << "代理需要认证:" << proxy.hostName();
authenticator->setUser("proxy_user");
authenticator->setPassword("proxy_pass");
});

SSL/TLS 安全连接

处理 HTTPS 请求时的 SSL 相关问题:

#include <QSslConfiguration>
#include <QSslSocket>

void secureRequest(const QString &url)
{
QNetworkRequest request(QUrl(url));

// 自定义 SSL 配置
QSslConfiguration sslConfig = request.sslConfiguration();
sslConfig.setProtocol(QSsl::TlsV1_2OrLater); // 最低 TLS 1.2
sslConfig.setPeerVerifyMode(QSslSocket::VerifyPeer); // 验证证书
request.setSslConfiguration(sslConfig);

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

// 处理 SSL 错误
connect(reply, &QNetworkReply::sslErrors, this,
[](const QList<QSslError> &errors) {
for (const QSslError &error : errors) {
qDebug() << "SSL 错误:" << error.errorString();
}

// 警告:仅用于开发和测试!生产环境应正确处理证书
// reply->ignoreSslErrors();
});

// SSL 握手完成
connect(manager, &QNetworkAccessManager::encrypted, this,
[](QNetworkReply *reply) {
qDebug() << "SSL 握手完成";

// 可以在这里检查证书信息
QSslConfiguration config = reply->sslConfiguration();
QSslCertificate cert = config.peerCertificate();
qDebug() << "证书主题:" << cert.subjectInfo(QSslCertificate::CommonName);
});
}

// 忽略特定 SSL 错误(如自签名证书)
void acceptSelfSignedCertificate(const QString &url)
{
// 加载自签名证书
QList<QSslCertificate> certs = QSslCertificate::fromPath("server-cert.pem");
if (certs.isEmpty()) {
qDebug() << "无法加载证书";
return;
}

// 创建期望的 SSL 错误列表
QList<QSslError> expectedErrors;
expectedErrors << QSslError(QSslError::SelfSignedCertificate, certs.at(0));

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

// 忽略这些特定的错误
reply->ignoreSslErrors(expectedErrors);
}
#include <QNetworkCookieJar>
#include <QNetworkCookie>

// 设置 Cookie Jar
QNetworkCookieJar *cookieJar = new QNetworkCookieJar(this);
manager->setCookieJar(cookieJar);

// 手动设置 Cookie
QNetworkCookie cookie;
cookie.setName("sessionId");
cookie.setValue("abc123");
cookie.setDomain(".example.com");
cookie.setPath("/");
cookie.setExpirationDate(QDateTime::currentDateTime().addDays(7));

QList<QNetworkCookie> cookies;
cookies << cookie;
cookieJar->setCookiesFromUrl(cookies, QUrl("https://example.com"));

// 自定义持久化 Cookie Jar
class PersistentCookieJar : public QNetworkCookieJar
{
public:
PersistentCookieJar(const QString &filePath, QObject *parent = nullptr)
: QNetworkCookieJar(parent), m_filePath(filePath)
{
load();
}

~PersistentCookieJar()
{
save();
}

bool setCookiesFromUrl(const QList<QNetworkCookie> &cookieList,
const QUrl &url) override
{
bool result = QNetworkCookieJar::setCookiesFromUrl(cookieList, url);
if (result) {
save();
}
return result;
}

private:
void load()
{
QFile file(m_filePath);
if (file.open(QIODevice::ReadOnly)) {
QByteArray data = file.readAll();
QList<QNetworkCookie> cookies = QNetworkCookie::parseCookies(data);
setAllCookies(cookies);
file.close();
}
}

void save()
{
QFile file(m_filePath);
if (file.open(QIODevice::WriteOnly)) {
QList<QNetworkCookie> cookies = allCookies();
for (const QNetworkCookie &cookie : cookies) {
file.write(cookie.toRawForm());
file.write("\n");
}
file.close();
}
}

QString m_filePath;
};

缓存管理

#include <QNetworkDiskCache>

// 设置磁盘缓存
QNetworkDiskCache *cache = new QNetworkDiskCache(this);
cache->setCacheDirectory(QStandardPaths::writableLocation(
QStandardPaths::CacheLocation) + "/http_cache");
cache->setMaximumCacheSize(100 * 1024 * 1024); // 100 MB

manager->setCache(cache);

// 控制缓存行为
QNetworkRequest request(QUrl("https://example.com/data"));

// 强制使用缓存
request.setAttribute(QNetworkRequest::CacheLoadControlAttribute,
QNetworkRequest::PreferCache);

// 强制从网络获取
request.setAttribute(QNetworkRequest::CacheLoadControlAttribute,
QNetworkRequest::AlwaysNetwork);

// 检查响应是否来自缓存
connect(reply, &QNetworkReply::finished, this, [=]() {
bool fromCache = reply->attribute(
QNetworkRequest::SourceIsFromCacheAttribute).toBool();
qDebug() << (fromCache ? "来自缓存" : "来自网络");
});

WebSocket 编程

WebSocket 是一种全双工通信协议,适用于实时通信场景,如聊天应用、实时数据推送等。Qt 通过 QtWebSockets 模块提供 WebSocket 支持。

模块配置

在 CMakeLists.txt 中添加:

find_package(Qt6 REQUIRED COMPONENTS WebSockets)
target_link_libraries(myapp PRIVATE Qt6::WebSockets)

WebSocket 客户端

#include <QWebSocket>
#include <QWebSocketHandshakeOptions>

class WebSocketClient : public QObject
{
Q_OBJECT

public:
WebSocketClient(QObject *parent = nullptr) : QObject(parent)
{
socket = new QWebSocket();

// 连接成功
connect(socket, &QWebSocket::connected, this, [this]() {
qDebug() << "WebSocket 连接成功";
emit connected();
});

// 断开连接
connect(socket, &QWebSocket::disconnected, this, [this]() {
qDebug() << "WebSocket 断开连接";
emit disconnected();
});

// 收到文本消息
connect(socket, &QWebSocket::textMessageReceived, this,
[this](const QString &message) {
qDebug() << "收到消息:" << message;
emit messageReceived(message);
});

// 收到二进制消息
connect(socket, &QWebSocket::binaryMessageReceived, this,
[this](const QByteArray &data) {
qDebug() << "收到二进制数据:" << data.size() << "bytes";
emit binaryReceived(data);
});

// 错误处理
connect(socket, &QWebSocket::errorOccurred, this,
[this](QAbstractSocket::SocketError error) {
qDebug() << "WebSocket 错误:" << socket->errorString();
emit error(socket->errorString());
});

// Pong 响应
connect(socket, &QWebSocket::pong, this,
[](quint64 elapsedTime, const QByteArray &payload) {
qDebug() << "Pong 响应时间:" << elapsedTime << "ms";
});
}

~WebSocketClient()
{
socket->close();
socket->deleteLater();
}

void connectToServer(const QUrl &url)
{
qDebug() << "连接到:" << url.toString();
socket->open(url);
}

// 使用子协议连接
void connectWithSubprotocol(const QUrl &url, const QStringList &protocols)
{
QWebSocketHandshakeOptions options;
options.setSubprotocols(protocols);
socket->open(url, options);
}

void sendText(const QString &message)
{
if (socket->state() == QAbstractSocket::ConnectedState) {
qint64 bytes = socket->sendTextMessage(message);
qDebug() << "发送" << bytes << "bytes";
}
}

void sendBinary(const QByteArray &data)
{
if (socket->state() == QAbstractSocket::ConnectedState) {
qint64 bytes = socket->sendBinaryMessage(data);
qDebug() << "发送二进制数据" << bytes << "bytes";
}
}

void ping()
{
socket->ping();
}

void close()
{
socket->close(QWebSocketProtocol::CloseCodeNormal, "用户关闭");
}

signals:
void connected();
void disconnected();
void messageReceived(const QString &message);
void binaryReceived(const QByteArray &data);
void error(const QString &errorString);

private:
QWebSocket *socket;
};

// 使用示例
WebSocketClient client;
client.connectToServer(QUrl("wss://example.com/ws"));

connect(&client, &WebSocketClient::connected, [&]() {
client.sendText("Hello, WebSocket!");
});

connect(&client, &WebSocketClient::messageReceived,
[](const QString &msg) {
qDebug() << "收到:" << msg;
});

WebSocket 服务器

#include <QWebSocketServer>
#include <QWebSocket>

class WebSocketServer : public QObject
{
Q_OBJECT

public:
WebSocketServer(quint16 port, QObject *parent = nullptr) : QObject(parent)
{
server = new QWebSocketServer("MyServer",
QWebSocketServer::NonSecureMode,
this);

if (server->listen(QHostAddress::Any, port)) {
qDebug() << "WebSocket 服务器启动在端口:" << port;

connect(server, &QWebSocketServer::newConnection, this,
&WebSocketServer::onNewConnection);
} else {
qDebug() << "启动失败:" << server->errorString();
}
}

~WebSocketServer()
{
server->close();
qDeleteAll(clients);
}

private slots:
void onNewConnection()
{
QWebSocket *client = server->nextPendingConnection();

QString clientInfo = QString("%1:%2")
.arg(client->peerAddress().toString())
.arg(client->peerPort());
qDebug() << "新客户端连接:" << clientInfo;

// 处理文本消息
connect(client, &QWebSocket::textMessageReceived, this,
[this, client](const QString &message) {
qDebug() << "收到消息:" << message;

// 回显消息
client->sendTextMessage("Echo: " + message);

// 广播给所有客户端
broadcast(message, client);
});

// 处理二进制消息
connect(client, &QWebSocket::binaryMessageReceived, this,
[this, client](const QByteArray &data) {
qDebug() << "收到二进制数据:" << data.size() << "bytes";
client->sendBinaryMessage(data);
});

// 客户端断开
connect(client, &QWebSocket::disconnected, this, [this, client, clientInfo]() {
qDebug() << "客户端断开:" << clientInfo;
clients.removeAll(client);
client->deleteLater();
});

clients << client;
}

public:
void broadcast(const QString &message, QWebSocket *sender = nullptr)
{
for (QWebSocket *client : clients) {
if (client != sender && client->isValid()) {
client->sendTextMessage(message);
}
}
}

void sendToClient(QWebSocket *client, const QString &message)
{
if (client && client->isValid()) {
client->sendTextMessage(message);
}
}

QList<QWebSocket*> getClients() const { return clients; }

private:
QWebSocketServer *server;
QList<QWebSocket*> clients;
};

// 安全 WebSocket 服务器(WSS)
class SecureWebSocketServer : public QObject
{
public:
SecureWebSocketServer(quint16 port, QObject *parent = nullptr) : QObject(parent)
{
server = new QWebSocketServer("SecureServer",
QWebSocketServer::SecureMode,
this);

// 加载证书和私钥
QFile certFile("server.crt");
QFile keyFile("server.key");

if (certFile.open(QIODevice::ReadOnly) &&
keyFile.open(QIODevice::ReadOnly)) {
QSslCertificate cert(&certFile);
QSslKey key(&keyFile, QSsl::Rsa);

QSslConfiguration config;
config.setLocalCertificate(cert);
config.setPrivateKey(key);
server->setSslConfiguration(config);

if (server->listen(QHostAddress::Any, port)) {
qDebug() << "安全 WebSocket 服务器启动在端口:" << port;
}
}
}

private:
QWebSocketServer *server;
};

TCP 编程

TCP 是可靠的面向连接的协议,适用于需要可靠传输的场景。

TCP 客户端

#include <QTcpSocket>

class TcpClient : public QObject
{
Q_OBJECT

public:
TcpClient(QObject *parent = nullptr) : QObject(parent)
{
socket = new QTcpSocket(this);

// 连接成功
connect(socket, &QTcpSocket::connected, this, [this]() {
qDebug() << "已连接到服务器";
emit connected();
});

// 断开连接
connect(socket, &QTcpSocket::disconnected, this, [this]() {
qDebug() << "与服务器断开连接";
emit disconnected();
});

// 收到数据
connect(socket, &QTcpSocket::readyRead, this, [this]() {
QByteArray data = socket->readAll();
qDebug() << "收到数据:" << data;
emit dataReceived(data);
});

// 错误处理
connect(socket, &QTcpSocket::errorOccurred, this,
[this](QAbstractSocket::SocketError error) {
qDebug() << "错误:" << socket->errorString();
emit error(socket->errorString());
});
}

void connectToHost(const QString &host, quint16 port)
{
socket->connectToHost(host, port);
}

void connectToHostEncrypted(const QString &host, quint16 port)
{
socket->connectToHostEncrypted(host, port);
}

void send(const QByteArray &data)
{
if (socket->state() == QAbstractSocket::ConnectedState) {
socket->write(data);
socket->flush();
}
}

void disconnect()
{
socket->disconnectFromHost();
}

signals:
void connected();
void disconnected();
void dataReceived(const QByteArray &data);
void error(const QString &errorString);

private:
QTcpSocket *socket;
};

TCP 服务器

#include <QTcpServer>
#include <QTcpSocket>

class TcpServer : public QObject
{
Q_OBJECT

public:
TcpServer(QObject *parent = nullptr) : QObject(parent)
{
server = new QTcpServer(this);

connect(server, &QTcpServer::newConnection, this,
&TcpServer::onNewConnection);
}

bool start(quint16 port)
{
if (server->listen(QHostAddress::Any, port)) {
qDebug() << "TCP 服务器启动在端口:" << port;
return true;
}
qDebug() << "启动失败:" << server->errorString();
return false;
}

private slots:
void onNewConnection()
{
QTcpSocket *client = server->nextPendingConnection();

QString clientInfo = QString("%1:%2")
.arg(client->peerAddress().toString())
.arg(client->peerPort());
qDebug() << "新客户端:" << clientInfo;

connect(client, &QTcpSocket::readyRead, this, [this, client]() {
QByteArray data = client->readAll();
qDebug() << "收到:" << data;

// 回显
client->write(data);

// 广播
broadcast(data, client);
});

connect(client, &QTcpSocket::disconnected, this, [this, client, clientInfo]() {
qDebug() << "客户端断开:" << clientInfo;
clients.removeAll(client);
client->deleteLater();
});

clients << client;
}

public:
void broadcast(const QByteArray &data, QTcpSocket *except = nullptr)
{
for (QTcpSocket *client : clients) {
if (client != except &&
client->state() == QAbstractSocket::ConnectedState) {
client->write(data);
}
}
}

private:
QTcpServer *server;
QList<QTcpSocket*> clients;
};

UDP 编程

UDP 是无连接的协议,适用于实时性要求高、可以容忍少量丢包的场景,如视频流、游戏等。

#include <QUdpSocket>

class UdpClient : public QObject
{
Q_OBJECT

public:
UdpClient(QObject *parent = nullptr) : QObject(parent)
{
socket = new QUdpSocket(this);

// 绑定端口接收数据
socket->bind(QHostAddress::Any, 12345);

connect(socket, &QUdpSocket::readyRead, this, [this]() {
while (socket->hasPendingDatagrams()) {
QByteArray datagram;
datagram.resize(socket->pendingDatagramSize());

QHostAddress sender;
quint16 senderPort;

socket->readDatagram(datagram.data(), datagram.size(),
&sender, &senderPort);

qDebug() << "收到来自" << sender.toString() << ":" << senderPort
<< "的数据:" << datagram;

emit datagramReceived(datagram, sender, senderPort);
}
});
}

// 发送数据报到指定地址
void send(const QByteArray &data, const QString &host, quint16 port)
{
socket->writeDatagram(data, QHostAddress(host), port);
}

// 广播
void broadcast(const QByteArray &data, quint16 port)
{
socket->writeDatagram(data, QHostAddress::Broadcast, port);
}

// 组播
void joinMulticastGroup(const QString &groupAddress)
{
socket->joinMulticastGroup(QHostAddress(groupAddress));
}

signals:
void datagramReceived(const QByteArray &data, const QHostAddress &sender,
quint16 senderPort);

private:
QUdpSocket *socket;
};

最佳实践

线程安全

QNetworkAccessManager 是基于 QObject 的,只能在创建它的线程中使用。如果在后台线程中需要发送网络请求,应该在该线程中创建独立的 QNetworkAccessManager 实例。

// 在工作线程中使用网络
class NetworkWorker : public QObject
{
Q_OBJECT

public:
NetworkWorker() : manager(new QNetworkAccessManager(this)) {}

public slots:
void fetchData(const QString &url)
{
QNetworkReply *reply = manager->get(QNetworkRequest(QUrl(url)));
connect(reply, &QNetworkReply::finished, this, [reply, this]() {
QByteArray data = reply->readAll();
emit dataFetched(data);
reply->deleteLater();
});
}

signals:
void dataFetched(const QByteArray &data);

private:
QNetworkAccessManager *manager;
};

错误处理

完整的错误处理应该包含网络错误、HTTP 状态码和解析错误:

void handleResponse(QNetworkReply *reply)
{
// 检查网络错误
if (reply->error() != QNetworkReply::NoError) {
QString errorMsg = reply->errorString();
QNetworkReply::NetworkError errorCode = reply->error();

// 根据错误类型进行不同处理
switch (errorCode) {
case QNetworkReply::ConnectionRefusedError:
qDebug() << "连接被拒绝";
break;
case QNetworkReply::HostNotFoundError:
qDebug() << "主机不存在";
break;
case QNetworkReply::TimeoutError:
qDebug() << "请求超时";
break;
case QNetworkReply::SslHandshakeFailedError:
qDebug() << "SSL 握手失败";
break;
default:
qDebug() << "网络错误:" << errorMsg;
}
reply->deleteLater();
return;
}

// 检查 HTTP 状态码
int statusCode = reply->attribute(
QNetworkRequest::HttpStatusCodeAttribute).toInt();

if (statusCode >= 200 && statusCode < 300) {
// 成功
QByteArray data = reply->readAll();
processSuccess(data);
} else if (statusCode >= 400) {
// HTTP 错误
handleHttpError(statusCode);
}

reply->deleteLater();
}

资源管理

// 使用 QPointer 安全地跟踪 QNetworkReply
QPointer<QNetworkReply> reply = manager->get(request);

connect(reply, &QNetworkReply::finished, this, [reply]() {
if (reply) { // 检查是否已被删除
// 处理响应
reply->deleteLater();
}
});

// 或使用自动删除(Qt 5.15+)
manager->setAutoDeleteReplies(true);

完整示例:HTTP 请求封装

#include <QNetworkAccessManager>
#include <QNetworkReply>
#include <QNetworkRequest>
#include <QJsonDocument>
#include <QJsonObject>

class HttpRequest : public QObject
{
Q_OBJECT

public:
enum class Method { Get, Post, Put, Delete };

explicit HttpRequest(QObject *parent = nullptr) : QObject(parent)
{
manager = new QNetworkAccessManager(this);

// 设置默认超时
manager->setTransferTimeout(30000);

// 设置默认重定向策略
manager->setRedirectPolicy(QNetworkRequest::NoLessSafeRedirectPolicy);
}

// GET 请求
void get(const QString &url, const QMap<QString, QString> &headers = {})
{
QNetworkRequest request(QUrl(url));
setHeaders(request, headers);
sendRequest(request, Method::Get);
}

// POST JSON
void post(const QString &url, const QJsonObject &data,
const QMap<QString, QString> &headers = {})
{
QNetworkRequest request(QUrl(url));
request.setHeader(QNetworkRequest::ContentTypeHeader, "application/json");
setHeaders(request, headers);

QJsonDocument doc(data);
sendRequest(request, Method::Post, doc.toJson());
}

// POST 表单
void postForm(const QString &url, const QMap<QString, QString> &params,
const QMap<QString, QString> &headers = {})
{
QNetworkRequest request(QUrl(url));
request.setHeader(QNetworkRequest::ContentTypeHeader,
"application/x-www-form-urlencoded");
setHeaders(request, headers);

QUrlQuery query;
for (auto it = params.begin(); it != params.end(); ++it) {
query.addQueryItem(it.key(), it.value());
}

sendRequest(request, Method::Post,
query.toString(QUrl::FullyEncoded).toUtf8());
}

// PUT 请求
void put(const QString &url, const QJsonObject &data,
const QMap<QString, QString> &headers = {})
{
QNetworkRequest request(QUrl(url));
request.setHeader(QNetworkRequest::ContentTypeHeader, "application/json");
setHeaders(request, headers);

QJsonDocument doc(data);
sendRequest(request, Method::Put, doc.toJson());
}

// DELETE 请求
void deleteResource(const QString &url,
const QMap<QString, QString> &headers = {})
{
QNetworkRequest request(QUrl(url));
setHeaders(request, headers);
sendRequest(request, Method::Delete);
}

// 设置认证
void setAuthentication(const QString &username, const QString &password)
{
authUsername = username;
authPassword = password;
}

// 设置 Bearer Token
void setBearerToken(const QString &token)
{
bearerToken = token;
}

signals:
void success(const QJsonObject &data);
void successRaw(const QByteArray &data);
void error(int statusCode, const QString &message);
void networkError(const QString &message);

private:
void setHeaders(QNetworkRequest &request, const QMap<QString, QString> &headers)
{
// 设置默认头
request.setRawHeader("User-Agent", "MyApp/1.0");
request.setRawHeader("Accept", "application/json");

// 设置认证
if (!bearerToken.isEmpty()) {
request.setRawHeader("Authorization",
"Bearer " + bearerToken.toUtf8());
} else if (!authUsername.isEmpty()) {
request.setRawHeader("Authorization",
"Basic " + QByteArray(authUsername + ":" + authPassword).toBase64());
}

// 设置自定义头
for (auto it = headers.begin(); it != headers.end(); ++it) {
request.setRawHeader(it.key().toUtf8(), it.value().toUtf8());
}
}

void sendRequest(const QNetworkRequest &request, Method method,
const QByteArray &data = QByteArray())
{
QNetworkReply *reply = nullptr;

switch (method) {
case Method::Get:
reply = manager->get(request);
break;
case Method::Post:
reply = manager->post(request, data);
break;
case Method::Put:
reply = manager->put(request, data);
break;
case Method::Delete:
reply = manager->deleteResource(request);
break;
}

connect(reply, &QNetworkReply::finished, this,
[this, reply]() { handleReply(reply); });
}

void handleReply(QNetworkReply *reply)
{
QScopedPointer<QNetworkReply, QScopedPointerDeleteLater> replyGuard(reply);

// 网络错误
if (reply->error() != QNetworkReply::NoError) {
emit networkError(reply->errorString());
return;
}

// 获取状态码
int statusCode = reply->attribute(
QNetworkRequest::HttpStatusCodeAttribute).toInt();

// 读取响应
QByteArray responseData = reply->readAll();

// 尝试解析 JSON
QJsonParseError parseError;
QJsonDocument doc = QJsonDocument::fromJson(responseData, &parseError);

if (parseError.error == QJsonParseError::NoError && doc.isObject()) {
if (statusCode >= 200 && statusCode < 300) {
emit success(doc.object());
} else {
emit error(statusCode, doc.object().value("message").toString());
}
} else {
if (statusCode >= 200 && statusCode < 300) {
emit successRaw(responseData);
} else {
emit error(statusCode, QString::fromUtf8(responseData));
}
}
}

QNetworkAccessManager *manager;
QString authUsername;
QString authPassword;
QString bearerToken;
};

// 使用示例
HttpRequest http;

// 设置认证
http.setBearerToken("your-access-token");

// GET 请求
http.get("https://api.example.com/users");

connect(&http, &HttpRequest::success, [](const QJsonObject &data) {
qDebug() << "成功:" << data;
});

connect(&http, &HttpRequest::error, [](int code, const QString &msg) {
qDebug() << "HTTP 错误" << code << ":" << msg;
});

// POST 请求
QJsonObject newUser;
newUser["name"] = "张三";
newUser["email"] = "[email protected]";
http.post("https://api.example.com/users", newUser);

小结

本章介绍了 Qt 网络编程的主要内容:

  1. QNetworkAccessManager:高层 HTTP 客户端,支持 GET、POST、PUT、DELETE 等请求
  2. WebSocket:全双工实时通信协议
  3. TCP 编程:可靠的面向连接协议
  4. UDP 编程:无连接的数据报协议
  5. SSL/TLS:安全的网络通信
  6. 最佳实践:线程安全、错误处理、资源管理

选择合适的网络 API 取决于具体需求:HTTP 请求使用 QNetworkAccessManager,实时通信使用 WebSocket,自定义协议使用 TCP/UDP。

练习

  1. 实现一个简单的 RESTful API 客户端,支持 CRUD 操作
  2. 创建一个 WebSocket 聊天室应用
  3. 实现一个支持断点续传的文件下载器
  4. 创建一个简单的 HTTP 服务器,处理静态文件请求
  5. 实现一个网络状态检测工具,检测连接是否可用

参考资源