Qt使用QNetworkAssessManager下载文件

功能目录:

  1. QNetworkAccessManager接口介绍
  2. 下载进度条展示,支持暂停,停止功能
  3. 显示下载/剩余大小,剩余时间,下载速度
  4. 多线程下载,不阻塞界面线程
  5. 文件断点续传下载
  6. 下载请求超时的处理
  7. 功能实现Demo地址

1.QNetworkAccessManager接口介绍

    官方文档:http://doc.qt.io/archives/qt-5.8/qnetworkaccessmanager.html

    可以一目了然的看到几个熟悉词汇的api:post、get、put、head,当然还有几个cookie相关的方法。

    可以发现使用manager还需要几个类:QNetworkRequest 专门用于请求的,QNetworkReply 接收请求的响应.

     比如1.只想从网页服务器上请求数据,使用get方法。         

        QNetworkAccessManager *manager = new QNetworkAccessManager(this);
        connect(manager, SIGNAL(finished(QNetworkReply*)),
                this, SLOT(replyFinished(QNetworkReply*)));
        QNetworkRequest request;
        request.setUrl(QUrl("http://qt-project.org"));
       
        reply = manager->get(request);
        connect(reply, &QNetworkReply::finished, this, &MainWindow::finished);

        //在finished函数中
        QByteArray bytes = reply->readAll();//读取网页数据

      2.附带数据向服务器请求返回数据,使用post,当post附带json数据时:

    QNetworkAccessManager *manager = new QNetworkAccessManager(this);
    QNetworkRequest request;

    //构造json数据
    QJsonObject jsonObject;
    jsonObject.insert("dcode", "code");
    jsonObject.insert("ver", "version");

    //json转QByteArray
    QString strJson = QString(QJsonDocument(jsonObject).toJson(QJsonDocument::Compact));
    QByteArray byte_json = strJson.toLocal8Bit();
    
    request.setUrl(QUrl("http://qt-project.org"));//要请求的地址
    //如果请求json数据,请求header要使用此设置
    request.setHeader(QNetworkRequest::ContentTypeHeader, QVariant("application/json"));
    
    if (m_Reply != Q_NULLPTR) {
		m_Reply->deleteLater();
	}
	m_Reply = manager->post(request, byte_json);
    QEventLoop loop;//非阻塞方式,采用事件循环
	QTimer timer;
	connect(m_Reply, SIGNAL(finished()), &loop, SLOT(quit()));
	QObject::connect(&timer, SIGNAL(timeout()), &loop, SLOT(quit()));
	timer.start(5000);//设置超时5s
	loop.exec(QEventLoop::ExcludeUserInputEvents);

	if (m_Reply->error() == QNetworkReply::NoError){
        QByteArray  strJsonText = m_Reply->readAll();//请求返回数据
    }

    1.1. QNetworkRequest

    同样看帮助文档:http://doc.qt.io/qt-5/qnetworkrequest.html

    主要就是这几个写方法,分别对一个请求的不同类进行配置。

            客户端发送一个HTTP请求到服务器的请求消息包括以下格式:请求行(request line)、请求头部(header)、空行和请     求数据四个部分组成,下图给出了请求报文的一般格式。请求行组成:请求方法+空格+url+空格+协议版本+回车符+换行符。       详情见HTTP 消息结构
     对于header,qt提供了一个枚举类型KnownHeaders分别表示不同项:

Constant Value Description
QNetworkRequest::ContentDispositionHeader 6 Corresponds to the HTTP Content-Disposition header and contains a string containing the disposition type (for instance, attachment) and a parameter (for instance, filename).
QNetworkRequest::ContentTypeHeader 0 Corresponds to the HTTP Content-Type header and contains a string containing the media (MIME) type and any auxiliary data (for instance, charset).
QNetworkRequest::ContentLengthHeader 1 Corresponds to the HTTP Content-Length header and contains the length in bytes of the data transmitted.
QNetworkRequest::LocationHeader 2 Corresponds to the HTTP Location header and contains a URL representing the actual location of the data, including the destination URL in case of redirections.
QNetworkRequest::LastModifiedHeader 3 Corresponds to the HTTP Last-Modified header and contains a QDateTime representing the last modification date of the contents.
QNetworkRequest::CookieHeader 4 Corresponds to the HTTP Cookie header and contains a QList representing the cookies to be sent back to the server.
QNetworkRequest::SetCookieHeader 5 Corresponds to the HTTP Set-Cookie header and contains a QList representing the cookies sent by the server to be stored locally.
QNetworkRequest::UserAgentHeader 7 The User-Agent header sent by HTTP clients.
QNetworkRequest::ServerHeader 8 The Server header received by HTTP clients.

