QT中的网络编程
Qt中的网络编程是通过封装操作系统的API进行实现的
C++标准库中,并没有提供网络编程的封装接口
进行网络编程时本质是在编写应用层代码,需要传输层提供支持
传输层最核心的协议为UDP/TCP
使用Qt网络编程的API时,需要在.pro文件中添加network模块
UDP Socket
Qt中提供进行udp编程的类主要有两个:QUdpSocket和QNetworkDatagram
QUdpSocket表示一个UDP的socket文件
名称 | 类型 | 说明 | 对标原生API |
bind(const QHostAddress&,quint16) | 方法 | 绑定指定的端口号 | bind |
receiveDatagram() | 方法 | 返回 QNetworkDatagram,读取一个UDP数据报 | recvfrom |
writeDatagram(const QNetworkDatagram&) | 方法 | 发送一个UDP数据报 | sendto |
readyRead | 信号 | 在收到数据并准备就绪后触发 |
QNetworkDatagram表示一个UDP数据报
名称 | 类型 | 说明 |
QNetworkDatagram(const QByteArray&,const QHostAddress&,quint16) | 构造函数 | 通过QByteArray,目标IP地址,目标端口号,构造一个UDP数据报,通常用于发送数据时 |
data() | 方法 | 获取数据报内部持有的数据,返回QByteArray |
senderAddress() | 方法 | 获取数据并中包含的对端的IP地址 |
senderPort() | 方法 | 获取数据报中包含的对端的端口号 |
例如:
实现基于UDP Socket写一个带有界面的Udp回显服务器
创建一个继承自Widget的文件
在ui界面创建一个list widget用于显示接收到的数据
在.pro文件中加入网络编程模块
在widget头文件中引入<QUdpSocket>头文件,若无法识别则编译一遍再看是否被识别
声明UdpSocket,将其作为成员变量
在构造函数进行实例化,将其挂到对象树,设置服务器窗口标题
在头文件声明处理函数,在构造函数进行信号槽连接
在构造函数绑定端口号
需要先进行信号槽绑定,再绑定端口号,因为绑定端口号后就能接收请求了,若端口号绑定完成后还未绑定信号槽就无法处理请求
在绑定端口号时,还需对返回值判断,避免端口号已经被绑定,导致绑定失败
实现槽函数,读取请求,构建响应,发送响应
在头文件中声明通过读取到的有效数据构建响应中有效数据的函数
实现
构建客户端,实现向服务器发送请求
新建文件继承自Widget,实现客户端
同样在.pro文件加上网络模块
在ui界面创建一个输入框用来接收有效数据,一个pushbutton,用于获取有效数据,构建数据报进行发送,构建一个listwidget用来显示发送的请求
将lineedit和pushbutton放在水平布局中--horizonlayout,再将水平布局放置在垂直布局中,将listwidget放置在垂直布局中
将垂直布局的拉伸系数设为5比1
再将lineedit和pushbutton的sizePolicy中的垂直策略设为expanding
在widget.cpp中定义目的IP,port
在widget.h中引入udpsocket作为成员变量,在构造函数进行定义,修改窗口标题
实现pushbutton的clicked的槽函数,来获取输入框数据,构建数据报进行发送
通过信号槽来处理服务器返回的响应
在widget头文件声明槽函数
在构造函数连接信号槽
实现槽函数
代码编写完成:
先启动服务器,再启动客户端
在客户端发送请求,能够收到响应,进行回显
:ffff是ipV6的环回ip
Qt中udp通信流程:
服务器:
创建socket,通过connect,连接socket,readyRead信号,和处理对象,处理函数,当收到请求后触发readyRead信号,调用处理函数。再将socket绑定端口号,获取固定端口号收到的数据。
收到数据后,通过
QNetworkDatagram& requestDatagram=socket->receiveDatagram()
来接收数据报,用
QString request=requestDatagram.data();
提取有效内容,对有效内容---请求进行处理后,构建响应
QNetworkDatagram responseDatagram(response.toUtf8(),requestDatagram.senderAddress(),requestDatagram.senderPort());
将响应通过
socket->writeDatagram(responseDatagram);
发送给客户端
客户端:
创建socket,构建请求数据报,将请求数据报发送到目的服务器
QNetworkDatagram requestDatagram(text.toUtf8(),QHostAddress(SERVER_IP),PORT);
//发送请求数据并
socket->writeDatagram(requestDatagram);
接收响应,通过连接socket的readyRead信号,实现槽函数,接收响应读取有效内容
const QNetworkDatagram& responseDatagram=socket->receiveDatagram();
QString response=responseDatagram.data();
Qt中无法将服务器放写在云服务器上,因为云服务器默认不带图形化界面,Qt程序需要依赖图形化界面来允许
但是可以通过Qt编写的客户端来连接Linux下连接云服务器写的服务器-----常见实现
TCP Socket
核心类是QTcpServer和QTcpSocket
QTcpServer用于监听端口,和获取客户端连接
名称 | 类型 | 说明 |
listen(const QHostAddress& ,quit16port) | 方法 | 绑定指定的地址和端口号,并开始监听 |
nextPendingConnection() | 方法 | 从系统中获取到一个已经建立好的tcp连接,返回一个QTcpSocket,表示这个客户端的连接,通过这个socket对象完成和客户端之间的通信 |
newConnection | 信号 | 有新的客户端建立来连接好后触发 |
QTcpSocket用户客户端和服务器之间的数据交互
名称 | 类型 | 说明 |
readAll() | 方法 | 读取当前接收缓冲区中的所有数据,返回QByteArray()对象 |
write(const QByteArray&) | 方法 | 把数据写入socket中 |
deleteLater | 方法 | 暂时把socket对象标记为无效,Qt会在下个事件循环中析构释放该对象 |
readyRead | 信号 | 有效数据到达并准备就绪时触发 |
disconnected | 信号 | 连接断开时触发 |
因为TCP时面向字节流的,所有读取数据和写入数据都是通过QByteArray
QByteArray用来表示一个字节数组,可以与QString进行相互转换
如:
使用QString的构造函数可以把QByteArray转换为QString
使用QString的toUtf8函数即可把QString转换为QByteArray
例如:
实现TCP回显服务器
在.pro文件引入网络模块,在widget头文件引入<QTcpServer>
在ui界面创建listWidget用于显示响应和请求
在构造函数设置窗口标题
在widget.h的类中声明QTcpServer作为成员变量,在构造函数进行初始化,并链入对象树
进行信号槽连接,声明槽函数,实现处理客户端连接
再将tcp设置绑定并监听,来用于获取新连接
实现获取连接后的槽函数,进行获取请求,进行响应,处理客户端断开连接
实现Tcp客户端
新建Widget文件
在.pro文件添加网络模块
在ui界面设计界面添加listwidget,pushbutton,lineEdit,设置布局
在widget.h声明QTcpSocket为成员变量,构造函数处进行初始化
在构造函数进行与服务器的连接
转到按钮的槽函数,实现请求的发送
在构造函数处绑定readyRead信号的槽函数,实现接收服务器的响应
先运行服务器,再启动客户端进行测试
退出客户端后也进行了对应处理
在Linux中多个客户端同时访问一个端口时,就只有一个客户端生效,因为服务端是单线程的,可以引入多线程,每个线程都单独处理一个客户端发送的请求
但是在qt中,上面代码能够处理多个客户端,无需为服务端引入多线程,因为问题的本质原因是在Linux等待客户端连接后,通过死循环来与客户端进行信息传输,若只有一个线程就只能进行一个死循环,而在qt中,无需循环,是通过信号槽来与客户端进行信息传输
HTTP Client
http是应用层协议,http协议的本身是基于Tcp协议实现的,使用一个Http的客户端/服务端,本质上是基于TCP Socket进行封装的
Qt仅提供了HTTP客户端,没有提供HTTP的服务端
主要类为:QNetworkAccessManager,QNetworkRequest,QNetworkReply
QNetworkAccessManager核心操作
方法 | 说明 |
get(const QNetworkRequest&) | 发起一个HTTP GET请求,返回QNetworkReply对象 |
post(const QNetworkRequest,const QByteArray&) | 发起一个HTTP POST请求,返回QNetworkReply对象 |
QNetworkRequest表示一个http请求---不包含请求正文
如果需要发送一个带有body---请求正文的请求,需要使用QNetworkAccessManager的post方法中通过单独的参数传入
方法 | 说明 |
QNetworkRequest | 通过URL构造一个HTTP请求 |
setHeader(QNetworkRequest::KnownHeaders header, const QVariant& value) | 设置请求头 |
其中QNetworkRequest::KnownHeaders是一个枚举类型:
取值 | 说明 |
ContentTypeHeader | 描述body的类型 |
ContentLengthHeader | 描述body的长度 |
LocationHeader | 用于重定向报文中指定的重定向地址(响应中使用) |
CookieHeader | 设值cookie |
UserAgenHeader | 设置User-Agent |
QNetworkReply表示一个HTTP响应,这个类同时也是QIODevice的子类
方法 | 说明 |
error() | 获取出错状态 |
errorString() | 获取出错原因的文本 |
readAll() | 读取响应body |
header(QNetworkRequest::KnownHeaders header) | 读取响应指定header的值 |
QNetworkReply还提供finished信号,会在客户端收到完整的响应数据之后触发
例:
创建一个继承自Widget的文件
在.pro文件添加网络模块
在ui界面创建一个listWidget用于显示请求和收到的响应内容,一个lineEdit用于输入url,一个按钮用于构建请求并发出
将响应的结果---HTML用QPlainTextEdit---纯文本用来表示,展示原始的html
若使用QTextEdit---富文本则会对htm进行解析渲染---支持标签的使用
将QNetworkAccessManager声明为类成员,在构造函数进行其初始化,和窗口标题定义,输入栏提示
转到pushButton的槽函数进行请求的发送,获取响应
此时就可以进行请求的发送
将plainedit替换为textedit后---支持富文本,并在输出响应时,采用setHtml来写入textedit后:
对标签进行解析