客户端在线更新-QT
   
    
    
    实现思路
   
 最近在做客户端的时候,需要将客户端在线更新,所以整理一下在线更新的思路,希望对你有帮助。
 首先在线更新是利用了文件解压之后会自动替换的原理,因此具体需要做的就是从服务器上下载到在线更新的压缩包,之后在对下载到的压缩包进行解压缩替换现有程序内的文件即可。
 需要注意的是更新软件需要独立运行,且注意不能占用需要解压的部分,也就是说在更新时候需要将软件本体退出,如下图所示
     
   
 同样的,您可以将上图部分做成双向更新,就可以使用软件本体更新更新客户端,使用更新客户端在更新软件本体,从而达到不在下载全部软件的目的,但是在客户端和服务器端做好区分。
    
    
    实现方式
   
    
    
    1.解压部分
   
 解压我选择了使用Quazip,Quazip是Qt平台下面的一个压缩解压缩库,Quazip编译需要依赖zlib库。
    
    
    Quazip编译和简单的使用
   
 从官网下载Quazip源码压缩包,官网地址:http://quazip.sourceforge.net/在官网可以看到它支持的平台以及下载入口。
 首先进入Quazip官网已经将对应的源码指向了github:https://github.com/stachenov/quazip 你也可以选择到此处下载源码
Quazip的编译并不复杂,我这里用windows为例子对Quazip进行编译讲解。
- 首先将下载的源码解压之后在源码目录下建立一个bulid文件夹
     
   
- 打开CMake工具选择需要编译的文件夹和输出文件夹,选好之后点击configure,之后选择对应的平台和vs版本
     
   
- 
之后点击open,打开vs编译出对应的debug和release版本就可以啦   
- 
编译完成之后测试对应的代码 
#include <iostream>
#include <JlCompress.h>
// 压缩解压可以按照代码分开测试
int main()
{
    // JlCompress::compressFile("test.zip","test.txt"); //压缩
    JlCompress::extractFile("test.zip","111.txt");  //解压
    return 0;
}
    
    
    2.下载部分
   
 服务器方面使用的是http接口,因此QT这边选择使用QT自带的QNetworkAccessManager来做下载(当然也可以使用其他的C库去实现http接口,比如libevent(后续会单独拿出来做讲解,这边就先只介绍QNetworkAccessManager))。
NetworkAccess API是围绕一个QNetworkAccessManager对象构造的,该对象包含它发送的请求的公共配置和设置。它包含代理和缓存配置,以及与此类问题相关的信号,以及可用于监视网络运行进度的应答信号。
一旦创建了QNetworkAccessManager对象,应用程序就可以使用它通过网络发送请求。QNetworkAccessManager提供了一组标准函数,它们接受一个请求和可选数据,每个函数返回一个QNetworkReply对象。返回的对象用于获取响应相应请求而返回的任何数据。
QNetworkAccessManager有一个异步API。 当上面的replyFinished槽被调用时,它接受的参数是QNetworkReply对象,该对象包含下载的数据和元数据(头等)。
    
     注意:
    
    请求完成后,用户有责任在适当的时候删除QNetworkReply对象。不要直接在finished()连接的槽内删除它。你可以使用deleteLater()函数。
   
    
     注意:
    
    QNetworkAccessManager将它接收到的请求队列化。并行执行的请求数量取决于协议。目前,对于桌面平台的HTTP协议,一个主机/端口组合并行执行6个请求。
   
    
    
    代码展示:
   
