【C++ Qt】网络编程(QUdpSocket、QTcpSocket、Http)
每日激励:“不设限和自我肯定的心态:I can do all things。 — Stephen Curry”
绪论:
本章将提到Qt中的网络部分,在看这篇文章之前需要有一定的网络基础也就是TCP/HTTP、本篇文章主要讲到的是Qt中基础的Udp、Tcp、Http的使用方法,并附有了多个小demo方便实操练习,并且其中还在每章最后进行了小总结回顾重要接口和函数方便回顾。
————————
早关注不迷路,话不多说安全带系好,发车啦(建议电脑观看)。
网络编程主要依赖于操作系统提供的Socket API。需要注意的是,C++标准库本身并未封装网络编程相关的API。
关于Qt网络编程的几个要点:
- 网络应用开发本质上是编写应用层代码,需要传输层协议(如TCP/UDP)的支持
- 为此,Qt提供了两套专门的网络编程API(QUDPSocket和QTcpSocket)
- 使用Qt网络编程API时,需先在.pro文件中添加network模块
- 之前学习的Qt控件和核心功能都属于QtCore模块(默认已包含)
为什么Qt要划分出这些模块呢?
Qt 本身是一个非常庞大,包罗万象的框架
如果把所有功能都放到一起那么即使一个简单的程序,都会生成很大的可执行程序
所以进行一个模块化处理,将其他的功能分别封装成不同的模块
默认情况下额外模块不会参与编译,需要在 .pro 文件中,引入对应的模块,才能把对应功能给编译加载
Qt 其实提供了 静态库 的版本(所有都引入)、动态库 的版本(指定引入)
1. QUdpSocket 🍿
核心API 概览
主要的类有两个:
QUdpSocket(同Linux理解的socket概念它本质是打开的一个文件描述符,通过这个文件进行通信)QNetworkDatagram(是 Qt 对 UDP 数据报的完整封装,不仅包含二进制数据,还包含了发送/接收的地址端口等元信息,提供了比原始二进制数据处理更方便的面向对象接口。)
QUdpSocket:
| 名称 | 类型 | 说明 | 对标原⽣ API |
|---|---|---|---|
| bind(const QHostAddress&, quint16) | ⽅法 | 绑定指定的端⼝号 | bind |
| receiveDatagram() | ⽅法 | 返回QNetworkDatagram读取⼀个 UDP 数据报. | recvfrom |
| writeDatagram(const QNetworkDatagram&) | ⽅法 | 发送⼀个 UDP 数据报 | sendto |
| readyRead | 信号 | 在收到数据并准备就绪后触发,当socket收到请求的时候,QUdpSocket就会触发这个信号,此时就可以在槽函数里完成请求的操作 | ⽆ (类似于 IO 多路复⽤的通知机制) |
QNetworkDatagram:
| 名称 | 类型 | 说明 | 对标原⽣ API |
|---|---|---|---|
| QNetworkDatagram(const QByteArray&, const QHostAddress& , quint16 ) | 构造函数 | 通过 QByteArray , ⽬标 IP 地址, ⽬标端⼝号 构造⼀个 UDP 数据报. 通常⽤于发送数据时. | ⽆ |
| data() | ⽅法 | 获取数据报内部持有的数据. 返回 QByteArray | ⽆ |
| senderAddress() | ⽅法 | 获取数据报中包含的对端的 IP 地址. | ⽆,recvfrom 包含了该功能. |
| senderPort() | ⽅法 | 获取数据报中包含的对端的端⼝号 | ⽆, recvfrom 包含了该功能. |
实操Demo📕
UDP 回显服务器🍔
实现一个带有界面的 UDP 回显服务器(一般来说正经服务器,很少有界面化,一般都是命令行)
- 创建Widget项目
- 创建一个QListWidget 表示通过 内部的item表示若干条消息
- 创建
QUdpSocket成员变量socket句柄 - 构造函数中
- 初始化socket对象
- 设置窗口标题
- 连接信号槽 接收来自socket的 QUdpSocket中的readyRead 信号,触发槽函数
- 进行绑定端口号(一定是先连接信号槽,在绑定端口)
- socket成员函数
bind - 第一个参数 ip(任意ip也就是
QHostAddress::Any宏) - 第二个参数 端口号
- 一个端口号只能被一个socket绑定,所以可能失败,此时就需要进行防御性编程
- 通过bind的bool类型的返回值知道是否成功
- 若失败则时可以调用
QMessageBox对话框提示- 可以使用
critical(严重)方法 - 标题可以提示:服务器启动错误
- 文本可以填写:socket的成员函数
errorString显示错误(相当于linux中的perror,本质上也是对相同的error的封装,存储着当前的错误信息,当调用的时候就会返回) - 直接
返回结束
- 可以使用
- socket成员函数
- 槽函数(processRequest):
- 读取请求并解析
- 通过socket对象的
receiveDatagram(对比c中的recv)获取请求 - 再通过数据报进行类型
QNetworkDatagram进行接收(requestDatagram) - 再将该对象调用
data函数变成QString类型方便使用
- 通过socket对象的
- 根据请求计算响应(因为是回显服务器,需要不用操作,就是请求本身)
- 新增一个函数process,内部处理获取到的请求,内部并响应
- QString process(QString&)函数:
- 就直接返回request即可(因为是一个回显服务器,对于成熟的项目来说这里会比较复杂,也就是主要的业务逻辑!)
- 把响应写回给客户端
- 将process处理完后返回回来的值(response)调用
toUtf8函数,也就是将 QString 转换为字节数组(这里本质是需要QbyteArray的,但使用Utf8是因为他和QbyteArray都是二进制的,所以可以互通) - 换句话说是因为QNetworkDatagram是需要QByteArray参数进行构造的
- 在设置返回的ip地址和端口,它包含在
QNetworkDatagram(这里需要注意是当时接收请求的对象中的ip和端口)内部函数senderAddress、sendPort中
- 将process处理完后返回回来的值(response)调用
- 最后通过socket对象调用
writeDatagram - 把这次交互的信息显示在界面上
- 构造一个QString 显示内容变量log:ip(根据上述设置ip和端口处一样)、端口(QString::number将字符变成整形)、请求报文数据、响应数据
- 然后给
listwidget多元素控制器添加子项目additem,构造就填入上述的显示内容log即可
- 读取请求并解析
注意在.pro 加入 network

