Qt5网络编程详细讲解
个人博客:blogs.wurp.top
一、 Qt 网络模块概述
-
核心特性:
- 跨平台:在 Windows、Linux、macOS 等系统上提供一致的 API。
- 事件驱动:与 Qt 的信号槽机制无缝集成,采用异步非阻塞 I/O,不会阻塞主线程(GUI线程)。
- 协议支持:提供了对 TCP、UDP、HTTP、HTTPS、FTP 等协议的高级封装。
- 抽象层次:既提供了低级的套接字类(如
QTcpSocket
),也提供了高级的网络请求类(如QNetworkAccessManager
)。
-
在项目中使用网络模块:
在你的项目文件(.pro
)中,需要添加一行来引入网络模块:QT += network
在源代码中,包含必要的头文件,例如:
#include <QTcpSocket> #include <QTcpServer> #include <QUdpSocket> #include <QNetworkAccessManager> #include <QNetworkReply>
二、 核心类详解
我们将从底层到高层依次讲解最重要的几个类。
1. TCP 编程
TCP 是一种面向连接、可靠的、基于字节流的传输层通信协议。
-
QTcpServer
:用于监听和接受传入的 TCP 连接。- 核心方法:
listen(const QHostAddress &address = QHostAddress::Any, quint16 port = 0)
:开始在指定地址和端口监听。成功返回true
。bool isListening()
:判断是否正在监听。void close()
:停止服务器监听。
- 重要信号:
void newConnection()
:当有新的客户端连接时发射此信号。
- 核心方法:
-
QTcpSocket
:用于建立 TCP 连接,进行数据的发送和接收。它继承自QAbstractSocket
,再继承自QIODevice
,因此可以像操作文件一样读写数据。- 核心方法:
connectToHost(const QString &hostName, quint16 port, OpenMode mode = ReadWrite)
:异步连接到主机。qint64 write(const QByteArray &data)
:发送数据。QByteArray readAll()
/qint64 read(char *data, qint64 maxSize)
:读取所有/指定大小的数据。qint64 bytesAvailable()
:返回可读的字节数。void disconnectFromHost()
:断开连接。
- 重要信号:
void connected()
:成功连接到服务器后发射。void disconnected()
:连接断开时发射。void readyRead()
:当有新的数据可供读取时发射。这是处理接收数据最常用的信号。void error(QAbstractSocket::SocketError socketError)
:发生错误时发射。void stateChanged(QAbstractSocket::SocketState socketState)
:状态改变时发射。
- 核心方法:
TCP 服务器编程流程:
- 创建
QTcpServer
对象。 - 调用
listen()
开始监听。 - 连接
newConnection()
信号到一个槽函数。 - 在槽函数中,调用
nextPendingConnection()
获取连接的QTcpSocket
。 - 保存或使用这个
QTcpSocket
对象,连接其readyRead()
,disconnected()
等信号到对应的槽函数进行数据读写和连接管理。 - 在
readyRead()
的槽函数中读取数据,使用write()
发送数据。
TCP 客户端编程流程:
- 创建
QTcpSocket
对象。 - (可选)连接
connected()
,readyRead()
,disconnected()
,error()
等信号到槽函数。 - 调用
connectToHost()
连接服务器。 - 连接成功后,使用
write()
发送数据。 - 在
readyRead()
的槽函数中处理服务器返回的数据。
示例:一个简单的 TCP 客户端连接和发送
// 在类头文件中声明
QTcpSocket *tcpSocket;// 在构造函数中初始化
tcpSocket = new QTcpSocket(this);
connect(tcpSocket, &QTcpSocket::connected, this, &MyClass::onConnected);
connect(tcpSocket, &QTcpSocket::readyRead, this, &MyClass::onReadyRead);
connect(tcpSocket, &QTcpSocket::errorOccurred, this, &MyClass::onError);// 连接服务器
tcpSocket->connectToHost("127.0.0.1", 8888);// 槽函数
void MyClass::onConnected() {qDebug() << "Connected to server!";tcpSocket->write("Hello, Server!");
}void MyClass::onReadyRead() {QByteArray data = tcpSocket->readAll();qDebug() << "Received from server:" << data;
}void MyClass::onError(QAbstractSocket::SocketError error) {qDebug() << "Error occurred:" << tcpSocket->errorString();
}
2. UDP 编程
UDP 是一种无连接、不可靠的报文传输协议。
QUdpSocket
:用于发送和接收 UDP 数据报。- 核心方法:
bool bind(quint16 port, ...)
:绑定到一个本地端口来接收数据报。qint64 writeDatagram(const QByteArray &datagram, const QHostAddress &host, quint16 port)
:向指定地址和端口发送数据报。qint64 readDatagram(char *data, qint64 maxSize, QHostAddress *address = nullptr, quint16 *port = nullptr)
:读取一个数据报及其发送者的信息。bool hasPendingDatagrams()
:判断是否有待处理的数据报。qint64 pendingDatagramSize()
:返回下一个待处理数据报的大小。
- 核心方法:
UDP 编程流程:
- 创建
QUdpSocket
对象。 - 调用
bind()
绑定一个端口(如果只想发送,可以不绑定;如果想接收,必须绑定)。 - 连接
readyRead()
信号到槽函数,处理接收到的数据报。 - 使用
writeDatagram()
发送数据。 - 在
readyRead()
的槽函数中,使用readDatagram()
或循环hasPendingDatagrams()
来读取所有数据报。
示例:接收 UDP 数据报
QUdpSocket *udpSocket = new QUdpSocket(this);
// 绑定到 7755 端口接收所有网卡的数据
if (udpSocket->bind(7755)) {connect(udpSocket, &QUdpSocket::readyRead, this, &MyClass::readPendingDatagrams);
}void MyClass::readPendingDatagrams() {while (udpSocket->hasPendingDatagrams()) {QByteArray datagram;datagram.resize(udpSocket->pendingDatagramSize());QHostAddress sender;quint16 senderPort;udpSocket->readDatagram(datagram.data(), datagram.size(), &sender, &senderPort);qDebug() << "Received from" << sender.toString() << ":" << senderPort<< "Data:" << datagram;}
}// 发送 UDP 数据报
QByteArray data = "Hello UDP!";
udpSocket->writeDatagram(data, QHostAddress("192.168.1.100"), 7755);
3. HTTP 编程 (高级 API)
Qt 使用 QNetworkAccessManager
(NAM) 来处理 HTTP、HTTPS 等协议请求,它是异步且高度封装的。
-
QNetworkAccessManager
:协调网络操作的中心类。它本身不处理数据,而是创建QNetworkReply
对象来代表一个网络请求。- 核心方法:
QNetworkReply *get(const QNetworkRequest &request)
:发起 GET 请求。QNetworkReply *post(const QNetworkRequest &request, const QByteArray &data)
:发起 POST 请求,可提交数据。QNetworkReply *put(const QNetworkRequest &request, const QByteArray &data)
:发起 PUT 请求。QNetworkReply *deleteResource(const QNetworkRequest &request)
:发起 DELETE 请求。
- 重要信号:
void finished(QNetworkReply *reply)
:当一个网络请求完成时发射(无论成功失败)。
- 核心方法:
-
QNetworkRequest
:封装一个网络请求的信息,如 URL、头信息(Header)、属性等。- 常用设置:
setUrl(const QUrl &url)
,setHeader(QNetworkRequest::KnownHeaders header, const QVariant &value)
。
- 常用设置:
-
QNetworkReply
:代表一个正在进行的或已完成的网络请求。它继承自QIODevice
,所以数据也是异步读取的。- 核心方法/属性:
QByteArray readAll()
/read()
:读取服务器返回的数据。QUrl url()
:返回请求的 URL。QVariant attribute(QNetworkRequest::Attribute code)
:获取请求的属性(如状态码)。QNetworkReply::NetworkError error()
:返回错误类型。QString errorString()
:返回错误描述。
- 重要信号:
void readyRead()
:有数据可读时发射(适合流式数据)。void downloadProgress(qint64 bytesReceived, qint64 bytesTotal)
:下载进度变化时发射。void uploadProgress(qint64 bytesSent, qint64 bytesTotal)
:上传进度变化时发射。void finished()
:请求完成时发射(与QNetworkAccessManager::finished
同步)。
- 核心方法/属性:
HTTP 编程流程 (GET 示例):
- 创建一个
QNetworkAccessManager
对象(通常一个应用一个实例即可)。 - 构建一个
QNetworkRequest
,设置其 URL 和头信息。 - 调用
QNetworkAccessManager::get()
等方法,发起请求,并获取返回的QNetworkReply
对象指针。 - 将
QNetworkReply
的finished()
,readyRead()
,errorOccurred()
等信号连接到槽函数。 - 在
finished()
的槽函数中,检查回复是否有错误,然后读取数据。注意:QNetworkReply
对象需要在槽函数中自行删除,调用deleteLater()
是安全的做法。
示例:发起一个简单的 GET 请求
// 在类头文件中声明
QNetworkAccessManager *manager;// 在构造函数中初始化
manager = new QNetworkAccessManager(this);
connect(manager, &QNetworkAccessManager::finished, this, &MyClass::onReplyFinished);// 发起请求
QUrl url("https://XXX.com/data");
QNetworkRequest request(url);
// 可以设置头信息,例如 User-Agent
request.setHeader(QNetworkRequest::UserAgentHeader, "MyQtApp/1.0");
QNetworkReply *reply = manager->get(request);// 可选的:连接reply本身的信号来跟踪进度
connect(reply, &QNetworkReply::downloadProgress, this, &MyClass::onDownloadProgress);// 请求完成的槽函数
void MyClass::onReplyFinished(QNetworkReply *reply) {if (reply->error() == QNetworkReply::NoError) {QByteArray response_data = reply->readAll();// 处理 response_data,可能是 JSON、XML 或 HTMLqDebug() << "Response:" << response_data;} else {qDebug() << "Error:" << reply->errorString();}// 清理 reply 对象reply->deleteLater();
}
POST 请求示例(提交 JSON 数据):
QNetworkRequest request(QUrl("https://XXX.com/submit"));
request.setHeader(QNetworkRequest::ContentTypeHeader, "application/json");QJsonObject jsonObject;
jsonObject["name"] = "Qt";
jsonObject["version"] = 5;
QJsonDocument doc(jsonObject);
QByteArray jsonData = doc.toJson();QNetworkReply *reply = manager->post(request, jsonData);
三、 高级主题与最佳实践
-
线程与网络:
QNetworkAccessManager
和套接字对象通常生活在创建它们的线程中,并且该线程必须运行事件循环。- 对于耗时操作(如处理大量接收到的数据),最好将数据处理工作移到工作线程(
QThread
)中,避免阻塞主线程(GUI线程)。 - 可以在工作线程中创建自己的
QNetworkAccessManager
,但需要确保该线程运行着事件循环。
-
数据编码:
- 网络传输的是字节流。
QString
需要转换为QByteArray
才能发送。 - 常用转换:
QString::toUtf8()
,QString::fromUtf8()
。确保收发双方使用相同的编码。
- 网络传输的是字节流。
-
处理粘包(TCP):
- TCP 是流协议,没有消息边界。
readyRead()
信号只表示有数据到达,不保证是一条完整的“消息”。 - 解决方案:设计应用层协议。常用方法:
- 定长协议:每条消息固定长度。
- 分隔符协议:用特殊字符(如
\n
)分隔消息。使用QIODevice::canReadLine()
和readLine()
。 - 定长头+变长体协议:消息由一个固定大小的头部(包含消息体长度)和变长的body组成。先读头部,解析出长度,再读取指定长度的body。
- TCP 是流协议,没有消息边界。
-
SSL/TLS 支持:
- Qt 提供了
QSslSocket
,它是QTcpSocket
的 SSL 加密版本,用法几乎完全相同。 - 需要在项目文件中添加
QT += network
(已包含)。 - 使用
QSslSocket::connectToHostEncrypted()
来建立安全连接。 - 可以处理证书验证相关的信号,如
sslErrors()
。
- Qt 提供了
-
代理支持:
- Qt 网络模块透明地支持系统代理设置。
- 也可以使用
QNetworkProxy
类来为单个套接字或全局(QNetworkProxy::setApplicationProxy
)设置自定义代理。
四、总结
需求/协议 | 推荐使用的Qt类 | 特点 |
---|---|---|
可靠流传输 (TCP) | QTcpServer , QTcpSocket | 面向连接,可靠,需要处理粘包 |
快速报文传输 (UDP) | QUdpSocket | 无连接,不可靠,效率高,需处理丢包和乱序 |
Web 请求 (HTTP/HTTPS) | QNetworkAccessManager , QNetworkRequest , QNetworkReply | 高级 API,异步,支持方法、头、Cookie、代理等 |
安全传输 (SSL/TLS) | QSslSocket (替代 QTcpSocket ) | 在 TCP 基础上提供加密和认证 |
Qt 的网络编程核心思想是 异步事件驱动。熟练掌握信号槽机制,并理解 readyRead()
, finished()
等关键信号的触发时机,是编写高效、健壮网络应用的关键。建议从简单的 TCP 回显服务器/客户端或 HTTP GET 请求例子开始实践,逐步深入到更复杂的应用。