Qt项目(4)Qt实现HTTP请求案例

在这里插入图片描述

目标

本文将完成利用qt程序发出http请求,请求一个网络资源,并下载到本地。

1.介绍计算机网络相关概念

1.1数据传输过程

以在浏览器中输入淘宝网为例,介绍数据在互联网是如何传输的。我们将要发送的数据包称作A(无论经历了怎么的编码都叫做A)。

  • 1.在发送数据之前,主机应该有自己的MAC地址和IP地址才具备发送的条件,每一块网卡上都有一个唯一的MAC地址,IP地址使用DHCP动态主机配置协议统一管理。
  • 2.打开浏览器,输入淘宝网址(又称域名),在浏览器缓存中查找是否保存有这个域名对应的IP地址,如果没有。将采用DNS(域名解析协议)解析ip地址,具体过程为:网络客户端PC输入www.baidu.com问本地DNS服务器,该网址的ip地址是多少,本地服务器查看缓存列表,缓存中没有就去问根(13台)DNS服务器,根回复去问.com区域管理,.com回应,由163.com管理,由163.com服务器返回ip地址,完成域名解析。
  • 3.已知对端IP之后,此时由浏览器(应用层)打包http协议找到传输层的对应端口,传输层使用UDP、TCP之一打包到网络层(UDP面向报文主要用于高速数据的实时传输,TCP面向连接,区别于UDP最大的特点是在传输数据之前先要建立连接(这是数据之外的开销,就是常说的三次握手),传输结束后要结束连接(四次挥手),而UDP可能随时的发送报文)。此时A在主机中的网络层。
  • 4.互联网分三个区域,接入层(用户主机所在一级,或大或小的局域网),汇聚层(运营商管理的交互机与数据链路),核心网。A要通过自己所处的局域网必须经过该局域网的路由器(指的是实验室多台主机共用的那个路由器,或者家里的路由器),路由器去寻找接入层与汇聚层之间的交换机。那么此时A应该打包MAC头,到达数据链路层,此时数据在向前传输首先应该知道该路由器的MAC地址,用到了ARP地址解析协议(广播ARP报文),已知对端的ip地址,获取对端的MAC地址,已知对端MAC之后,A打包MAC头,在以太网中传输到路由器上。注:以太网中传输是用到MAC地址的,而以太网是局域网的组网协议(类似的协议还有WLAN!),也就是说对于发送端,MAC地址或者说以太网协议,仅在本地主机到那个直连的路由器之间应用。
  • 5.A到达路由器之后,将寻找最短路径(路由)找对端,将采用到RIP路由信息协议和OSFP开放式最短路径优先协议找下一跳的路径,当A到达核心网中时,由于AS域自治,可能还需使用AS之间的边界网关协议BGP,最终A到达淘宝服务器。
  • 6.淘宝服务器响应A的请求,返回数据将经历A来时经历的动作到达A。这种交互方式称之为C/S交互,常见的如访问网址;与之对应的另一种交互方式是P2P,例如微信聊天(发消息不去找服务器,而是找某个客户端)。

1.2网络协议

在这里插入图片描述

1.3互联网中的设备

  • 网卡是工作在链路层的网络组件,是局域网中连接计算机和传输介质的接口,以前通过插槽接入主板,现在大多做在一起了。网络接口板又称为通信适配器或网络适配器(network adapter)或网络接口卡NIC(Network Interface Card),但是更多的人愿意使用更为简单的名称“网卡”。
  • 中继器 是物理层延长设备,中继器从一个网络电缆里接收信号, 放大它们,将其送入下一个电缆。(现已被淘汰)
  • 集线器又叫Hub,是一种用于“星形”网络组织的中心设备。它具备中继器的特点,端口比中继器更密集,因此又把集线器叫做端口更多的中继器。集线器是一种半双工(同一时间只能接收或发送数据,不能同时既接受又发送数据)。例如1号接口接收数据,2.3.4端口都能收到信息,广播方式传输,具有安全风险。
  • 网桥(2层交互机)是一种对帧进行转发的技术,根据MAC分区块(数据链路层)。网桥将网络的同一网段在数据链路层连接起来,只能连接同构网络(同一网段),不能连接异构网络(不同网段)。(这是因为他没有网络层,不能区分网段)它能将一个大的LAN分割为多个网段,或将两个以上的LAN互联为一个逻辑LAN。
  • 路由器(3层交换机)负责网络互联、路由选择、分组转发(数据处理)、协议转换(如左边局域网使用以太网协议,右边网络使用tcp/ip协议)等功能。
  • 网关(Gateway)又称网间连接器、协议转换器,由于历史的原因,许多有关TCP/IP的文献曾经把网络层使用的路由器称为网关,在今天很多局域网采用都是路由来接入网络,因此通常指的网关就是路由器的IP。采用DHCP自动获得默认网关,另外一种自动获得网关的办法是通过安装代理服务器软件(如MS Proxy)的客户端程序来自动获得,其原理和方法和DHCP有相似之处。

