使用Qt进行网络编程#
开发具有网络功能的程序
Qt网络模块提供类,允许您编写TCP/IP客户端和服务器。它提供了代表底层网络概念的类,如QTcpSocket
、QTcpServer
和 QUdpSocket
,以及用于使用常用协议执行网络操作的类,如QNetworkRequest
、QNetworkReply
和 QNetworkAccessManager
。
Qt的网络编程类#
Qt网络C++类的页面包含了Qt网络中的C++类的列表。
高级HTTP网络操作#
网络访问API是执行常见网络操作的一组类。API在特定操作和协议之上提供了一个抽象层(例如,通过HTTP获取和发布数据),并仅公开用于泛型或高级概念的类、函数和信号。
网络请求由QNetworkRequest
类表示,它还充当与请求相关信息的通用容器,如任何头部信息和使用的加密。当请求对象构造时指定的URL决定了请求使用的协议。目前支持HTTP和本地文件URL用于上传和下载。
网络操作的协调由QNetworkAccessManager
类执行。一旦创建请求,就使用此类来调度它并发出信号以报告其进度。管理器还协调使用cookies
在客户端存储数据、身份验证请求和使用代理。
网络请求的回复由QNetworkReply
类表示;这些在请求被分发时由QNetworkAccessManager
创建。可以通过QNetworkReply
提供的信号来分别监视每个回复,或者开发者可以选择使用管理器的信号来完成这一目的,并丢弃回复的引用。由于QNetworkReply
是QIODevice的子类,因此回复可以同步处理或异步处理;即,作为阻塞或非阻塞性操作。
每个应用程序或库可以创建一个或多个QNetworkAccessManager
的实例来处理网络通信。
使用QTcpSocket和QTcpServer通过TCP#
TCP(传输控制协议)是一种低级别网络协议,被包括HTTP和FTP在内的多数互联网协议用于数据传输。它是一种可靠、面向流、面向连接的传输协议。它特别适合于数据的连续传输。
QTcpSocket
类提供了TCP的接口。您可以使用QTcpSocket
来实现标准网络协议,如POP3、SMTP和NNTP,以及自定义协议。
在任何数据传输开始之前,必须先与远程主机和端口建立TCP连接。一旦建立连接,通过peerAddress()
和peerPort()
可以获取对方的IP地址和端口号。在任何时候,对方都可以关闭连接,这将立即停止数据传输。
QTcpSocket
以异步方式工作,并发出信号来报告状态变化和错误,就像QNetworkAccessManager
一样。它依赖于事件循环来检测传入数据并自动刷新传出数据。您可以使用QTcpSocket::write()向套接字写入数据,使用QTcpSocket::read()读取数据。QTcpSocket
代表两个独立的数据流:一个用于读取,一个用于写入。
因为 QTcpSocket
继承了 QIODevice,所以你可以使用 QTextStream 和 QDataStream 与它一起使用。在从 QTcpSocket
读取时,必须通过预先调用 bytesAvailable()
确保足够的数据可用。
如果你需要处理传入的 TCP 连接(例如,在服务器应用程序中),请使用 QTcpServer
类。调用 listen()
来设置服务器,并连接到 newConnection()
信号,该信号每次在客户端连接时都会发出一次。在你的槽中,调用 nextPendingConnection()
来接受连接,并使用返回的 QTcpSocket
与客户端通信。
尽管它的大部分功能都是异步的,但仍可以使用 QTcpSocket
同步(即阻塞)的方式。为了获得阻塞行为,调用 QTcpSocket
的 waitFor…() 函数;这些函数会挂起调用线程,直到发出信号。例如,调用非阻塞函数 connectToHost()
后,调用 waitForConnected()
以阻塞线程,直到发出 connected()
信号。
同步套接字通常会引导出控制流程更简单的代码。waitFor…() 方法的缺点是,在 waitFor…() 函数阻塞时,不会处理事件。如果在 GUI 线程中使用,可能会冻结应用程序的用户界面。因此,我们建议你只在非 GUI 线程中使用同步套接字。当以同步方式使用时,QTcpSocket
不需要事件循环。
《Fortune 客户端》和《Fortune 服务器》示例展示了如何使用 QTcpSocket
和 QTcpServer
编写 TCP 客户端-服务器应用程序。还可以参考《阻塞式 Fortune 客户端》了解如何在单独的线程中使用同步的 QTcpSocket
(不需要使用事件循环)的示例,以及《多线程 Fortune 服务器》了解每个活跃客户端拥有一个线程的多线程 TCP 服务器的示例。
使用 QUdpSocket 进行 UDP 通信#
UDP(用户数据报协议)是一种轻量级、不可靠、面向数据报、无连接的协议。当可靠性不是很重要时可以使用它。例如,一个报告每日时间的服务器可以选择 UDP。如果包含每日时间的数据报丢失,客户端可以简单地再次发送请求。
QUdpSocket
类允许您发送和接收 UDP 数据报。它继承了 QAbstractSocket
,因此共享了大部分 QTcpSocket
接口。主要区别在于,QUdpSocket
以数据报形式传输数据,而不是以连续的数据流传输。简而言之,数据报是有限大小的数据包(通常小于 512 字节),包含数据报发送者和接收者的 IP 地址和端口号,以及传输的数据。
QUdpSocket
支持 IPv4 广播。广播常用于实现网络发现协议,例如查找网络上哪个主机拥有最多的可用磁盘空间。一块主机向网络广播一个数据包,所有其他主机都会接收。接收请求的每个主机随后将包含其当前可用磁盘空间的回复发送给发送者。发起者等待接收到所有主机的回复,然后可以选择拥有最多可用空间的服务器来存储数据。要广播一个数据包,只需将其发送到特殊的地址 Broadcast
(255.255.255.255),或发送到您的本地网络的广播地址。
bind()
准备套接字接受传入的数据报,这类似于 TCP 服务器的 listen()
方法。每当接收到一个或多个数据报时,QUdpSocket
会发出 readyRead() 信号。调用 readDatagram()
读取数据报。
Broadcast Sender(广播发送器)和Broadcast Receiver(广播接收器)示例展示了如何使用 Qt 编写 UDP 发送器和 UDP 接收器。
QUdpSocket
也支持多播。Multicast Sender(多播发送器)和Multicast Receiver(多播接收器)示例展示了如何使用 UDP 多播客户端。
使用 QHostInfo 解析主机名
在建立网络连接之前,QTcpSocket
和 QUdpSocket
会执行名称查找,将连接到的主机名转换成 IP 地址。此操作通常使用 DNS(域名服务)协议执行。
QHostInfo
提供了一个静态函数,允许您自己执行此类查找。通过调用以主机名、QObject 指针和槽签名作为参数的 lookupHost()
,当结果就绪时,QHostInfo
将执行名称查找并调用给定的槽。实际的查找在一个单独的线程中执行,利用操作系统本身的名称查找方法。
QHostInfo
还提供了一个名为 fromName()
的静态函数,它以主机名作为参数并返回结果。在这种情况下,名称查找与调用者在同一个线程中执行。这种重载对于非 GUI 应用程序或在不同线程中进行名称查找非常有用。(在 GUI 线程中调用此函数可能会导致您的用户界面冻结,因为函数在执行查找过程中会阻塞。)
网络代理支持
使用 Qt 进行网络通信可以通过代理进行,这些代理会在本地和远程连接之间直接或过滤网络流量。
单个代理由 QNetworkProxy
类表示,该类用于描述和配置代理连接。支持在不同网络通信层面上操作的代理类型,包括 SOCKS 5 支持允许在网络通信的低层次上进行代理,而 HTTP 和 FTP 代理在协议级别上工作。有关更多信息,请参阅 ProxyType
。
代理可以根据每个套接字或整个应用程序的网络通信来启用。可以在套接字连接之前通过调用其 setProxy()
函数使其使用代理。可以通过使用 setApplicationProxy()
函数来为所有后续套接字连接启用应用程序级的代理。
代理工厂用于创建代理使用策略。由 QNetworkProxyFactory
提供基于特定代理类型的查询。这些查询编码在 QNetworkProxyQuery
对象中,这些对象允许根据关键标准选择代理,例如代理的目的(TCP、UDP、TCP服务器、URL请求)、本地端口、远程主机和端口,以及正在使用的协议(HTTP、FTP等)。
使用 proxyForQuery()
直接查询工厂。可以通过将工厂传递给 setApplicationProxyFactory()
来实现代理应用程序级策略,并通过继承 QNetworkProxyFactory
创建自定义的代理策略;请参阅类文档以获取详细信息。