使用 QNetworkAccessManager->Post() 会导致 SEGV 关闭应用程序

Posted

技术标签:

【中文标题】使用 QNetworkAccessManager->Post() 会导致 SEGV 关闭应用程序【英文标题】:Using QNetworkAccessManager->Post() causes SEGV on closing the application 【发布时间】:2016-11-19 16:12:44 【问题描述】:

****更新:我注意到我只在 Windows 上得到了段错误,在 Linux 上很好。在 Windows 上,我使用 QT 5.5 和 MinGW32。我还是想知道为什么。

**** 初始问题: 这里没什么难的,我创建了一个基本的控制台应用程序。我有一个 QNetworkAccessManager 发送 Post() 请求。当我关闭控制台时,出现了段错误。

请注意,请求发送和接收成功,我的问题只是关于那个段错误。

如果没有发送 Post() 请求,关闭控制台不会崩溃。堆栈没有太多帮助。

堆栈

0   ntdll!RtlFreeHeap           0x77b5e041  
1   ucrtbase!free           0x5e4c5eab  
2   LIBEAY32!CRYPTO_free            0x5e5a123e  

Main.cpp

#include <QCoreApplication>
#include "CNetworkHandleTest.h"

int main(int argc, char *argv[])

    QCoreApplication a(argc, argv);

    CNetworkHandleTest net;
    net.start();

    return a.exec();

CNetworkHandleTest.cpp

#include "CNetworkHandleTest.h"

CNetworkHandleTest::CNetworkHandleTest()

    m_Manager = new QNetworkAccessManager(this);
    // Connect the network manager so we can handle the reply
    connect(m_Manager, SIGNAL(finished(QNetworkReply*)), this, SLOT(onFinished(QNetworkReply*)));
    m_nTotalBytes = 0;


CNetworkHandleTest::~CNetworkHandleTest()

    disconnect();
    m_Manager->deleteLater();


void CNetworkHandleTest::onFinished(QNetworkReply* reply)

    // Look at reply error
    // Called when all the data is receivedqDebug() << "Error code:" << reply->error();
    qDebug() << "Error string:" << reply->errorString();
    reply->close();
    reply->deleteLater();


void CNetworkHandleTest::start()

    // Configure the URL string and then set the URL
    QString sUrl(BASE_URL);
    sUrl.append("/console/5555/upload");
    m_Url.setUrl(sUrl);

    // Make the request object based on our URL
    QNetworkRequest request(m_Url);

    // Set request header (not sure how or why this works, but it works)
    // \todo investigate
    request.setHeader(QNetworkRequest::ContentTypeHeader, "application/x-www-form-urlenconded");

    // Make file object associated with our DB file
    QFile file("/tx_database.db");
    if(!file.open(QIODevice::ReadOnly))
    
        qDebug() << "Failed to open file";
    

    // Read the entire file as a binary blob
    QByteArray data(file.readAll());

    // Set our request to our request object
    // Note: there should probably be a flag so that when start is called it does not do
    // any processing in case we are already in the middle of processing a request
    m_Request = request;

    // Send it
    m_Reply = m_Manager->post(m_Request, data);

    // Need to connect the signals and slots to the new reply object (manager makes a new
    // reply object every post; may need to investigate if memory should be freed when
    // done processing a response)
    connect(m_Reply,    SIGNAL(readyRead()), this, SLOT(onReadyRead()));
    connect(m_Manager, SIGNAL(authenticationRequired(QNetworkReply*, QAuthenticator*)),
            this,       SLOT(onAuthenticationRequired(QNetworkReply*, QAuthenticator*)));


void CNetworkHandleTest::onReadyRead()

    // Whenever data becomes available, this slot is called.  It is called every time data
    // is available, not when all the data has been received.  It is our responsibility to
    // keep track of how much we have received if we want to show progress or whatever
    // but we do not need to keep track if we have received all the data.  The slot
    // OnFinished will be called when the all the data has been received.

    qDebug() << "Bytes available:" << m_Reply->bytesAvailable();
    m_nTotalBytes += m_Reply->bytesAvailable();
    qDebug() << "Bytes thus far:" << m_nTotalBytes;
    QByteArray responseData = m_Reply->readAll();
    qDebug() << "Response" << responseData;
    m_Reply->size();


void CNetworkHandleTest::onAuthenticationRequired(QNetworkReply* reply, QAuthenticator* authenticator)



CNetworkHandleTest.h

#ifndef CNETWORKHANDLETEST_H
#define CNETWORKHANDLETEST_H