1.2. QNetworkReply

    帮助文档:http://doc.qt.io/qt-5/qnetworkreply.html

    此类继承自QIODevice,可使用QIODevice的所有接口,包括readall读取接收的所有信息。

    同时此类提供了finished信号,在响应完斥候发出此信号,可关联自定义槽函数函数,做响应处理。

    提供了attribute属性函数,可以判断响应的类型,比如RedirectionTargetAttribute是目标url告知进行重定向

    QNetworkReply不会自动释放空间,一定要主动处理内存释放,可以调用QObject::deleteLater令其自动释放空间

connect(m_reply, SIGNAL(downloadProgress(qint64,qint64)), this, SLOT(onDownloadProgress(qint64, qint64)));
connect(m_reply, SIGNAL(readyRead()), this, SLOT(onReadyRead()));
connect(m_reply, SIGNAL(finished()), this, SLOT(onFinished()));
connect(m_reply, SIGNAL(error(QNetworkReply::NetworkError)), this, SLOT(onError(QNetworkReply::NetworkError)));

如果请求下载功能:

onDownloadProgress(qint64, qint64);显示的就是下载的进度,参数1和2分别代表本次下载字节数和文件总字节大小。

onReadyRead();下载过程中开始从服务器读取数据,用于读取数据写入本地文件中,采用数据追加的方式方便断点的续传:          

    void xx::onReadyRead()
    {
        if (m_reply == NULL) return;
        QFile file(m_fileName);//本地文件名
        if (file.open(QIODevice::WriteOnly | QIODevice::Append))
        {
            file.write(m_reply->readAll());
        }
        file.close();
    }

onFinished():下载完成事件。

onError():下载错误事件

2.下载进度条展示,支持暂停,停止功能
    
    QNetworkAccessManager使用get方法发送请求,使用QNetworkReply接受返回。QNetworkReply使用下载进程信号来反馈每次下载数据的大小和文件的总大小。
    2.1    readyRead()
        这是QNetworkReply的父类QIODevice提供的下载过程中的信号,可以在此信号中写入下载文件大小。
    2.2 downloadProgress(qint64 bytesReceived, qint64 bytesTotal) 
        bytesReceived代表每次下载文件字节大小,bytesTotal代表下载文件字节总大小。                    

//更新进度条;  
QString sValue = QString("%1").arg(bytesReceived * 100 / (bytesTotal));//百分比,所以乘以100
ui.progressBar->setValue(sValue.toInt());
        


    2.3 暂停和停止功能要点:需要解绑QNetworkReply的信号和槽,并调用QNetworkReply的abort方法和DeleteLater()方法。

 disconnect(m_reply, SIGNAL(downloadProgress(qint64, qint64)), this, SLOT(onDownloadProgress(qint64, qint64)));
 disconnect(m_reply, SIGNAL(readyRead()), this, SLOT(onReadyRead()));
 disconnect(m_reply, SIGNAL(finished()), this, SLOT(onFinished()));
 disconnect(m_reply, SIGNAL(error(QNetworkReply::NetworkError)), this, SLOT(onError(QNetworkReply::NetworkError)));
 m_reply->abort();
 m_reply->deleteLater();
 m_reply = NULL;

    

3.显示下载/剩余大小,剩余时间,下载速度
        计算剩余大小,剩余时间和下载速度,需要根据下载过程中每次下载字节数和总下载字节数计算,所以还是在downloadProgress(qint64 bytesReceived, qint64 bytesTotal)
        响应的槽函数中来计算。

        // 下载大小MB/总大小MB
        QString strDownCurrent = QStringLiteral("下载大小:%1/%2").arg(transformUnit(m_currentDownload)).arg(transformUnit(bytesTotal));
        ui.lb_size->setText(strDownCurrent);

        //剩余时间
        qint64 ispeed		= m_intervalDownload * 1000 / (timeNow - m_timeInterval);
        qint64 timeRemain	= (bytesTotal - bytesReceived) / ispeed;
        QString strRemaind	= QStringLiteral("剩余时间:%1").arg(transformTime(timeRemain));
        ui.lb_remaind->setText(strRemaind);

        //下载速度
        QString strSpeed	= QStringLiteral("下载速度:%1").arg(transformUnit(ispeed, true));
        ui.lb_rate->setText(strSpeed);

4.多线程下载,不阻塞界面线程        
        此处有两种方法,一种是通过MoveToThread方法,通过一个继承与QObject的功能类实现。
        二是继承QThread,通过QThread来实现多线程,我使用的是第二种,提供过一个继承与QThread的中间类。
        当点击下载按钮时,启动下载线程:

        m_strTargetAddress  = "d:/Qt_Download.zip";//自定义目标下载地址
        QString strUrl	    = ui.lineEdit->text();//服务器下载地址

        if (m_pDownloadThread != NULL )
        {
            m_pDownloadThread->start();
        }