2.QT实现HTTP请求

2.1 network模块

Qt Network模块用来编写基于TCP/IP的网络程序,其中提供了较低层次的类,比如QTcpSocket、QTcpServer、QUdpSocket等,来表示低层次的网络概念;还有高层次的类,比如QNetworkRequest、QNetworkReply和QNetworkAccessManager,使用通用的协议来执行网络操作。Qt的网络模块是一个庞大的体系,还有网络代理类QNetworkProxy类,承载管理QNetworkConfigurationManager类、QNetworkSession类以及涉及通信安全的QSsl等。需要注意使用network模块,须在在pro文件中添加network模块。

2.2 网络访问接口

网络访问接口是一组执行常见的网络操作的类的集合。该接口在特定的操作和协议(例如,http)上提供了一个抽象层,开发者只需要使用其提供的类、函数和信号即可完成操作,而不需要知道底层是如何实现的。 网络请求是由QNetworkRequest类来表示,它也作为与请求有关的信息的容器(也就是一个集合了与“请求”相关属性及方法的类对象)。QNetworkAccessManager类用来协调网络操作,可以调度创建好的请求,并发射信号来报告进度。(也是一个类对象,表征的是网络操作)网络请求的应答使用QNetworkReply类表示,他会在请求调度完成时由QNetworkAccessManager创建。QNetworkReply提供的信号可以用来单独监视每一个应答,也就是这个类对象表征的应答的信息。

2.3 步骤

由HTTP客户端(qt程序)发起一个请求,建立一个到服务器制定端口的TCP连接(默认80端口);HTTP服务器在指定的端口监听客户端发送过来的请求,一旦收到请求,服务器端就会向客户端发回一个应答。在程序中,使用get()函数发出请求,返回一个QNetworkReply对象管理应答,当数据来临时,产生readyread信号;过程中出错会产生error信号;下载进度更新时会产生process信号;处理结束会产生finish信号。因此,发出请求,接收应答对象,关联相关信号槽,在槽内做处理即可。出错或者结束,打印告知用户,或者对话框;新数据来就往文件里面写;进度更新就去更新进度条

2.4 代码

.h文件

#ifndef WIDGET_H
#define WIDGET_H
#include <QWidget>
#include <QNetworkAccessManager>
#include <QNetworkReply>
#include <QNetworkRequest>
#include <QDebug>
#include <QTextCodec>
#include <QFile>
QT_BEGIN_NAMESPACE
namespace Ui { class Widget; }
QT_END_NAMESPACE
class Widget : public QWidget
{
    Q_OBJECT
public:
    Widget(QWidget *parent = nullptr);
    ~Widget();
private:
    Ui::Widget *ui;
    void Init();
    QNetworkAccessManager *manager;
    QNetworkReply *reply;
    QFile *myFile;
private slots:
    void doProcessReadyRead();
    void doProcessError(QNetworkReply::NetworkError);
    void doProcessFinished();
    void doProcessDownload(qint64,qint64);
    void on_getBtn_clicked();
};
#endif // WIDGET_H

.cpp文件