// Required packages
#include <QNetworkAccessManager>
#include <QNetworkRequest>
#include <QNetworkReply>
#include <QByteArray>
#include <QUrlQuery>
#include <QHostInfo>
#include <QObject>
#include <QUrl>

// Helper packages
#include <QCoreApplication>
#include <QFile>
#include <QDir>

// Our packages
#include <iostream>
#include <fstream>
#include <cstdlib>

#define BASE_URL "localhost:5000"
#define BOUNDARY "123456787654321"

class CNetworkHandleTest : public QObject

    Q_OBJECT

public:
    CNetworkHandleTest();
    ~CNetworkHandleTest();

    void start();

protected Q_SLOTS:

    void onFinished(QNetworkReply* reply);
    void onReadyRead();
    void onAuthenticationRequired(QNetworkReply* reply, QAuthenticator* authenticator);

private:

    QNetworkAccessManager* m_Manager;
    QNetworkRequest       m_Request;
    QNetworkReply*        m_Reply;
    QUrl                  m_Url;

    int                m_nTotalBytes;
;

#endif // CNETWORKHANDLETEST_H

【问题讨论】:

您可以从onFinished() 发出信号并将其连接到应用程序的quit() 插槽。这应该在转移完成后结束应用程序。当前关闭应用程序可能会触发导致崩溃的终止形式 当终端关闭时,基本代码可能没有处理信号或 Windows 用来终止程序的任何内容。在 Linux 上,这可能是一个标准信号并导致应用程序正常终止,而在 Windows 上可能不是。在搜索 Window 的信号终端关闭方式时发现了这个:***.com/questions/20511182/… 请注意m_managerdeleteLater 是不必要的:实例将从QObject::~QObject 中删除。请注意 CNetworkHandleTest 的析构函数在没有事件循环运行时运行,所以 iff m_manager 没有父级,它会泄漏。不要使用deleteLater,除非您可以明确说明这样做的原因。唯一典型的原因是在从给定对象调用的槽内:如果连接是自动的,即delete x 错误当且仅当 x == sender() 也不需要调用disconnect()QObject 的全部意义在于让生活变得轻松:OK 可以删除具有父对象或具有活动连接的对象。按价值持有它们也可以:) 也不需要调用reply-&gt;close()。正确的QIODevice 派生类确实是正确的 C++ 类:它们总是可破坏的。它们的重点是自动管理资源,并通过构造使您的代码正确。你不能忘记关闭QFileQNetworkReply:你不需要一开始就关闭它!只需销毁对象即可。 【参考方案1】:

当你关闭控制台时,你的程序会以最不优雅的方式死掉。您需要编写一些代码来使其优雅: 下面是一个完整的测试用例:

// https://github.com/KubaO/***n/tree/master/questions/network-cleanup-40695076
#include <QtNetwork>
#include <windows.h>

extern "C" BOOL WINAPI handler(DWORD)

   qDebug() << "bye world";
   qApp->quit();
   return TRUE;


int main(int argc, char *argv[])

   SetConsoleCtrlHandler(&handler, TRUE);
   QCoreApplication a(argc, argv);

   QNetworkAccessManager mgr;
   int totalBytes = 0;

   QObject::connect(&mgr, &QNetworkAccessManager::finished, [](QNetworkReply *reply)
      qDebug() << "Error string:" << reply->errorString();
   );

   QNetworkRequest request(QUrl"http://www.google.com");
   request.setHeader(QNetworkRequest::ContentTypeHeader, "application/x-www-form-urlenconded");

   auto reply = mgr.post(request, QByteArray"abcdefgh");
   QObject::connect(reply, &QIODevice::readyRead, [&]
      qDebug() << "Bytes available:" << reply->bytesAvailable();
      totalBytes += reply->bytesAvailable();
      qDebug() << "Bytes thus far:" << totalBytes;
      reply->readAll();
   );
   QObject::connect(reply, &QObject::destroyed, []
      qDebug() << "reply gone";
   );
   QObject::connect(&mgr, &QObject::destroyed, []
      qDebug() << "manager gone";
   );
   return a.exec();

如果你在控制台窗口按Ctrl-C或者点击[x],关机是有序的,输出是:

[...]
bye world
reply gone
manager gone

【讨论】:

以上是关于使用 QNetworkAccessManager->Post() 会导致 SEGV 关闭应用程序的主要内容,如果未能解决你的问题,请参考以下文章

离线使用 QNetworkAccessManager

跨 dll 使用 QNetworkAccessManager

使用 QNetWorkAccessManager 将值传递给插槽

如何使用 Qt/QNetworkAccessManager (C++) 实现 SFTP

如何使用 QNetworkAccessManager 找出数据传输延迟

使用 QNetworkAccessManager 时如何处理代理