download_manager.h
#ifndef UPDATE_DOWNLOAD_MANAGER_H
#define UPDATE_DOWNLOAD_MANAGER_H
#include <QObject>
#include <QNetworkAccessManager>
#include <QNetworkReply>
class DownloadManager : public QObject
{
    Q_OBJECT
public:
    explicit DownloadManager(QObject *parent = 0);
    ~DownloadManager() override;
    // 下载文件
    void downloadFile(const QString &url,const QString &fileName);
    // 设置是否支持断点续传
    void setDownInto(const bool &isSupportBreakPoint);
    // 停止下载
    void stopDownload();
    // 关闭下载
    void closeDownload();
    // 获取下载url
    QString getDownloadUrl();
    // 重置参数;
    void reset();
private:
    // 停止下载工作;
    void stopWork();
    // 删除文件;
    void removeFile(const QString &fileName);
signals:
    void signalDownloadProcess(qint64, qint64);
    void signalReplyFinished(int);
    void signalDownloadError();
private slots:
    void onDownloadProgress(qint64 bytesReceived, qint64 bytesTotal);
    void onReadyRead();
    void onFinished();
    void onError(QNetworkReply::NetworkError code);
private:
    QNetworkAccessManager *network_manager_{nullptr};
    QNetworkReply         *reply_{nullptr};
    QUrl                   url_{};
    QString                file_name_{};
    bool                   is_support_break_point_{false};
    qint64                 bytes_received_;
    qint64                 bytes_total_;
    qint64                 bytes_current_received_;
    bool                   is_stop_{true};
};
#endif //UPDATE_DOWNLOAD_MANAGER_H
download_manager.cpp
#include <QFile>
#include <QFileInfo>
#include <iostream>
#include "download_manager.h"
#define DOWNLOAD_FILE_SUFFIX "_tmp"
DownloadManager::DownloadManager(QObject *parent) : QObject(parent)
{
    network_manager_ = new QNetworkAccessManager(this);
    for(const auto &str : network_manager_->supportedSchemes())
        std::cout << str.toStdString() << std::endl;
}
DownloadManager::~DownloadManager()
{
}
void DownloadManager::downloadFile(const QString &url, const QString &fileName)
{
// 防止多次点击开始下载按钮,进行多次下载,只有在停止标志变量为true时才进行下载;
    if (is_stop_)
    {
        is_stop_ = false;
        url_ = QUrl(url);
        // 从url 中获取文件名,但不是都有效;
        // 将当前文件名设置为临时文件名,下载完成时修改回来;
        file_name_ = fileName + DOWNLOAD_FILE_SUFFIX;
        // 如果当前下载的字节数为0那么说明未下载过或者重新下载
        // 则需要检测本地是否存在之前下载的临时文件,如果有则删除
        if (bytes_current_received_ <= 0)
        {
            removeFile(file_name_);
        }
        QNetworkRequest request;
        request.setUrl(url_);
        // 如果支持断点续传,则设置请求头信息;
        if (is_support_break_point_)
        {
            QString strRange = QString("bytes=%1-").arg(bytes_current_received_);
            request.setRawHeader("Range", strRange.toLatin1());
        }
        reply_ = network_manager_->get(request);
        connect(reply_, SIGNAL(downloadProgress(qint64, qint64)), this, SLOT(onDownloadProgress(qint64, qint64)));
        connect(reply_, SIGNAL(readyRead()), this, SLOT(onReadyRead()));
        connect(reply_, SIGNAL(finished()), this, SLOT(onFinished()));
        connect(reply_, SIGNAL(error(QNetworkReply::NetworkError)), this, SLOT(onError(QNetworkReply::NetworkError)));
    }
}
void DownloadManager::setDownInto(const bool &isSupportBreakPoint)
{
    is_support_break_point_ = isSupportBreakPoint;
}
void DownloadManager::stopDownload()
{
    // 这里is_stop_变量为了保护多次点击暂停下载按钮,bytes_current_received_ 被不停累加;
    if (!is_stop_)
    {
        //记录当前已经下载字节数;
        bytes_current_received_ += bytes_received_;
        stopWork();
    }
}
void DownloadManager::closeDownload()
{
    stopWork();
    reset();
    removeFile(file_name_);
}
QString DownloadManager::getDownloadUrl()
{
    return url_.toString();
}
void DownloadManager::reset()
{
    bytes_current_received_ = 0;
    bytes_received_ = 0;
    bytes_total_ = 0;
}
void DownloadManager::stopWork()
{
    is_stop_ = true;
    if (reply_ != nullptr)
    {
        disconnect(reply_, SIGNAL(downloadProgress(qint64, qint64)), this, SLOT(onDownloadProgress(qint64, qint64)));
        disconnect(reply_, SIGNAL(readyRead()), this, SLOT(onReadyRead()));
        disconnect(reply_, SIGNAL(finished()), this, SLOT(onFinished()));
        disconnect(reply_, SIGNAL(error(QNetworkReply::NetworkError)), this, SLOT(onError(QNetworkReply::NetworkError)));
        reply_->abort();
        reply_->deleteLater();
        reply_ = nullptr;
    }
}
void DownloadManager::removeFile(const QString &fileName)
{
    // 删除已下载的临时文件;
    QFileInfo fileInfo(fileName);
    if (fileInfo.exists())
    {
        QFile::remove(fileName);
    }
}
void DownloadManager::onDownloadProgress(qint64 bytesReceived, qint64 bytesTotal)
{
    if (!is_stop_)
    {
        bytes_received_ = bytesReceived;
        bytes_total_ = bytesTotal;
        // 更新下载进度;(加上 bytes_current_received_ 是为了断点续传时之前下载的字节)
        emit signalDownloadProcess(bytes_received_ + bytes_current_received_, bytes_total_ + bytes_current_received_);
    }
}
void DownloadManager::onReadyRead()
{
    if (!is_stop_)
    {
        QFile file(file_name_);
        if (file.open(QIODevice::WriteOnly | QIODevice::Append))
        {
            file.write(reply_->readAll());
        }
        file.close();
    }
}
void DownloadManager::onFinished()
{
    is_stop_ = true;
    QVariant statusCode = reply_->attribute(QNetworkRequest::HttpStatusCodeAttribute);
    if (reply_->error() == QNetworkReply::NoError)
    {
        // 重命名临时文件;
        QFileInfo fileInfo(file_name_);
        if (fileInfo.exists())
        {
            int index = file_name_.lastIndexOf(DOWNLOAD_FILE_SUFFIX);
            QString realName = file_name_.left(index);
            QFile::rename(file_name_, realName);
        }
    }
    else
    {
        // 有错误输出错误;
        QString strError = reply_->errorString();
        std::cout << "__________" + strError.toStdString() << std::endl;
    }
    emit signalReplyFinished(statusCode.toInt());
}
void DownloadManager::onError(QNetworkReply::NetworkError code)
{
    QString strError = reply_->errorString();
    std::cout << "__________" + strError.toStdString() << std::endl;
    closeDownload();
    emit signalDownloadError();
}
main.cpp
#include <QApplication>
#include "download_manager.h"
int main(int argc, char *argv[])
{
    QApplication app(argc,argv);
    DownloadManager dd;
    dd.downloadFile("https://dl.hdslb.com/mobile/fixed/bili_win/bili_win-install.exe?v=1.6.1","aa.exe");
    return app.exec();
}
    
    
    项目示例
   
 好了,有了上面的基础部分,接下来我们就可以完成我们的整体部分的代码
 参考项目地址: https://gitee.com/turbolove/qt-update.git
    
    
     注意:
    
    项目有个进入主程序的功能,因此加了版本的判断,只有需要更新的时候才会显示更新。
   