5.文件断点续传下载
        5.1关于断点续传,QNetworkRequest提供了方法SetRawHeader和"Range"字段来设置请求的字节大小和宽度。
        在HTTP协议请求中,如果想从文件的某一位置接受数据,就要加上Range头部,Range头部的格式有如下几种情况:
        表示头500个字节:bytes=0-499  
        表示第二个500字节:bytes=500-999  
        表示最后500个字节:bytes=-500  
        表示500字节以后的范围:bytes=500-  
        第一个和最后一个字节:bytes=0-0,-1  
        同时指定几个范围:bytes=500-600,601-999

        在发出带Range的请求后,服务器会在Content-Range头部返回当前接受的范围和文件总大小,如:

        Content-Range: 0-499/22400

        这里0-499是指当前发送的数据的范围,而22400则是文件的总大小。


        5.2要实现断点续传,只有客户端是不行的,服务器上的文件也必须支持断点续传,也就是说,在进行断点下载续传时,首先要判断要下载文件是否支持断点下载。
            windows下如何判断支持要下载文件支持断点下载:
            5.2.1 cmd下使用curl 命令加 -i 参数 和要请求的字节大小

                eg: curl -i --range 0-9 http://sqdownb.onlinedown.net/down/55412_20161201034403.zip
                如返回数据中包含Content-Range和 HTTP/1.1 206 Partial Content字节,例如执行上述命令时,返回结果如下:

    HTTP/1.1 206 Partial Content
    Date: Wed, 01 Aug 2018 03:12:14 GMT
    Content-Type: application/zip
    Content-Length: 10
    Last-Modified: Wed, 07 Dec 2016 10:13:32 GMT
    Connection: keep-alive
    ETag: "5847e0cc-78ac98"
    Content-Range: bytes 0-9/7908504

           处Content-Range: bytes 0-9/7908504 返回的字节数7908504就是文件的总字节大小。

            HTTP/1.1 206 Partial Content     代表状态值206 代表支持断点下载
           所以此处返回带有Content-Range和HTTP/1.1 206 Partial Content字节,代表此链接是支持断点下载的。

          5.2.2 Qt代码中通过QNetworkRequest发送请求"Range",通过返回值QNetworkReply的rawHeader("Content-Range")是否存在返回值来查看。

bool xx::bSupportBreak(const QUrl &url)
{
	QNetworkAccessManager manager;
	QEventLoop loop;
	QTimer timer;

	QNetworkRequest request;
	request.setUrl(url);
	request.setRawHeader("Range", "bytes=0-9");

	//发出请求,获取文件地址的头部信息;
	QNetworkReply *reply = manager.get(request);
	if (!reply)		return false;

	connect(reply, SIGNAL(finished()), &loop, SLOT(quit()));
	connect(&timer, SIGNAL(timeout()), &loop, SLOT(quit()));

	timer.start(2000);
	loop.exec(QEventLoop::ExcludeUserInputEvents);

	if (reply->error() != QNetworkReply::NoError)
	{
		// 请求发生错误;
		qDebug() << reply->errorString();
		return false;
	}
	else if (!timer.isActive())
	{
		// 请求超时超时,未获取到文件信息;
		qDebug() << "Request Timeout";
		return false;
	}
	timer.stop();

	QByteArray range = reply->rawHeader("Content-Range");
	if (!QString::fromLocal8Bit(range).isEmpty())
		return true;

	return false;
}

6.下载请求超时的处理                 
    Qt5.8中不管是QNetworkAccessManager类还是QNetworkRequest类没有提供超时的机制,所以只能通过自己来计算。
        在开始下载时,通过定时器,每5s检测下载字节发生的变化值。        

_timeOut->start(5000);

        如果5s内没有发生变化,则默认说明超时,超时的原因有很多,下载过程中网络断开,或者网络连接不稳定,网卡被禁用掉等。通过定时器超时事件一直循环检测是否有网络链接,如果有网络连接则继续下载,反之下载暂停。这样的好处是下载没完成之前,网络出现波动不会影响整体文件的下载,通过轮询下载,一直到文件下载完成。
        
        不过此处我为了不让程序无节制的请求连接,如果断网超过30分钟就让程序断开请求,不再下载。        

void xx::handleTimeOut()
{
	if (m_bytesDown != m_bytesReceived) {
		m_bytesDown = m_bytesReceived;
	}
	else if (!m_bClickPause) {

		//网络断开时停止下载
		if (m_reply != NULL)
		{
			stopDownload();
			m_timeElapsed.start();
		}

		//30分钟循环超时(30分钟再次连接网络可以继续下载)
		int nElapsed = m_timeElapsed.elapsed();
		if (nElapsed >= 1 * 30 * 60 * 1000)
		{
			_timeOut->stop();
			return;
		}

		//检测网络
		bool bConnect = this->bSupportBreak(m_qStrUrl);
		if (bConnect)
		{
			_timeOut->stop();
			this->downloadFile(m_qStrUrl, m_qStrFilePath);
		}

	}
}

7.功能实现Demo地址

        环境是VS2015 + win10 64位。

        附带源码和可执行程序下载地址,同时还有编译成功的Openssl的32位和64位的文件。

        https://download.csdn.net/download/whanjim19/10576909

你可能感兴趣的:(Qt,C++)