【C++ Qt】网络编程(QUdpSocket、QTcpSocket、Http)

【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网络编程的几个要点:

  1. 网络应用开发本质上是编写应用层代码,需要传输层协议(如TCP/UDP)的支持
  2. 为此,Qt提供了两套专门的网络编程API(QUDPSocket和QTcpSocket)
  3. 使用Qt网络编程API时,需先在.pro文件中添加network模块
  4. 之前学习的Qt控件和核心功能都属于QtCore模块(默认已包含)

为什么Qt要划分出这些模块呢?

Qt 本身是一个非常庞大,包罗万象的框架
如果把所有功能都放到一起那么即使一个简单的程序,都会生成很大的可执行程序
所以进行一个模块化处理,将其他的功能分别封装成不同的模块
默认情况下额外模块不会参与编译,需要在 .pro 文件中,引入对应的模块,才能把对应功能给编译加载
Qt 其实提供了 静态库 的版本(所有都引入)、动态库 的版本(指定引入)

1. QUdpSocket 🍿

核心API 概览
主要的类有两个:

  1. QUdpSocket(同Linux理解的socket概念它本质是打开的一个文件描述符,通过这个文件进行通信)
  2. 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 回显服务器(一般来说正经服务器,很少有界面化,一般都是命令行)

  1. 创建Widget项目
  2. 创建一个QListWidget 表示通过 内部的item表示若干条消息
  3. 创建QUdpSocket成员变量socket句柄
  4. 构造函数中
    1. 初始化socket对象
    2. 设置窗口标题
    3. 连接信号槽 接收来自socket的 QUdpSocket中的readyRead 信号,触发槽函数
  5. 进行绑定端口号(一定是先连接信号槽,在绑定端口)
    1. socket成员函数bind
    2. 第一个参数 ip(任意ip也就是QHostAddress::Any宏
    3. 第二个参数 端口号
    4. 一个端口号只能被一个socket绑定,所以可能失败,此时就需要进行防御性编程
    5. 通过bind的bool类型的返回值知道是否成功
    6. 若失败则时可以调用QMessageBox对话框提示
      1. 可以使用critical(严重)方法
      2. 标题可以提示:服务器启动错误
      3. 文本可以填写:socket的成员函数errorString显示错误(相当于linux中的perror,本质上也是对相同的error的封装,存储着当前的错误信息,当调用的时候就会返回)
      4. 直接 返回 结束
  6. 槽函数(processRequest):
    1. 读取请求并解析
      1. 通过socket对象的receiveDatagram(对比c中的recv)获取请求
      2. 再通过数据报进行类型QNetworkDatagram进行接收(requestDatagram)
      3. 再将该对象调用data函数变成QString类型方便使用
    2. 根据请求计算响应(因为是回显服务器,需要不用操作,就是请求本身)
      1. 新增一个函数process,内部处理获取到的请求,内部并响应
      2. QString process(QString&)函数:
        1. 就直接返回request即可(因为是一个回显服务器,对于成熟的项目来说这里会比较复杂,也就是主要的业务逻辑!)
    3. 把响应写回给客户端
      1. 将process处理完后返回回来的值(response)调用 toUtf8 函数,也就是将 QString 转换为字节数组(这里本质是需要QbyteArray的,但使用Utf8是因为他和QbyteArray都是二进制的,所以可以互通)
      2. 换句话说是因为QNetworkDatagram是需要QByteArray参数进行构造的
      3. 在设置返回的ip地址和端口,它包含在QNetworkDatagram(这里需要注意是当时接收请求的对象中的ip和端口)内部函数senderAddresssendPort
    4. 最后通过socket对象调用writeDatagram
    5. 把这次交互的信息显示在界面上
      1. 构造一个QString 显示内容变量log:ip(根据上述设置ip和端口处一样)、端口(QString::number将字符变成整形)、请求报文数据、响应数据
      2. 然后给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 🧋

  1. 打开ui文件拖拽(要保证可以主动给服务器发起请求)
  2. 创建QUdpSocket类型的成员变量socket
  3. 构造函数
    1. 添加两个全局变量 SERVER_IP 127.0.0.1(QString
    2. SERVER_PORT 9090(quint16:端口号是两个字节的 无符号整数 类似 unsigned short,只不过此处的不同于short他是固定就是2字节的)
    3. 初始化socket对象挂在对象树上
    4. 修改窗口标题 setWindowTitle
  4. 给按钮添加点击信号的槽函数
    1. 获取输入框的内容
    2. 构造UDP的请求数据包,创建QNetworkDatagram对象
      1. 并构造初始化传入获取的内容并且转成字节数组QByteQByteArray(调用toUtf8
      2. 还要传入ip和端口(其中ip的QString无法直接使用要转换成QHostAddres才能使用)
    3. 发送请求数据,调用writeDatagram(使用之前的socket)
    4. 把发送的请求也添加到列表框中,也就是给多元素控件添加到一个小项目并构造为 客户端说: text,直接使用第一个构造函数传递QString即可
    5. 最后把输入框中的内容清空clear

输入框(单行LineEdit + 多元素控件ListWidget),发送按钮pushButton,显示服务器返回的内容,和显示已经发送的内容

在这里插入图片描述

同样对于客户端也要处理下服务器返回回来的响应

  1. 通过信号槽,来处理服务器的数据
    1. 同样是通过 readyRead 信号,触发信号槽
  2. 信号槽 processResponse
    1. 通过这个函数处理收到的响应
      1. 读取响应数据 receiveDatagram,返回一个QNetworkDatagram报文类型变量
      2. 再通过 data 函数将数据报装换成QString
    2. 把响应数据显示到界面
      1. 添加多元素控件添加子项目服务器说: 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总结🔥🔥🔥

常用内容:

  1. QNetworkDatagram 数据报类
    1. 作为发送和接收的数据报类,发送和接收的都是这个类型
    2. 发送时构造的就是传递ip和port指定目的地以及数据
    3. 接收时则是直接通过socket读取到该类然后将QByteArray的二进制转换成QString即可(QString::fromUtf8不过也可以直接接收因为QByteArray和utf8本质都是二进制))
  2. QUdpSocket 文件类:一个类在客户端和服务端都使用到了
    1. 通过这个类完成对消息的读写操作,并通过QNetWorkDatagram完成消息传递
    2. 服务端中还需要通过socket绑定bind服务器的ip和port信息
    3. 客户端则是自己通过QNetWorkDatagram指定发送服务端信息即可
  3. &QUdpSocket::readyRead 消息准备就绪的信号
    1. 通过这个信号连接槽函数,完成对UDP数据的处理
    2. 注意一定要先连接槽函数在绑定端口号:
  4. .pro 中要添加 network

2. TcpSocket🧋

其中Tcp和Udp的区别这里就不具体提及了~
核心 API 概览,核心类是两个:
QTcpServerQTcpSocket 分别作用于服务端和客户端(其中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回显服务器🍕

  1. 创建Widget项目(.pro 中添加 network)
  2. ui文件中拖拽一个ListWidget
  3. 添加QTcpServer类成员变量句柄
  4. 构造函数中:
    1. 修改窗口标题 服务器
    2. 创建QTcpServer的实例,并挂在对象树上
    3. 创建 newConnection 信号的信号槽processConnection
      1. 通过nextPendingConnection获取一个客户端的socket(clientSocket 变量)
        1. 然后通过clientSocket 获取 :ip地址和端口(peerAddresspeerPort 对端客户端的ip和端口)
        2. 其中端口需要转换成整数使用QString::number + “客户端上线” 的信息
        3. 并且在ListWidget中添加子项
      2. 处理客户端发来请求的情况,通过readyRead信号,lambda表达式:
        1. 获取客户端的消息 readAll,获取request请求报文
        2. 根据请求处理响应 通过process函数完成:内部应该是业务逻辑,但此处只是回显,所以直接返回请求的QString即可,得到response请求报文
        3. 把响应返回给客户端(write
        4. 把上述内容记录到日志中(这里就打印即可),日志信息:客户端ip、端口(改成整形)、request、response
      3. 处理客户端断开连接的情况
        1. 通过disconnect信号,lambda表达式:
        2. 把断开连接的信息通过日志显示:客户端ip + 端口 + 客户端已下线
        3. 存放到ListWidget中
        4. 手动释放ClientSocket(这个东西是可能存在N个的,随着客户端越来越多不释放,积累起来就会导致 内存泄漏和文件描述符泄露)
        5. delete clientSocket(这样写时可以的不过必须要保证这是最后一步,因为这个lambda中都是围绕着clientSocket来进行的,并且也可能因为一些如异常或者return导致没有delete)所以这里还是不建议使用的
        6. 而是通过调用clientSocket的deleteLater(他不是立即销毁的,而是在下一轮事件循环中,再进行上述销毁操作)
    4. 绑定并监听端口号
      1. 一定要确保先写好处如何理连接,处理请求再绑定监听
      2. listen(监听接收任意ip,端口)
      3. 可能失败所以接收listen返回值进行判断 若失败则弹出对话框
      4. QMessageBos::critical this 服务器启动失败 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客户端的实现🍿

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


主要源码:

在这里插入图片描述
//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总结🔥🔥🔥

  1. 服务器中 QTcpServer::newConnection 处理新链接的信号,注意需要连接从而完成新链接新用户的消息处理和断开连接处理
  2. nextPendingConnection() 从就绪的连接中获取一个,从而拿到一个客户端(QTcpSocket *)
  3. QTcpSocket常用的函数:
    1. peerAddress(获取地址)、peerPort(端口)
    2. readAll(获取socket中的数据)
    3. write(通过socket发送消息数据:客户端需要设置3点、而服务端中它拿到的socket是已经确定好客户端的),socket本质其实就是{ip:port}找到另外一方
    4. connectToHost(客户端中使用:确定连接的服务端,从而确定消息发送位置)
  4. QTcpSocket::readyRead,同前面客户端消息就绪的信号
  5. 注意QT信号槽中使用lambda时不要引用(&)获取而是值(=)获取 注意!!!!
  6. QTcpSocket::disconnected 用户断开时会触发的信号

总之:

  1. TcpServer是服务器用于获取客户端以及处理客户端新链接、新消息、断开的类
  2. TcpSocket是一个中间件完成消息的传递,服务端可以通过nextPendingConnection获取一个连接(用户客户端),而客户端则是直接创建一个TcpSocket并且设置要发送的服务端

3. HTTP Client 🥞

Qt中也是提供了Http的客户端,Http协议本质上也就是基于TCP协议实现的,实现一个HTTP客户端/服务器,本质上也是基于Tcp Socket 进行封装的

核⼼ API
关键类主要是三个:

  1. QNetworkAccessManager
  2. QNetworkRequest
  3. 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)

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

– 注意最后这里访问的是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总结🔥🔥🔥

  1. QNetworkRequest构造一个http请求,初始化填入url代表路径
  2. 这里url需要的类型是QUrl但也很好转换QString能直接转换
  3. QNetworkAccessManager中的get/post(本质就是http的method)函数进行发送请求和并且接收响应QNetworkReply,但需要注意的是响应处不是阻塞的
  4. 所以需要设置一个信号槽,信号是QNetworkReply::finished当数据到达时就会触发
  5. 当QNetworkReply的error函数和QNetworkReply::NoError相等就代表没有问题此时就能在继续处理http响应了

本章完。预知后事如何,暂听下回分解。

如果有任何问题欢迎讨论哈!

如果觉得这篇文章对你有所帮助的话点点赞吧!

持续更新大量C++ Qt细致内容,早关注不迷路。

Read more

10分钟打造专属AI助手!ToDesk云电脑/顺网云/海马云操作DeepSeek哪家强?

10分钟打造专属AI助手!ToDesk云电脑/顺网云/海马云操作DeepSeek哪家强?

文章目录 * 一、引言 * 云计算平台概览 * ToDesk云电脑:随时随地用上高性能电脑 * 二 .云电脑初体验 * DeekSeek介绍 * 版本参数与特点 * 任务类型表现 * 1、ToDesk云电脑 * 2、顺网云电脑 * 3、海马云电脑 * 三、DeekSeek本地化实操和AIGC应用 * 1. ToDesk云电脑 * 2. 海马云电脑 * 3、顺网云电脑 * 四、结语 * 总结:云电脑如何选择? 一、引言 DeepSeek这些大模型让 AI 开发变得越来越有趣,但真要跑起来,可没那么简单! * 本地配置太麻烦:显卡不够、驱动难装、环境冲突,光是折腾这些就让人心态崩了。 * 云端性能参差不齐:选错云电脑,可能卡到爆、加载慢,还容易掉线,搞得效率直线下降。 * 成本难控:有的平台按小时计费,价格一会儿一个样,

By Ne0inhk
用 DeepSeek 打造你的超强代码助手

用 DeepSeek 打造你的超强代码助手

DeepSeek Engineer 是啥? 简单来说,DeepSeek Engineer 是一个基于命令行的智能助手。它能帮你完成这些事: * 快速读文件内容:比如你有个配置文件,直接用命令把它加载进助手,后续所有操作都可以基于这个文件。 * 自动改文件:它不仅能提建议,还可以直接生成差异表(diff),甚至自动应用修改。 * 智能代码生成:比如你让它生成代码片段,它会按照指定格式和规则直接返回。 更重要的是,这一切都是通过 DeepSeek 的强大 API 来实现的。想象一下,你有个贴身助手,不仅能听懂你的代码需求,还能直接动手帮你写! 核心功能拆解 我们先来看 DeepSeek Engineer 的几个核心能力,让你更好地理解它的强大之处。 1. 自动配置 DeepSeek 客户端 启动这个工具时,你只需要准备一个 .env 文件,里面写上你的 API Key,比如: DEEPSEEK_API_

By Ne0inhk
解锁DeepSeek潜能:Docker+Ollama打造本地大模型部署新范式

解锁DeepSeek潜能:Docker+Ollama打造本地大模型部署新范式

🐇明明跟你说过:个人主页 🏅个人专栏:《深度探秘:AI界的007》 🏅 🔖行路有良友,便是天堂🔖 目录 一、引言 1、什么是Docker 2、什么是Ollama 二、准备工作 1、操作系统 2、镜像准备 三、安装 1、安装Docker 2、启动Ollama 3、拉取Deepseek大模型 4、启动Deepseek  一、引言 1、什么是Docker Docker:就像一个“打包好的App” 想象一下,你写了一个很棒的程序,在自己的电脑上运行得很好。但当你把它发给别人,可能会遇到各种问题: * “这个软件需要 Python 3.8,但我只有 Python 3.6!

By Ne0inhk
深挖 DeepSeek 隐藏玩法·智能炼金术2.0版本

深挖 DeepSeek 隐藏玩法·智能炼金术2.0版本

前引:屏幕前的你还在AI智能搜索框这样搜索吗?“这道题怎么写”“苹果为什么红”“怎么不被发现翘课” ,。看到此篇文章的小伙伴们!请准备好你的思维魔杖,开启【霍格沃茨模式】,看我如何更新秘密的【知识炼金术】,我们一起来解锁更加刺激的剧情!友情提醒:《《《前方高能》》》 目录 在哪使用DeepSeek 如何对提需求  隐藏玩法总结 几个高阶提示词 职场打工人 自媒体创作 电商实战 程序员开挂 非适用场地 “服务器繁忙”如何解决 (1)硅基流动平台 (2)Chatbox + API集成方案 (3)各大云平台 搭建个人知识库 前置准备 下载安装AnythingLLM 选择DeepSeek作为AI提供商 创作工作区 导入文档 编辑  编辑 小编寄语 ——————————————————————————————————————————— 在哪使用DeepSeek 我们解锁剧情前,肯定要知道在哪用DeepSeek!咯,为了照顾一些萌新朋友,它的下载方式我放在下面了,拿走不谢!  (1)

By Ne0inhk