#include "widget.h"
#include "ui_widget.h"
Widget::Widget(QWidget *parent)
    : QWidget(parent)
    , ui(new Ui::Widget)
{
    ui->setupUi(this);
    Init();
}
Widget::~Widget()
{
    delete ui;
}
void Widget::Init()
{
    //为全局变量分配空间,记得调用哦!
    manager = new QNetworkAccessManager(this);
    myFile = new QFile(this);
}
void Widget::doProcessReadyRead()
{
    //读取应答数据,并且写入文件中
    while(!reply->atEnd()){
        QByteArray ba = reply->readAll();
        myFile->write(ba);
    }
}
void Widget::doProcessError(QNetworkReply::NetworkError error)
{
    //打印请求过程中的错误,具体情况帮助手册有枚举
    qDebug()<<reply->errorString();
    switch ((int)error) {
    case QNetworkReply::ConnectionRefusedError:
            qDebug()<<"QNetworkReply::ConnectionRefusedError";
        break;
    case QNetworkReply::RemoteHostClosedError:
            qDebug()<<"QNetworkReply::RemoteHostClosedError";
    default:qDebug()<<error;
        break;
    }
}
void Widget::doProcessFinished()
{
    qDebug()<<"接收数据完毕!";
    myFile->close();//强制刷新
}
void Widget::doProcessDownload(qint64 a, qint64 b)
{
    //进度条的显示
    ui->progressBar->setMaximum(b);//all total
    ui->progressBar->setValue(a);//recv_total
}
void Widget::on_getBtn_clicked()
{
    QNetworkRequest req;
    req.setRawHeader(QByteArray("User-Agent"),QByteArray("MyOwnBrowser 1.0"));//设置http包的请求头
    QString url_str = ui->lineEdit->text();
    QUrl url;
    url.setUrl(url_str);//获取用户输入的url
    req.setUrl(url);
//  if(manager->networkAccessible() == QNetworkAccessManager::NotAccessible){
//     manager->setNetworkAccessible(QNetworkAccessManager::Accessible);
//  }
    reply = manager->get(req);//发送http get请求
    connect(reply,SIGNAL(readyRead()),this,SLOT(doProcessReadyRead()));//数据来临的信号,IO操作大多数都是这个信号
    connect(reply,SIGNAL(error(QNetworkReply::NetworkError)),this,SLOT(doProcessError(QNetworkReply::NetworkError)));
    connect(reply,SIGNAL(finished()),this,SLOT(doProcessFinished()));//传输(一次应答)完成
    //会将总文件长度,和当前文件传输长度反馈到参数中,正适合做进度条
    connect(reply,SIGNAL(downloadProgress(qint64,qint64)),this,SLOT(doProcessDownload(qint64,qint64)));

    QStringList list = url_str.split("/");
    QString filename = list.at(list.length()-1);
    myFile->setFileName(filename);
    bool ret = myFile->open(QIODevice::WriteOnly|QIODevice::Truncate);//以重写的方式打开,在写入新的数据时会将原有
                                                                        //数据全部清除,游标设置在文件开头。
    ui->progressBar->setValue(0);
    ui->progressBar->setMinimum(0);
}

main文件

#include "widget.h"
#include <QApplication>
int main(int argc, char *argv[])
{
    QApplication a(argc, argv);
    Widget w;
    w.show();
    return a.exec();
}

ui文件

见效果部分。

3.效果与总结

可以看到,请求了一张图片,jpg格式。进度条显示完毕之后,在本地目录中打开下载文件,如下,证明试验成功。
在这里插入图片描述
总结

  • 通过supportedSchemes();方法查看QNetworkAccessManager类支持的协议。
  • QT报错QNetworkReply::UnknownNetworkError,在网络中查询答案,未果,最终确定由于本地特殊网络环境的原因,因此当出现这个问题的时候,考虑关闭防火墙,关闭杀毒软件,关闭与网络相关的应用进程。多从本地配置的角度考虑,因为qt对于network的支持,尤其是这种简单http请求的支持应该是比较完善了。
  • 在与网络有关的程序的调试时,有一个抓包软件叫做wireshark,对理论理解大有帮助。链接在文末。
  • 遇到错误qt.network.ssl: QSslSocket::connectToHostEncrypted: TLS initialization failed时,去openssl官网下载Win64 OpenSSL v3.0.2 Light工具。具体下载那个版本,应在程序中添加以下代码查询:
#include <QSslSocket>
qDebug()<<QSslSocket::sslLibraryBuildVersionString();

可以看到我的程序中的版本。
在这里插入图片描述

安装过程中要选拷贝到其他目录。安装好后在安装目录找到 libcrypto-1_1-x64.dll libssl-1_1-x64.dll 两个文件 ,然后粘贴到qt安装目录X:\Qt\Qt5.15.2\5.15.2\msvc2019_64\bin目录再次运行,错误消失,完美解决。

OpenSSL是干什么的?在计算机网络上,OpenSSL是一个开放源代码的软件库包,应用程序可以使用这个包来进行安全通信,避免窃听,同时确认另一端连接者的身份。这个包广泛被应用在互联网的网页服务器上。SSL是Secure Sockets Layer(安全套接层协议)的缩写,可以在Internet上提供秘密性传输。Netscape公司在推出第一个Web浏览器的同时,提出了SSL协议标准。其目标是保证两个应用间通信的保密性和可靠性,可在服务器端和用户端同时实现支持。已经成为Internet上保密通讯的工业标准。

4.传送门

END

🎏文章原创,首发于CSDN论坛。
🎏欢迎点赞❤❤收藏⭐⭐打赏💴💴!
🎏欢迎评论区或私信指出错误❌,提出宝贵意见或疑问❓。


  • 2
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

瑾芳玉洁错过的烟火

原创不易,请多支持

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值