- 
首先我们需要创建一个软件界面,项目widgets下文件是跟界面有关的内容 
- 
core内放了一些必要的线程类 
- 
http内放了专门做文件下载等http接口实现的类 
    
    
    关键代码:
   
版本判断,我这边比较简单,就是一个版本号字符串的解析和比较
bool MainWindow::checkNeedUpdate(const QString &version)
{
    if(version == m_version)
    {
        return false;
    }
    if(m_version.split(".").size() != version.split(".").size() || m_version.split(".").size() < 3)
    {
        return false;
    }
    auto strList1 = m_version.split(".");
    auto strList2 = version.split(".");
    for (int i = 0; i < strList1.size(); ++i)
    {
        int mVer = strList1[i].toInt();
        int newVer = strList2[i].toInt();
        if (mVer == newVer)
        {
            continue;
        }
        return newVer > mVer;
    }
    return false;
}
下载速度转换
// 转换单位;
QString MainWindow::transformUnit(qint64 bytes , bool isSpeed)
{
    QString strUnit = " B";
    if (bytes <= 0)
    {
        bytes = 0;
    }
    else if (bytes < UNIT_KB)
    {
    }
    else if (bytes < UNIT_MB)
    {
        bytes /= UNIT_KB;
        strUnit = " KB";
    }
    else if (bytes < UNIT_GB)
    {
        bytes /= UNIT_MB;
        strUnit = " MB";
    }
    else if (bytes > UNIT_GB)
    {
        bytes /= UNIT_GB;
        strUnit = " GB";
    }
    if (isSpeed)
    {
        strUnit += "/S";
    }
    return QString("%1%2").arg(bytes).arg(strUnit);
}
版本判断
void MainWindow::updateVersionCheck(const QJsonArray &array)
{
    m_list.clear();
    m_versionMap.clear();
    ui.listWidget->clear();
    for(const auto & ver : array)
    {
        QString version = ver.toObject()["name"].toString();
        QString filename = ver.toObject()["file_path"].toString();
        QString describe = ver.toObject()["describe"].toString();
        if(checkNeedUpdate(version))
        {
            m_list << version;
            m_versionMap[version] = QPair<QString,QString>(filename,describe);
        }
    }
    std::reverse(m_list.begin(),m_list.end());
	// 版本号获取顺序逆序插入列表
    for(int i = 0 ; i < m_list.size() ;i++)
    {
        ui.listWidget->addItem(m_list[i]);
    }
    if(m_list.size()>0)
    {
        ui.plainTextEdit->setPlainText(m_versionMap[m_list[0]].second);
        ui.btn_enter->setText("Enter(Update)");
    }
    else
    {
         ui.btn_enter->setText("Enter");
    }
}
 
