本系列笔记所记述的books项目设计并实现了程序自动升级功能,因此需要从网络上下载升级EXE文件,这涉及网络下载的相关功能。
信号和槽函数
针对网络方面的编程,Qt中只用几行代码就可以实现其他语言程序需要大量代码才能实现的功能,由此可见Qt在网络通信方面的优势。网络通信中重要且复杂的消息处理相关代码在Qt程序中仅用短短2行即可完成(connect函数),这2行代码实现了Qt的消息传递机制,即信号与槽函数。
本节中我仅针对与网络通信相关的3个比较常用的信号与槽关联函数进行讨论,代码如下:
connect(pReply, QNetworkReply::finished, this, myFinished);
connect(pReply, QNetworkReply::downloadProgress, this, myDownloadProgress);
connect(reply, SIGNAL(error(QNetworkReply::NetworkError)), this,
SLOT(slotError(QNetworkReply::NetworkError)));
- 第1行代码表示当网络通信任务完成(如下载完成)时发出
QNetworkReply::finished
信号,connect函数将这个信号与自定义的myFinished函数相关联,由myFinished函数处理通信任务完成后程序应该执行的功能(如将下载的数据保存到文件中)。 - 第2行代码处理的是下载进度,由自定义的myDownloadProgress实现。为保证用户友好性,在下载较大文件时,需要为用户提供下载进度。
QNetworkReply::downloadProgress
发出信号时,会传递出“当前传输数据量”和“应传输数据总量”。自定义的myDownloadProgress函数通过qint64 bytesReceived
和qint64 bytesTotal
参数向用户传递当前传输数据量和应传输数据总量。QNetworkReply::downloadProgress
并不实时发出信号,而是在系统较空闲的情况下发送,myDownloadProgress函数接收到信号和上述2个参数后做出相应处理。 - 第3行代码是错误处理函数。当网络通信发生错误时,系统发出
error(QNetworkReply::NetworkError)
信号,可在slotError(QNetworkReply::NetworkError)
函数中处理错误。
在上述代码中,有的connect函数有SIGNAL和SLOT宏,将信号和槽函数括起来,如
SIGNAL(error(QNetworkReply::NetworkError))
,但有的函数没有使用这些宏。Qt5之前要求必须使用SIGNAL和SLOT宏,但Qt5之后允许不加。实际上,SIGNAL和SLOT宏的定义为空,即程序中SIGNAL和SLOT宏不执行任何代码,只是一种标识,提供给编译器。
功能模块和类
使用上述类和函数就可以实现网络通信,完成文件下载的功能。但对books项目来说,需要下载的文件比较多,除了升级文件,还有升级版本控制文件、其他配置文件等。因此,需要制作一个通用的下载类,将网络下载功能代码打包成一体,主要代码如下:
// books.pro文件
Qt += core gui network
// UpdateByNetwork.h文件
class UpdateByNetwork : public QObject
{
Q_OBJECT
public:
UpdateByNetwork();
~UpdateByNetwork();
// ...
}
// UpdateByNetwork.cpp文件
void UpdateByNetwork::startDownload()
{
// 开始下载
bDownloaded = false;
QUrl url = QUrl(baseAddress + downloadFileName);
QString strAppDir = QCoreApplication::applicationFilePath();
strAppDir = strAppDir.left(strAppDir.lastIndexOf("/"));
QDir mydir(strAppDir); // = QDir::current();
mydir.mkdir(LOCALUPDATEDIR); // 将文件下载到指定目录
pFile = new QFile(LOCALUPDATEDIR + "/" + downloadFileName);
if(!pFile->open(QIODevice::WriteOnly | QIODevice::Truncate)){
qDebug() << "本地文件无法创建,无法下载文件。";
return;
}
pManager = new QNetworkAccessManager();
pReply = pManager->get(QNetworkRequest(url));
// 创建信号机制
connect(pReply, QNetworkReply::finished, this, UpdateByNetwork::myFinished);
return;
}
void UpdateByNetwork::myFinished()
{
if(pFile){
pFile->write(pReply->readAll());
pFile->flush();
pFile->close();
delete pFile;
pFile = nullptr;
}
pReply->deleteLater();
pReply = nullptr;
bDownloaded = true;
// QMessageBox::information(this, "完成", "本地下载完成");
}
生成UpdateByNetwork.h和UpdateByNetwork.cpp文件后,要在books.pro文件中添加“network”关键字。同时在UpdateByNetwork.cpp文件中要引用<QtNetwork>头文件,这样Qt程序才能正式使用网络服务功能。
生成自定义类时必须使用public QObject
派生,同时要在类定义的首行处添加Q_OBJECT
宏。这两处代码的主要目的是允许自定义类使用Qt提供的信号与槽功能。如果疏忽这一点,程序编译时就会出现非常奇怪的错误,这点需要提前注意。
在UpdateByNetwork::startDownload()
函数中,程序允许用户指定下载文件的具体目录和文件名,调用函数前应事先定义,然后在startDownload()
函数内部进行统一处理。函数UpdateByNetwork::myFinished()
处理了下载结束后的清理工作。
最后在dialog.h中加入#include "updatebynetwork.h"
,然后定义一个升级用的类变量UpdateByNetwork *pUpdate
,并在dialog.cpp中进行实例化:pUpdate = new UpdateByNetwork()
,指定下载目录和文件名后开始下载。
该部分具体代码请参考GitHub—initxuan::books
Qt获取应用程序本地目录的函数有多种,包括
QDir::currentDir()
、QCoreApplication::applicationDirPath()
、QCoreApplication::applicationFilePath()
等。本程序中使用的是QCoreApplication::applicationFilePath()
函数,获取目录最后有一个字符“/”,将其删除后获得应用程序目录。如果直接单击应用程序EXE执行程序,则上述函数功能完全一样;如果通过其他方式执行程序,则程序运行结果将会出现差别,具体如下:
QDir::currentDir()
获取的是执行环境的当前目录,也就是说,如果应用程序在Windows开始菜单中有快捷方式,那么利用快捷方式启动程序时,QDir::currentDir()
获得的目录是开始菜单中的目录,类似“C:\ProgramData\Microsoft\Windows\Start Menu\Programs”。QCoreApplication::applicationFilePath()
函数一般情况下获取的是应用程序的真实目录。经过调试,目前最佳方案是使用QCoreApplication::applicationFilePath()
函数,建议直接使用。
网友评论