网络与多线程
网络编程
UDP编程
主要的类有两个:
- Qudpsocket
- QNetworkDatagram
Qudpsocket
Qudpsocket 表示一个 UDP 的 socket
| 名称 | 类型 | 说明 | 对标原生 API |
|---|---|---|---|
| bind(const QHostAddress&, quint16) | 方法 | 绑定指定的端口号 | bind |
| receiveDatagram() | 方法 | 返回 QNetworkDatagram,读取一个 UDP 数据报 | recvfrom |
| writeDatagram(const QNetworkDatagram&) | 方法 | 发送一个 UDP 数据报 | sendto |
| readyRead | 信号 | 在收到数据并准备就绪后触发 | 无 (类似于 IO 多路复用的通知机制) |
QNetworkDatagram
QNetworkDatagram 表示一个 UDP 数据报
| 名称 | 类型 | 说明 | 对标原生 API |
|---|---|---|---|
| QNetworkDatagram(const QByteArray&, const QHostAddress&, quint16) | 构造函数 | 通过 QByteArray、目标 IP 地址、目标端口号构造一个 UDP 数据报(通常用于发送数据) | 无 |
| data() | 方法 | 获取数据报内部特有的数据(返回 QByteArray) | 无 |
| senderAddress() | 方法 | 获取数据报中包含的对端 IP 地址 | 无(recvfrom 包含该功能) |
| senderPort() | 方法 | 获取数据报中包含的对端端口号 | 无(recvfrom 包含该功能) |
TCP编程
核心类有两个:
- QTcpServer
- QTcpSocket
QTcpServer
QTcpServer 用于监听端口和获取客户端连接
| 名称 | 类型 | 说明 | 对标原生 API |
|---|---|---|---|
| listen(const QHostAddress&, quint16 port) | 方法 | 绑定指定的地址和端口号,并开始监听 | bind 和 listen |
| nextPendingConnection() | 方法 | 获取已建立的 TCP 连接(返回 QTcpSocket 对象,用于与客户端通信) | accept |
| newConnection | 信号 | 当新的客户端建立连接后触发 | 无(类似于 IO 多路复用中的通知机制) |
QTcpSocket
QTcpSocket 用于客户端和服务器之间的数据交互
| 名称 | 类型 | 说明 | 对标原生 API |
|---|---|---|---|
| readAll() | 方法 | 读取当前接收缓冲区中的所有数据 | read |
| write(const QByteArray&) | 方法 | 将数据写入 socket | write |
| deleteLater | 方法 | 标记 socket 对象为无效(Qt 在下个事件循环中释放该对象) | 无(类似于半自动化的垃圾回收) |
| readyRead | 信号 | 当数据到达并准备就绪时触发 | 无(类似于 IO 多路复用中的通知机制) |
| disconnected | 信号 | 当连接断开时触发 | 无(类似于 IO 多路复用中的通知机制) |
HTTP Client
进行 Qt 开发时,与服务器的通信常使用 HTTP 协议:
- 通过 HTTP 从服务器获取数据
- 通过 HTTP 向服务器提交数据
核心类是三个:
- QNetworkAccessManager
- QNetworkRequest
- QNetworkReply
QNetworkAccessManager
| 方法 | 说明 |
|---|---|
| get(const QNetworkRequest&) | 发起 HTTP GET 请求(返回 QNetworkReply 对象) |
| post(const QNetworkRequest&, const QByteArray&) | 发起 HTTP POST 请求(返回 QNetworkReply 对象) |
QNetworkRequest
| 方法 | 说明 |
|---|---|
| QNetworkRequest(const QUrl&) | 通过 URL 构造 HTTP 请求 |
| setHeader(QNetworkRequest::KnownHeaders header, const QVariant &value) | 设置请求头 |
请求头取值
| 取值 | 说明 |
|---|---|
| ContentTypeHeader | 描述 body 的类型 |
| ContentLengthHeader | 描述 body 的长度 |
| LocationHeader | 用于重定向报文中指定重定向地址(响应中使用) |
| CookieHeader | 设置 cookie |
| UserAgentHeader | 设置 User-Agent |
QNetworkReply
QNetworkReply 表示一个 HTTP 响应(继承自 qroDevice)
| 方法 | 说明 |
|---|---|
| error() | 获取出错状态 |
| errorString() | 获取出错原因的文本描述 |
| readAll() | 读取响应 body |
| header(QNetworkRequest::KnownHeaders header) | 读取指定响应头的值 |
重要信号:
- finished():当客户端收到完整的响应数据后触发
多线程编程
QThread
QThread 是 Qt 框架中处理多线程编程的类(父类为 QObject),继承了信号槽机制、对象树管理等特性。
QThread 的特点
线程创建和管理
- 简单的线程创建:继承 QThread 并重写
run()函数(线程入口点) - 生命周期管理:
start():启动线程quit():优雅地请求线程退出terminate():强制终止线程(可能导致资源泄漏,尽量避免使用)
- 简单的线程创建:继承 QThread 并重写
信号和槽机制在线程中的应用
- 线程间通信:通过信号槽实现跨线程通信(例如任务完成时通知主线程)
- 异步操作:在后台线程执行耗时任务,通过信号返回结果
资源管理
- 对象树机制:QThread 参与 Qt 对象树管理(父对象销毁时自动清理)
- 线程局部存储:通过
threadLocalStorage()安全访问线程局部变量
可重入性和线程安全性
- 可重入函数:QThread 的非静态成员函数可被多线程同时调用(不访问共享数据时)
- 线程安全的操作:内部同步处理确保启动/停止线程的正确性
常用 API
| 方法 | 说明 |
|---|---|
| run() | 线程入口函数(需重写) |
| start() | 调用 run() 启动线程(若线程已在运行则无效) |
| currentThread() | 返回管理当前线程的 QThread 指针 |
| isRunning() | 线程运行时返回 true |
| sleep()/msleep()/usleep() | 使线程休眠(秒/毫秒/微秒) |
| wait() | 阻塞线程直至:1. 线程执行完成 2. 超时(默认 ULONG_MAX 永不超时) |
| terminate() | 强制终止线程(需谨慎使用) |
| finished() | 信号:线程结束时触发(用于清理工作) |
使用步骤
- 自定义类继承 QThread
- 重写
run()函数(包含需执行的复杂逻辑) - 启动线程:调用
start()(非直接调用run()) - 通过信号通知主线程任务完成
- 关闭线程
多线程使用事项
- 禁止在线程函数中操作 UI:
- 原因1(线程安全性):UI 操作应由主线程管理(GUI 框架非线程安全)
- 原因2(事件循环机制):跨线程操作 UI 会干扰主线程事件循环(导致界面异常)
connect()的第五参数Qt::ConnectionType:连接类型 说明 Qt::AutoConnection 自动选择(同线程用 Direct,跨线程用 Queued) Qt::DirectConnection 信号发出后槽函数立即在同一线程执行(需确保线程安全) Qt::QueuedConnection 槽函数插入接收者线程事件队列(跨线程安全) Qt::BlockingQueuedConnection 发送线程阻塞直至槽函数执行完毕(注意死锁风险) Qt::UniqueConnection 标志位(可与其他类型组合,确保唯一连接)
线程安全
常用同步类:
- 互斥锁:QMutex、QMutexLocker
- 条件变量:QWaitCondition
- 信号量:QSemaphore
- 读写锁:QReadLocker、QWriteLocker、QReadWriteLock
互斥锁
QMutex
cpp
QMutex mutex;
mutex.lock(); // 上锁
// 访问共享资源
mutex.unlock(); // 解锁QMutexLocker(RAII 方式管理锁)
cpp
QMutex mutex;
{
QMutexLocker locker(&mutex); // 自动上锁
// 访问共享资源
} // 作用域结束自动解锁读写锁
cpp
QReadWriteLock rwLock;
// 读操作
{
QReadLocker locker(&rwLock); // 上读锁(允许多线程读)
// 读取共享资源
}
// 写操作
{
QWriteLocker locker(&rwLock); // 上写锁(独占访问)
// 修改共享资源
}条件变量
QWaitCondition
cpp
QMutex mutex;
QWaitCondition condition;
// 等待线程
mutex.lock();
while (!conditionFullfilled()) {
condition.wait(&mutex); // 释放锁并等待
}
// 条件满足后继续执行
mutex.unlock();
// 通知线程
mutex.lock();
changeCondition();
condition.wakeAll(); // 唤醒所有等待线程
mutex.unlock();信号量
QSemaphore
cpp
QSemaphore semaphore(2); // 允许2个线程并发访问
semaphore.acquire(); // 获取信号量(资源不足时阻塞)
// 访问共享资源
semaphore.release(); // 释放信号量作业
完成聊天软件的网络部分
