使用Qt进行网络编程

编程具有网络功能的应用程序

Qt 网络模块提供了允许您编写 TCP/IP 客户端和服务器的类。它提供了低级别的类,如 QTcpSocketQTcpServerQUdpSocket,这些类代表了低级别的网络概念,以及高级别的类,如 QNetworkRequestQNetworkReplyQNetworkAccessManager,用于使用常见协议执行网络操作。

Qt的网络编程类

Qt Network C++ Classes 页面包含了 Qt Network 中的 C++ 类列表。

HTTP的高级网络操作

网络访问API是一组用于执行常见网络操作的类。该API提供了对使用的特定操作和协议(例如,通过HTTP获取和发布数据)的抽象层,并且仅暴露用于通用或高级概念的类、函数和信号。

网络请求由QNetworkRequest类表示,该类还充当与请求相关的信息的通用容器,例如任何头信息和使用的加密。在构造请求对象时指定的URL决定了请求使用的协议。目前支持HTTP和本地文件URL用于上传和下载。

网络操作的协调由QNetworkAccessManager类执行。一旦创建了请求,该类用于分派请求并发出信号以报告其进度。管理器还协调使用cookies在客户端存储数据、认证请求以及使用代理。

网络请求的回复由QNetworkReply类表示;这些回复在请求被发送时由QNetworkAccessManager创建。QNetworkReply提供的信号可以用来单独监控每个回复,或者开发者可以选择使用管理器的信号来实现这一目的,并放弃对回复的引用。由于QNetworkReply是QIODevice的子类,回复可以同步或异步处理;即,作为阻塞或非阻塞操作。

每个应用程序或库可以创建一个或多个QNetworkAccessManager实例来处理网络通信。

使用QTcpSocket和QTcpServer进行TCP通信

TCP(传输控制协议)是一种低级网络协议,大多数互联网协议(包括HTTP和FTP)都使用它进行数据传输。它是一种可靠的、面向流的、面向连接的传输协议。它特别适合连续的数据传输。

../_images/tcpstream.png

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 ClientFortune Server 示例展示了如何使用 QTcpSocketQTcpServer 来编写 TCP 客户端-服务器应用程序。另请参阅 Blocking Fortune Client 以了解如何在单独的线程中使用同步的 QTcpSocket(不使用事件循环),以及 Threaded Fortune Server 以了解一个多线程 TCP 服务器的示例,每个活动客户端都有一个线程。

使用QUdpSocket与UDP

UDP(用户数据报协议)是一种轻量级、不可靠、面向数据报、无连接的协议。当可靠性不重要时可以使用它。例如,报告一天中时间的服务器可以选择UDP。如果包含一天中时间的数据报丢失,客户端可以简单地发出另一个请求。

../_images/udppackets.png

QUdpSocket 类允许你发送和接收UDP数据报。它继承了 QAbstractSocket,因此共享了 QTcpSocket 的大部分接口。主要区别在于 QUdpSocket 以数据报的形式传输数据,而不是作为连续的数据流。简而言之,数据报是一个大小有限的数据包(通常小于512字节),除了传输的数据外,还包含数据报发送者和接收者的IP地址和端口。

QUdpSocket 支持 IPv4 广播。广播通常用于实现网络发现协议,例如查找网络上哪个主机的硬盘空间最空闲。一个主机向网络广播一个数据报,所有其他主机都会接收到。每个接收到请求的主机随后会向发送者发送回复,告知其当前的可用磁盘空间。发起者等待直到收到所有主机的回复,然后可以选择可用空间最多的服务器来存储数据。要广播数据报,只需将其发送到特殊地址 Broadcast (255.255.255.255),或者发送到本地网络的广播地址。

bind() 准备套接字以接受传入的数据报,类似于 TCP 服务器的 listen()。每当一个或多个数据报到达时,QUdpSocket 会发出 readyRead() 信号。调用 readDatagram() 来读取数据报。

Broadcast SenderBroadcast Receiver 示例展示了如何使用 Qt 编写 UDP 发送器和 UDP 接收器。

QUdpSocket 还支持多播。Multicast SenderMulticast Receiver 示例展示了如何编写UDP多播客户端。

使用QHostInfo解析主机名

在建立网络连接之前,QTcpSocketQUdpSocket 会执行名称查找,将您要连接的主机名转换为IP地址。此操作通常使用DNS(域名服务)协议执行。

QHostInfo 提供了一个静态函数,允许您自己执行这样的查找。通过调用 lookupHost() 并传入主机名、QObject 指针和槽签名,QHostInfo 将执行名称查找并在结果准备好时调用给定的槽。实际的查找是在一个单独的线程中完成的,利用操作系统自己的方法执行名称查找。

QHostInfo 还提供了一个名为 fromName() 的静态函数,该函数以主机名作为参数并返回结果。在这种情况下,名称查找在与调用者相同的线程中执行。此重载对于非GUI应用程序或在单独的、非GUI线程中执行名称查找非常有用。(在GUI线程中调用此函数可能会导致用户界面在函数执行查找时冻结。)

支持网络代理

使用Qt进行网络通信可以通过代理进行,代理可以引导或过滤本地和远程连接之间的网络流量。

单个代理由QNetworkProxy类表示,该类用于描述和配置与代理的连接。支持在不同网络通信级别上操作的代理类型,其中SOCKS 5支持在低级别上代理网络流量,而HTTP和FTP代理在协议级别上工作。有关更多信息,请参见ProxyType

代理可以在每个套接字的基础上启用,也可以为应用程序中的所有网络通信启用。一个新打开的套接字可以在连接之前通过调用其setProxy()函数来使用代理。通过使用setApplicationProxy()函数,可以为所有后续的套接字连接启用应用程序范围的代理。

代理工厂用于创建代理使用的策略。QNetworkProxyFactory 根据特定代理类型的查询提供代理。查询本身编码在 QNetworkProxyQuery 对象中,这些对象使得可以根据关键标准选择代理,例如代理的用途(TCP、UDP、TCP服务器、URL请求)、本地端口、远程主机和端口,以及使用的协议(HTTP、FTP等)。

proxyForQuery() 用于直接查询工厂。可以通过将工厂传递给 setApplicationProxyFactory() 来实现应用程序范围的代理策略,并且可以通过子类化 QNetworkProxyFactory 来创建自定义代理策略;有关详细信息,请参阅类文档。