• 文件批量上传的工具,要实现暂停继续、断点续传等功能(使用QtNetwork和QHttpMultiPart,和定时器检查超时)


    最近在做一个文件批量上传的工具,要实现暂停继续、断点续传等功能。利用Qt自带的QtNetwork模块,完成这些需求并没有费多少周章,主要思路就是将文件分块,然后用while循环依次传输。具体实现代码比较复杂,简化了一下大致是这样子的:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    int chunkUpload(UploadItem *item, const qint64 &chunkSize)
    {
    Q_ASSERT(item != 0);

    // ...

    QNetworkRequest request;
    QNetworkReply* reply;
    QHttpMultiPart* multiPart = new QHttpMultiPart(QHttpMultiPart::FormDataType);
    QBuffer* buffer = new QBuffer(multiPart);
    QEventLoop loop;
    QTimer timer;

    // ... append fields to multiPart ...

    request.setUrl(UPLOAD_URL);
    reply = networkManager->post(request, multiPart);
    // 保证reply销毁的时候multiPart和buffer也会随之销毁。
    multiPart->setParent(reply);

    // 加入一个消息循环,reply传输完成前进程会一直阻塞在这里;并设置一个定时器,如果传输超时则强制跳出消息循环
    timer.setSingleShot(true);
    timer.setInterval(NetworkTimeout);
    connect(reply, SIGNAL(uploadProgress(qint64,qint64)), &timer, SLOT(start()));
    connect(reply, SIGNAL(downloadProgress(qint64,qint64)), &timer, SLOT(start()));
    connect(&timer, &QTimer::timeout, &loop, &QEventLoop::quit);
    connect(reply, &QNetworkReply::finished, &loop, &QEventLoop::quit);
    timer.start();
    loop.exec();

    reply->deleteLater();
    if (reply->isRunning())
    reply->abort();

    if (reply->error() != QNetworkReply::NoError)
    return -1;

    // ... deal with the reply ...

    return 0;
    }

    void upload()
    {
    while (NOT_FINISHED) {
    chunkUpload(item, CHUNK_SIZE);
    }
    }

    程序编译运行过程很顺利,测试的时候也没发现什么问题。但后来我随手上传了一个1G大小的文件,发现每次文件上传到70%左右的时候程序就崩溃了,小文件就没这个问题。急忙打开任务管理器,这才发现上传文件的时候,程序内存占用会随着上传进度的增加而增加,上传1G文件的时候内存最多会吃到1.5G,这时候程序申请不到更多内存了,我又没做检查,当然就会崩溃掉。

    限制上传文件大小这种事我是不会做的,毕竟一个上传工具占用内存比PS都高实在不科学。注意到文件上传完成之后内存会立即回到正常值,显然原因并不是我忘记释放内存而是内存释放不及时,这样看来唯一可疑的地方就是上面chunkUpload函数里面的reply->deleteLater()那一句了吧。于是我写了个方法监听reply的销毁时机,果然每一块上传完成之后reply没有销毁,直到文件全部上传完毕之后才输出一大堆“I’m destroyed…”的信息。

    根据Qt文档的说明,QObject::deleteLater()并没有将对象立即销毁,而是向主消息循环发送了一个event,下一次主消息循环收到这个event之后才会销毁对象。我在这里使用deleteLater只是因为Qt文档里推荐这么做而已,其他并没多想。是这样的话一切都说得通了,因为chunkUpload函数是在一个while循环里,程序还没来得及处理这个event就立即进行下一块传输了,传输过程中生成的QNetworkReply以及它关联的QBufferQHttpMultiPart当然也就来不及删除了。崩溃原因找到了。你不就是来不及处理销毁对象的event嘛,手动让你处理下不就行了?于是修改upload函数代码如下:

    1
    2
    3
    4
    5
    6
    7
    void upload()
    {
    while (NOT_FINISHED) {
    chunkUpload(item, CHUNK_SIZE);
    qApp->processEvents(); // 让主程序把消息队列中的QEvent处理完
    }
    }

    编译、运行,内存占用依然没有改变,看样子加这一行用处不大。再次查询QObject::deleteLater()的文档,发现这样一句话:

    …for the object to be deleted, the control must return to the event loop from which deleteLater() was called.

    这么说来,deleteLater销毁QObject的唯一时机就是程序返回主消息循环以后了呢。无奈只能放弃deleteLater。考虑到每次return前都要手动delete亦或是使用goto语句实在都不够优雅,所以利用Qt自带的QScopedPointer,修改chunkUpload函数如下:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    int chunkUpload(UploadItem *item, const qint64 &chunkSize)
    {
    Q_ASSERT(item != 0);

    // ...

    QNetworkRequest request;
    QScopedPointer<QNetworkReply> reply;
    QHttpMultiPart* multiPart = new QHttpMultiPart(QHttpMultiPart::FormDataType);
    QBuffer* buffer = new QBuffer(multiPart);
    QEventLoop loop;
    QTimer timer;

    // ... append fields to multiPart ...

    request.setUrl(UPLOAD_URL);
    reply.reset(networkManager->post(request, multiPart));
    multiPart->setParent(reply.data());

    // ... event loop ...

    if (reply->isRunning())
    reply->abort();

    if (reply->error() != QNetworkReply::NoError)
    return -1;

    // ... deal with the reply ...

    return 0;
    }

    问题解决。

    https://blog.yeatse.com/2015/03/18/qobject-deletelater-usage/

  • 相关阅读:
    高德地图在h5项目中的集成(点标记)
    angular中点击页面任意地方让显示的元素消失
    关于echars中雷达图的一些配置
    部署项目到阿里云服务器上遇到的问题
    sql语句的简单记录
    C#中的数据类型
    原型和继承
    Git 命令行使用
    以前一直设置水平居中,现在我们来讨论一下图片居中的四种小技巧
    让盒子两端对齐小技巧 => inline-block
  • 原文地址:https://www.cnblogs.com/findumars/p/6204406.html
Copyright © 2020-2023  润新知