fromstdString 将其他类型变成QString
主要源码如下:
//widget.h#ifndefWIDGET_H#defineWIDGET_H#include<QWidget>#include<QUdpSocket>#include<QHostAddress>#include<QMessageBox>#include<QNetworkDatagram>#include<QDateTime> QT_BEGIN_NAMESPACE namespace Ui {classWidget;} QT_END_NAMESPACE classWidget:publicQWidget{ Q_OBJECT public:Widget(QWidget *parent =nullptr);~Widget();private: Ui::Widget *ui; QUdpSocket* socket; QString process2(const QString& s);private slots:voidprocess();};#endif// WIDGET_H//widget.cpp#include"widget.h"#include"ui_widget.h"Widget::Widget(QWidget *parent):QWidget(parent),ui(new Ui::Widget){ ui->setupUi(this); socket =newQUdpSocket(parent); ui->listWidget->setObjectName(QString("UDP服务器"));connect(socket,&QUdpSocket::readyRead,this,&Widget::process);//注意一定要先连接槽函数在绑定端口号:bool isconnect = socket->bind(QHostAddress::Any,8081);//QHostAddress::Any宏代表任意ip连接if(!isconnect){QMessageBox::critical(this,"错误","服务器启动错误");//弹窗提示return;}setWindowTitle("Udp服务端");}Widget::~Widget(){delete ui;} QString Widget::process2(const QString& request){return request;}voidWidget::process(){//1. 读取请求并解析s QNetworkDatagram requestPacket = socket->receiveDatagram(); QString request = requestPacket.data();//QString s(UdpPacket.data());// QString s2 = QString::fromUtf8(UdpPacket.data()); QString response =process2(request); QNetworkDatagram responsePacket(response.toUtf8(),requestPacket.senderAddress(),requestPacket.senderPort());//使用Utf8是因为他和QbyteArray都是二进制的//时间戳部分没有写在步骤里是因为这部分是临时起意可以直接copy哟~ socket->writeDatagram(responsePacket); qint64 msTimestamp =QDateTime::currentMSecsSinceEpoch(); QDateTime dateTime =QDateTime::fromMSecsSinceEpoch(msTimestamp); QString formatted = dateTime.toString("yyyy-MM-dd hh:mm:ss.zzz"); QString log ="["+ formatted +" : "+ requestPacket.senderAddress().toString()+":"+QString::number(requestPacket.senderPort())+"] req: "+ request +", resp: "+ response; ui->listWidget->addItem(log);}UDP 回显客户端UDPClient 🧋
- 打开ui文件拖拽(要保证可以主动给服务器发起请求)
- 创建QUdpSocket类型的成员变量socket
- 构造函数
- 添加两个全局变量 SERVER_IP 127.0.0.1(
QString) - SERVER_PORT 9090(
quint16:端口号是两个字节的 无符号整数 类似 unsigned short,只不过此处的不同于short他是固定就是2字节的) - 初始化socket对象挂在对象树上
- 修改窗口标题
setWindowTitle
- 添加两个全局变量 SERVER_IP 127.0.0.1(
- 给按钮添加点击信号的槽函数
- 获取输入框的内容
- 构造UDP的请求数据包,创建
QNetworkDatagram对象- 并构造初始化传入获取的内容并且转成字节数组QByteQByteArray(调用
toUtf8) - 还要传入ip和端口(其中ip的QString无法直接使用要转换成
QHostAddres才能使用)
- 并构造初始化传入获取的内容并且转成字节数组QByteQByteArray(调用
- 发送请求数据,调用writeDatagram(使用之前的socket)
- 把发送的请求也添加到列表框中,也就是给多元素控件添加到一个小项目并构造为 客户端说: text,直接使用第一个构造函数传递
QString即可 - 最后把输入框中的内容清空
clear
输入框(单行LineEdit + 多元素控件ListWidget),发送按钮pushButton,显示服务器返回的内容,和显示已经发送的内容

同样对于客户端也要处理下服务器返回回来的响应
- 通过信号槽,来处理服务器的数据
- 同样是通过
readyRead信号,触发信号槽
- 同样是通过
- 信号槽 processResponse
- 通过这个函数处理收到的响应
- 读取响应数据
receiveDatagram,返回一个QNetworkDatagram报文类型变量 - 再通过 data 函数将数据报装换成QString
- 读取响应数据
- 把响应数据显示到界面
- 添加多元素控件添加
子项目:服务器说: text
- 添加多元素控件添加
- 通过这个函数处理收到的响应
最终效果:

此处打印的ip为 ffff 它是ipv6的环回ip
可以通过 build生成的exe文件打开多个客户端
那现在能否将这个UDP服务器放到与服务器上呢?
大概率不行。。(取决于Qt程序是否安装了图形化界面,因为Qt是依赖图形化界面来运行的)
能否使用现在的Udp客户端连之前Linux阶段的Udp服务器—完全OK!
一般也就是说服务器并不会那 Qt写,Qt常用于编写客户端
主要源码如下:
//widgt.h#ifndefWIDGET_H#defineWIDGET_H#include<QWidget>#include<QUdpSocket>#include<QNetworkDatagram>#include<QHostAddress> QT_BEGIN_NAMESPACE namespace Ui {classWidget;} QT_END_NAMESPACE classWidget:publicQWidget{ Q_OBJECT public:Widget(QWidget *parent =nullptr);~Widget();private slots:voidon_pushButton_clicked();private: Ui::Widget *ui; QUdpSocket *socket;private slots:voidprocessResponse();};#endif// WIDGET_H//widget.cpp#include"widget.h"#include"ui_widget.h" QString SERVER_IP ="127.0.0.1"; quint16 SERVER_PORT =8081;//unsigned shortWidget::Widget(QWidget *parent):QWidget(parent),ui(new Ui::Widget){ ui->setupUi(this); socket =newQUdpSocket(parent);setWindowTitle("Udp客户端");//接收处理服务器发送回来的数据connect(socket,&QUdpSocket::readyRead,this,&Widget::processResponse);//通过readyRead信号知道消息准备完毕//}Widget::~Widget(){delete ui;}voidWidget::on_pushButton_clicked(){ QString context = ui->lineEdit->text(); QNetworkDatagram request(context.toUtf8(),QHostAddress(SERVER_IP),SERVER_PORT); socket->writeDatagram(request);// QListWidgetItem *item= new QListWidgetItem(context);//不会内存泄漏因为 item 给到 ListWidget 进行了管理当父类销毁子类也会消亡,但程序运行的内存会越来越大! ui->listWidget->addItem("客户端说:"+ context); ui->lineEdit->clear();}voidWidget::processResponse(){const QNetworkDatagram& packet = socket->receiveDatagram(); QString s =QString::fromUtf8(packet.data()); ui->listWidget->addItem("服务器说"+ s);}Qt UDP总结🔥🔥🔥
常用内容:
QNetworkDatagram数据报类- 作为发送和接收的数据报类,发送和接收的都是这个类型
- 发送时构造的就是传递ip和port指定目的地以及数据
- 接收时则是直接通过socket读取到该类然后将QByteArray的二进制转换成QString即可(
QString::fromUtf8不过也可以直接接收因为QByteArray和utf8本质都是二进制))
QUdpSocket文件类:一个类在客户端和服务端都使用到了- 通过这个类完成对消息的读写操作,并通过QNetWorkDatagram完成消息传递
- 服务端中还需要通过socket绑定
bind服务器的ip和port信息 - 客户端则是自己通过QNetWorkDatagram指定发送服务端信息即可
&QUdpSocket::readyRead消息准备就绪的信号- 通过这个信号连接槽函数,完成对UDP数据的处理
- 注意一定要先连接槽函数在绑定端口号:
.pro 中要添加 network
2. TcpSocket🧋
其中Tcp和Udp的区别这里就不具体提及了~
核心 API 概览,核心类是两个:QTcpServer 和 QTcpSocket 分别作用于服务端和客户端(其中Server自然就是服务端使用,Socket则是客户端使用)
QTcpServer ⽤于监听端口获取客户端连接:
| 名称 | 类型 | 说明 | 对标原⽣ API |
|---|---|---|---|
| listen(const QHostAddress&, quint16 port) | ⽅法 | 绑定指定的地址和端⼝号, 并开始监听. | bind 和 listen |
| nextPendingConnection() | ⽅法 | 从系统中获取到⼀个已经建⽴好的 tcp 连接. 返回⼀个 QTcpSocket , 表⽰这个客⼾端的连接. 通过这个 socket 对象完成和客⼾端之间的通信. | accept |
| newConnection | 信号 | 有新的客⼾端建⽴连接好之后触发 | ⽆ (但是类似于 IO 多路复⽤中的通知机制) |
QTcpSocket ⽤⼾客⼾端和服务器之间的数据交互.(有点类似Udp中的QNetWorkDatagram)
| 名称 | 类型 | 说明 | 对标原⽣ API |
|---|---|---|---|
| readAll() | ⽅法 | 读取当前接收缓冲区中的所有数据. 返回 QByteArray 对象. | read |
| write(const QByteArray& ) | ⽅法 | 把数据写⼊ socket 中. | write |
| deleteLater | ⽅法 | 暂时把 socket 对象标记为⽆效. Qt 会在下个事件循环中析构释放该对象. | ⽆ (但是类似于 “半⾃动化的垃圾回收”) |
| peerAddress | 方法 | 获取对端客户端的ip(通过toString查看ip的字符串) | 无 |
| peerPort | 方法 | 获取对端客户端port | 无 |
| readyRead | 信号 | 有数据到达并准备就绪时触发. | ⽆ (但是类似于 IO 多路复⽤中的通知机制) |
| disconnected | 信号 | 连接断开时触发. | ⽆ (但是类似于 IO 多路复⽤中的通知机制) |
实操 Demo 🗒️

Tcp回显服务器🍕
- 创建Widget项目(.pro 中添加 network)
- ui文件中拖拽一个
ListWidget - 添加
QTcpServer类成员变量句柄 - 构造函数中:
- 修改窗口标题 服务器
- 创建
QTcpServer的实例,并挂在对象树上 - 创建 newConnection 信号的信号槽processConnection
- 通过
nextPendingConnection获取一个客户端的socket(clientSocket 变量)- 然后通过clientSocket 获取 :ip地址和端口(
peerAddress、peerPort对端客户端的ip和端口) - 其中端口需要转换成整数使用QString::number + “客户端上线” 的信息
- 并且在ListWidget中添加子项
- 然后通过clientSocket 获取 :ip地址和端口(
- 处理客户端发来请求的情况,通过
readyRead信号,lambda表达式:- 获取客户端的消息
readAll,获取request请求报文 - 根据请求处理响应 通过
process函数完成:内部应该是业务逻辑,但此处只是回显,所以直接返回请求的QString即可,得到response请求报文 - 把响应返回给客户端(
write) - 把上述内容记录到日志中(这里就打印即可),日志信息:客户端ip、端口(改成整形)、request、response
- 获取客户端的消息
- 处理客户端断开连接的情况
- 通过
disconnect信号,lambda表达式: - 把断开连接的信息通过日志显示:客户端ip + 端口 + 客户端已下线
- 存放到ListWidget中
- 手动释放
ClientSocket(这个东西是可能存在N个的,随着客户端越来越多不释放,积累起来就会导致 内存泄漏和文件描述符泄露) - delete clientSocket(这样写时可以的不过必须要保证这是最后一步,因为这个lambda中都是围绕着clientSocket来进行的,并且也可能因为一些如异常或者return导致没有delete)所以这里还是不建议使用的
- 而是通过调用clientSocket的deleteLater(他不是立即销毁的,而是在下一轮事件循环中,再进行上述销毁操作)
- 通过
- 通过
- 绑定并监听端口号
- 一定要确保先写好处如何理连接,处理请求再绑定监听
listen(监听接收任意ip,端口)- 可能失败所以接收listen返回值进行判断 若失败则弹出对话框
QMessageBos::criticalthis 服务器启动失败 tcpSerccer的方法eroorString显示错误原因
上述代码其实不够严谨,作为回显服务器是已经够了的
实际使用 TCP 的过程中,TCP 是面向字节流的,一个完整的请求,可能会分成多断字节数组进行传输
虽然 TCP 已经帮我们处理了很多棘手的问题了,但是 TCP 本身不负责区分,从哪里到哪里是一个完整的应用层数据报 (粘包问题)
更严谨的做法,应该是每次收到的数据都给放到一个大的字节数组缓冲区中,并且提前约定好应用层协议的格式(分隔符?长度?其他办法?)再按照协议格式对缓冲区数据进行更细致的解析处理~~
(当前不打算写这么复杂了)
主要源码:

#include"widget.h"#include"ui_widget.h"Widget::Widget(QWidget *parent):QWidget(parent),ui(new Ui::Widget){ ui->setupUi(this);setWindowTitle("服务器"); server =newQTcpServer(parent);connect(server,&QTcpServer::newConnection,this,&Widget::processConnection);//处理新链接//注意监听前一定要先把上述内容先绑定了!bool ret = server->listen(QHostAddress::Any,8080);//失败会返回false所以进行接收处理if(!ret){QMessageBox::critical(this,"服务器启动失败",server->errorString());//弹出消息框return;}}Widget::~Widget(){delete ui;} QString Widget::process(QString& requset)// 主要业务逻辑{return requset;//这里只是最简单的回显服务器所以就直接返回了}voidWidget::processConnection(){ QTcpSocket *clientSocket = server->nextPendingConnection();//从系统中获取一个已经建立好的tcp连接 QHostAddress add = clientSocket->peerAddress();//ip:add.toString();通过转换成String就能看到ip quint16 port = clientSocket->peerPort();//端口 ui->listWidget->addItem("["+add.toString()+":"+ port +"]:"+ \ "客户端已上线");//处理客户端发来的请求(同样的readyRead信号)connect(clientSocket,&QTcpSocket::readyRead,this,[=](){//注意QT信号槽中使用lambda时不要引用(&)获取而是值(=)获取 注意!!!! QString request = clientSocket->readAll();//获取用户的request数据报//处理请求~~const QString& response =process(request); clientSocket->write(response.toUtf8()); QString log ="["+ add.toString()+":"+QString::number(port)+"] req:"+ request +",res:"+ response; ui->listWidget->addItem(log);});//处理客户端断开(disconnected信号)connect(clientSocket,&QTcpSocket::disconnected,this,[=](){ QString log ="["+ add.toString()+":"+ port +"]已下线";qDebug()<< log;//日志打印 ui->listWidget->addItem(log);//注意一定要释放该clientSocket,他本质就是文件描述符// delete clientSocket;这个容易出现问题:立即删除对象:立即释放内存、可能正在处理信号槽:如果还有未处理的信号,会导致崩溃、可能引发二次删除:如果有父对象,父对象可能再次删除它、线程不安全:如果在事件循环中调用,可能导致不可预测行为 clientSocket->deleteLater();//他不是立即销毁的,而是在下一轮事件循环中,再进行上述销毁操作;保证信号槽安全:所有挂起的事件和信号都会先被处理});}Tcp客户端的实现🍿

- 创建新的Widget项目
- 创建
QTcpSocket对象(socket) - 构造函数中:
- 设置窗口
- 创建socket的实例(并指定父对象)
- 和服务器建立连接
connectToHsot(服务器ip、端口) 开始三次握手(但又不同于原生api的connect是阻塞的) - 通过
readyRead信号 的 信号槽,处理响应(直接使用lambda):- 读取响应内容 response
- 把响应内容添加到界面上(ListWidget->addItem)
- 等待连接建立的结果,确定是否连接成功 waitForConnected
- 同样进行判断 连接是否成功的返回值,若失败则 弹出致命错误对话框
- “服务器连接出错”;socket的方法
errorString;并直接终止进程
- 给按钮添加点击的槽函数:
- 获取输入框内容
- 发送输入框内容给服务器注意转成QByteArray
- 把消息显示到界面上 客户端说: text
- 清空输入框内容

主要源码:

//widget.cpp#include"widget.h"#include"ui_widget.h"Widget::Widget(QWidget *parent):QWidget(parent),ui(new Ui::Widget){ ui->setupUi(this); socket =newQTcpSocket(parent); socket->connectToHost("127.0.0.1",8080);//处理返回的消息connect(socket,&QTcpSocket::readyRead,this,[=](){ QString response = socket->readAll(); ui->listWidget->addItem("服务端说:"+ response);});//处理连接失败if(!socket->waitForConnected()){QMessageBox::critical(this,"client 连接服务器失败",socket->errorString());exit(0);//结束进程}}Widget::~Widget(){delete ui;}voidWidget::on_pushButton_clicked(){ QString request = ui->lineEdit->text(); socket->write(request.toUtf8()); ui->listWidget->addItem("客户端说:"+ request);//最后清空LineEdit ui->lineEdit->clear();}之前在学习 Linux 时写的 TCP 的回显服务器的时候,遇到了一个问题:多个客户端同时访问的时候,就只有一个生效后来引入了多线程,每个客户端安排一个单独的线程,问题才得到改善
之前写的那个程序,之所以出现上述问题,和 TCP 和 多线程 都没啥关系…从来没有说法说 TCP 服务器必须使用多线程编写!
之前存在这个问题的本质原因,是写双重循环,里层循环没有及时结束,导致外层循环不能快速的第二次调用到accept,导致第二个客户端无法进行处理了。
引入多线程,本质上就是把双重循环,化简成两个独立的循环
咱们 Qt 的服务器程序中,其实一个循环都没写, 是通过 Qt 内置的 信号槽 来驱动的
Qt Tcp总结🔥🔥🔥
- 服务器中 QTcpServer::newConnection 处理新链接的信号,注意需要连接从而完成新链接新用户的消息处理和断开连接处理
- nextPendingConnection() 从就绪的连接中获取一个,从而拿到一个客户端(QTcpSocket *)
- QTcpSocket常用的函数:
- peerAddress(获取地址)、peerPort(端口)
- readAll(获取socket中的数据)
- write(通过socket发送消息数据:客户端需要设置3点、而服务端中它拿到的socket是已经确定好客户端的),socket本质其实就是{ip:port}找到另外一方
- connectToHost(客户端中使用:确定连接的服务端,从而确定消息发送位置)
- QTcpSocket::readyRead,同前面客户端消息就绪的信号
- 注意QT信号槽中使用lambda时不要引用(&)获取而是值(=)获取 注意!!!!
- QTcpSocket::disconnected 用户断开时会触发的信号
总之:
- TcpServer是服务器用于获取客户端以及处理客户端新链接、新消息、断开的类
- TcpSocket是一个中间件完成消息的传递,服务端可以通过nextPendingConnection获取一个连接(用户客户端),而客户端则是直接创建一个TcpSocket并且设置要发送的服务端
3. HTTP Client 🥞
Qt中也是提供了Http的客户端,Http协议本质上也就是基于TCP协议实现的,实现一个HTTP客户端/服务器,本质上也是基于Tcp Socket 进行封装的
核⼼ API
关键类主要是三个:
- QNetworkAccessManager
- QNetworkRequest
- QNetworkReply
QNetworkAccessManager 提供了 HTTP 的核⼼操作
| ⽅法 | 说明 |
|---|---|
| get(const QNetworkRequest& ) | 发起⼀个 HTTP GET 请求. 返回 QNetworkReply 对象. |
| post(const QNetworkRequest& , const QByteArray& ) | 发起⼀个 HTTP POST 请求. 返回 QNetworkReply 对象. |
QNetworkRequest 表⽰⼀个 HTTP 请求(不含 body行):
如果需要发送⼀个带有 body 的请求(⽐如 post), 会在 QNetworkAccessManager 的 post ⽅法中通过单独的参数来传⼊ body.
| ⽅法 | 说明 |
|---|---|
| QNetworkRequest(const QUrl& ) | 通过 URL 构造⼀个 HTTP 请求. |
| setHeader(QNetworkRequest::KnownHeaders header, const QVariant &value) | 设置请求头. |
其中的QVariant本质 可以理解成 void*
其中的 QNetworkRequest::KnownHeaders 是⼀个枚举类型, 常⽤取值:
| 取值 | 说明 |
|---|---|
| ContentTypeHeader | 描述 body 的类型. |
| ContentLengthHeader | 描述 body 的⻓度. |
| LocationHeader | ⽤于重定向报⽂中指定重定向地址. (响应中使⽤, 请求⽤不到) |
| CookieHeader | 设置 cookie |
| UserAgentHeader | 设置 User-Agent |
QNetworkReply 表⽰⼀个 HTTP 响应. 这个类同时也是 QIODevice 的⼦类
| ⽅法 | 说明 |
|---|---|
| error() | 获取出错状态. |
| errorString() | 获取出错原因的⽂本. |
| readAll() | 读取响应 body. |
| header(QNetworkRequest::KnownHeaders header) | 读取响应指定 header 的值. |
实操Demo Http客户端🥐

上面的框框并不是ListWidget了而是QPlainTextEdit,因为它返回的大概率是一个html(其中不使用QTextEdit是因为他会自动渲染HTML)
- 同样的
.pro中添加network标识 - 添加
QNetworkAccessManger成员变量(client) - 构造函数:
- 实例话添加的成员变量,指定父元素(对象树)
- 给按钮添加点击的槽函数:
- 获取输入框中的url(
QUrl对象获取 QString能自动转换成QUrl) - 通过client调用get方法并传递url(post请求的话需要添加body)
- 构造一个HTTP请求对象 requset(传入url),会返回一个QNetworkReply响应对象
- 但同样注意的是 get只负责发送请求,并不会阻塞等待,那么解决方法:response内部有一个finished信号,当响应返回时就会触发这个信号
- 使用connect连接并直接通过lambda简单实现槽函数即可
- 所以通过response的 finished信号 的槽函数进行处理:
- 判断response的error 若等于 NoError则代表响应正确获取:
- 通过readAll读取response的html数据(QString)
- 并设置到PlainText中
- 若失败,则将错误信息(使用errorString)设置到PalinText中
- 最后同样调用response的deleteLater,让其在该轮事件循环结束后释放
- 获取输入框中的url(
– 注意最后这里访问的是http的而非https

实际开发中, HTTP Client 获取到的数据,也不一定非得是 HTML, 更大的可能性是客户端开发和服务器开发约定好交互的数据格式,按照约定的格式,客户端拿到之后,进行解析,并显示到界面上.
源码:

#include"widget.h"#include"ui_widget.h"Widget::Widget(QWidget *parent):QWidget(parent),ui(new Ui::Widget){ ui->setupUi(this); client =newQNetworkAccessManager(parent);}Widget::~Widget(){delete ui;}voidWidget::on_pushButton_clicked(){ QUrl url = ui->lineEdit->text(); QNetworkRequest request(url);//一个http请求 QNetworkReply *response = client->get(request);//其中这里并不会阻塞,所以需要通过一个信号来处理后续返回//这里的 response QNetworkReply内部有的信号finished当触发时代表响应了connect(response,&QNetworkReply::finished,this,[=](){if(response->error()== QNetworkReply::NoError){//若相等则代表没有错误 QString req = response->readAll(); ui->plainTextEdit->setPlainText(req);}else{ ui->plainTextEdit->setPlainText(response->errorString());} response->deleteLater();//下一个事件循环时销毁});}Qt Http总结🔥🔥🔥
QNetworkRequest构造一个http请求,初始化填入url代表路径- 这里url需要的类型是QUrl但也很好转换QString能直接转换
QNetworkAccessManager中的get/post(本质就是http的method)函数进行发送请求和并且接收响应QNetworkReply,但需要注意的是响应处不是阻塞的- 所以需要设置一个信号槽,信号是
QNetworkReply::finished当数据到达时就会触发 - 当QNetworkReply的
error函数和QNetworkReply::NoError相等就代表没有问题此时就能在继续处理http响应了
本章完。预知后事如何,暂听下回分解。
如果有任何问题欢迎讨论哈!
如果觉得这篇文章对你有所帮助的话点点赞吧!
持续更新大量C++ Qt细致内容,早关注不迷路。