14. 网络编程

计算机网络相关的知识涉及非常广泛,一些基本的概念就需要读者自行掌握, 本章主要讲在Qt中HTTP,TCP和UDP的应用编程。

在HTTP级别,Qt Network模块提供了网络访问API,主要由QNetworkRequest,QNetworkAccessManager和QNetworkReply组成。 简而言之,QNetworkRequest类似于HTTP请求,该请求被传递给QNetworkAccessManager以在线发送请求。 此类返回一个QNetworkReply,它可以解析HTTP答复。网络访问API在内部使用下面提到的套接字类(对于TCP和SSL)。

对于套接字级别的通信分别有QTcpSocket,QUdpSocket和QSslSocket来实现, 这些类通过waitFor *方法提供了一个同步API以及一个异步API,如果可能的话(例如,事件循环正在运行),应始终首选异步API。 Qt还提供了QTcpServer来启用TCP通信的服务器端部分。

为了通过SSL进行安全通信,Qt Network除了中央QSslSocket外还提供了很多具备辅助功能的类, 例如QSslCertificate,QSslConfiguration和QSslError。Qt中唯一受支持的SSL后端是OpenSSL,需要单独安装。

14.1. TCP

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

Qt中QTcpSocket类提供了TCP的接口,在这个基础上我们也可以使用QTcpSocket来实现标准网络协议,例如POP3,SMTP和NNTP,以及自定义协议。

QTcpSocket使用必须先建立与远程主机和端口的TCP连接,然后才能开始任何数据传输。 建立连接后,即可通过QTcpSocket::peerAddress()和QTcpSocket::peerPort()获得对等方的IP地址和端口。 对等方可以随时关闭连接,然后数据传输将立即停止。

QTcpSocket异步工作,并发出信号来报告状态更改和错误,就像QNetworkAccessManager一样。 它依靠事件循环来检测输入数据并自动刷新输出数据。您可以使用QTcpSocket::write()将数据写入套接字,并使用QTcpSocket::read()读取数据。 QTcpSocket表示两个独立的数据流:一个用于读取,一个用于写入。

由于QTcpSocket继承了QIODevice,因此可以将其与QTextStream和QDataStream一起使用。 从QTcpSocket读取时,必须通过事先调用QTcpSocket::bytesAvailable()来确保有足够的数据可用。

如果您需要处理客户端发起的TCP连接(例如,在服务器应用程序中),请使用QTcpServer类。 调用QTcpServer::listen()设置服务器,并连接到QTcpServer::newConnection()信号,该信号对于每个连接的客户端发出一次。 在对应的槽函数中,调用QTcpServer::nextPendingConnection()接受连接,并使用返回的QTcpSocket与客户端进行通信。

尽管它的大多数功能都是异步工作的,但也可以同步使用QTcpSocket(即阻塞)。 要获得阻塞行为,请调用QTcpSocket的waitFor…()函数;它们挂起调用线程,直到发出信号为止。 例如,在调用非阻塞QTcpSocket::connectToHost()函数之后, 调用QTcpSocket::waitForConnected()阻塞线程,直到发出connect()信号为止。

同步套接字通常导致代码具有更简单的控制流程。waitFor…()方法的主要缺点是,在waitFor…()函数阻塞时不会处理事件。 如果在GUI线程中使用,这可能会冻结应用程序的用户界面。因此,我们建议您仅在非GUI线程中使用同步套接字,同步使用时,QTcpSocket不需要事件循环。

14.2. UDP

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

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

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

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

该广播发送器和广播接收器的示例显示如何写一个UDP发送者和使用Qt一个UDP接收机。

QUdpSocket还支持多播。“多播发送者”和“多播接收器”示例显示了如何使用写入UDP多播客户端。

14.3. HTTP

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

网络请求由QNetworkRequest类表示,该类也充当与请求相关联的信息(例如,任何标头信息和所使用的加密)的常规容器。 构造请求对象时指定的URL确定用于请求的协议。当前,支持HTTP,FTP和本地文件URL进行上载和下载。

网络操作的协调由QNetworkAccessManager类执行。创建请求后,将使用此类来分派请求并发出信号以报告其进度。 管理器还协调使用Cookie来存储客户端上的数据,身份验证请求以及代理的使用。

对网络请求的答复由QNetworkReply类表示。这些是由QNetworkAccessManager在调度请求时创建的。 QNetworkReply提供的信号可用于单独监视每个答复,或者开发人员可以选择为此目的使用管理器的信号,而放弃对答复的引用。 由于QNetworkReply是QIODevice的子类,因此可以同步或异步地处理答复。即作为阻塞或非阻塞操作。

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

14.4. 例程说明

野火提供的Qt Demo已经开源,仓库地址在:

文档所涉及的示例代码也提供下载,仓库地址在:

本章例程在 embed_qt_develop_tutorial_code/Data

例程通过QNetworkRequest从服务器获取野火demo最新的程序包。

14.4.1. 编程思路

14.4.2. 代码讲解

14.4.3. 编译构建

build001
  • Ubuntu 选择 Desktop Qt 5.11.3 GCC 64bit 套件,编译运行

  • LubanCat 选择 ebf_lubancat,只编译

提示

当两种构建套件进行切换时,请重新构建项目或清除之前项目。针对我们的工程还需要手动重新构建QtUI和Skin。

build002

14.4.4. 运行结果

14.4.4.1. PC实验

直接点击编译并运行程序,结果如下:

result001

14.4.4.2. LubanCat实验

通过SCP或者NFS将编译好的程序拷贝到LubanCat上

NFS环境搭建 参考这里

SCP命令如下:

# scp传输文件
# scp 文件名 服务器上的某个用户@服务器ip:/文件保存路径
scp filename server_user_name@192.168.0.205:server_file_path
# 从服务器拉取文件
# scp 服务器上的某个用户@服务器ip:/服务器文件存放路径 拉取文件保存路径
scp server_user_name@192.168.0.229:server_file_path local_path

编译好的程序在 embed_qt_develop_tutorial_code/app_bin/data 目录中,通过scp命令将编译好的程序拉到LubanCat。

scp root@192.168.0.174:/home/embed_qt_develop_tutorial_code/app_bin/data/Setting /usr/local/qt-app/

在LubanCat运行程序,使用run_myapp.sh配置好环境,并执行 Setting 。

sudo /usr/local/qt-app/run_myapp.sh /usr/local/qt-app/Setting
result002