QTcpSocket 或 QSslSocket 会自动创建读/写线程吗?

Posted

技术标签:

【中文标题】QTcpSocket 或 QSslSocket 会自动创建读/写线程吗?【英文标题】:Do QTcpSocket or QSslSocket automatically create thread for reading/writing? 【发布时间】:2017-07-13 10:14:31 【问题描述】:

尽管没有在任何地方使用std::threadQThread,仍然遇到以下问题:

    总是来自 Qt 的运行时调试错误日志:

    QObject::connect: 无法将“QAbstractSocket::SocketError”类型的参数排队 (确保使用 qRegisterMetaType() 注册 'QAbstractSocket::SocketError'。)

    TcpSocket::flush() 方法间歇性崩溃; 我使用这种方法来确保立即写入 TCP;现在有时应用程序会在SIGPIPE 这种方法下完全崩溃

在网上搜索,发现有人建议修复第一个问题(即元错误),当我们有多个线程时,我需要使用qRegisterMetaType() 注册。 同样的多线程也被称为第二个问题的原因;见this 和this。

但我没有超过 1 个线程! 我的套接字代码如下所示:

struct Socket : public QSslSocket

  Q_OBJECT public:

  void ConnectSlots ()
  
    const auto connectionType = Qt::QueuedConnection;
    connect(this, SIGNAL(readyRead()), this, SLOT(ReceiveData()), connectionType);
    connect(this, SIGNAL(disconnected()), this, SLOT(Disconnected()), connectionType);
    connect(this, SIGNAL(error(QAbstractSocket::SocketError)),
            this, SLOT(Error(QAbstractSocket::SocketError)), connectionType);
    //                           ^^^^^^^ error comes whether I comment this or not
  

  public slots:
  void ReceiveData ()  ... 
  void Disconnected ()  ... 
  void Error ()  ... 

问题:Qt 是否会自行创建任何内部线程用于读/写目的? (我希望不是)。如何解决以上 2 个问题?

【问题讨论】:

我首先想到的是在信号/插槽连接中使用int 而不是枚举类型。 IE。 ...SLOT(Error(int)... 如果您不想用 Qt 元系统打扰自己,请正确地将参数转换为插槽中的枚举值。 您在代码中显示“无论我是否对此进行评论都会出错”。注释掉具体是什么 - 只是 ErrorQAbstractSocket::SocketError 参数或对 connect 的整个调用? @vahancho,如果我做到了Error(int),那么就会出现类型不兼容的错误:"QObject::connect: Incompatible sender/receiver arguments. Connection::Socket::error( QAbstractSocket::SocketError) --> Connection::Socket::Error(int)".假设如果我删除参数并使其成为Error(),那么 Qn 中提到的实际问题仍然存在。 为什么需要QueuedConnection?通常我使用默认的 AutoConnection @Jeka,因为很多时候“Write()”函数被调用,在它完成之前“Read()”被调用。我的代码逻辑期望“Write()”应该在“Read()”开始之前完全完成,反之亦然。使用QueuedConnection,该部分已解决。 【参考方案1】:

我认为问题与线程无关,而是QAbstractSocket::SocketError 类型参数 Qt::QueuedConnection 的组合导致了问题。

查看 Qt5.8 源代码中的各种 connect 实现,如果将 Qt::QueuedConnection 指定为连接类型,则将执行对信号参数类型的检查。比如……

int *types = 0;
if ((type == Qt::QueuedConnection)
        && !(types = queuedConnectionTypes(signalTypes.constData(), signalTypes.size()))) 
   return QMetaObject::Connection(0);

如果任何类型未注册,queuedConnectionTypes 将返回一个空指针。

因此,如果连接排队,则必须注册 signal 使用的所有参数,无论它们是否被插槽使用。为避免错误,请确保您致电...

qRegisterMetaType<QAbstractSocket::SocketError>();

在任何使用QAbstractSocket::SocketError 参数和Qt::QueuedConnection 组合的connect 调用之前的某个时间点。

【讨论】:

感谢您的回答。您是否还想添加一些关于如何以及在何处/何时进行注册的代码(为了新手)。另外,对问题2有任何想法吗?我的猜测是 1 & 2 可能是相关的。可能是由于上述问题,error() 未发出信号 --> Error() 插槽未被调用 --> 套接字仍在继续写入和刷新断开连接的套接字 --> 崩溃。 编辑了答案以包括基本修复——调用qRegisterMetaType。关于flush 问题,我目前还不确定。正如您所说,它们可能是相关的——SIGPIPE 表明 flush 可能导致 另一端关闭/关闭后写入。 是的,上述解决方案解决了第一个问题。第二个问题,我必须等待被复制(希望它不会)。顺便说一句,当我查看qRegisterMetaType() 的代码时,它的定义以QT_DEPRECATED 为前缀。我正在使用最新的 Qt 5.9。这是正确的吗?我们是否需要使用Q_DECLARE_METATYPE()?请也解释一下。 关于QT_DEPRECATED 宏和qRegisterMetaType,我不知道为什么。 documentation 没有提到它已被弃用。至于Q_DECLARE_METATYPE,这已经在Qt头文件中用于QAbstractSocket::SocketError,所以不需要在你自己的代码中显式使用它。【参考方案2】:

不,套接字不会为读/写创建单独的线程。相反,只要观察到读/写,操作系统就会在给定的套接字描述符上引发事件。这个事件应该排队。因此,Qt::QueuedConnection 是首选。

QAbstractSocket::SocketError 是即兴的,显示为特定于操作系统。这是无法避免的。最多可以在发生此类错误时销毁套接字。

为避免崩溃,每当套接字断开连接时,可以执行以下操作:

void Destroy (QWebSocket* const pSocket)

  if(pSocket == nullptr)
    return;
  pSocket->disconnect();  // no further signal/slot
  pSocket->close();  // graceful closure
  pSocket->deleteLater(); // don't delete immediately; let the Qt take care
  pSocket = nullptr; // to avoid further undefined behaviour

即使执行了上述操作,有时也会由于write() 操作而导致套接字崩溃。即。当socketclose()-es时,它会尝试flush()所有可写的数据。在此期间,如果远程连接已经关闭,则操作系统使用SIGPIPE 事件使程序崩溃。不幸的是,在 C++ 中使用 std::exceptions 无法阻止它。

下面帖子中提到的解决方案没有帮助:How to prevent SIGPIPEs (or handle them properly) 这可以通过以下方式避免:

if(pSocket->isValid())
  pSocket->sendBinaryMessage(QByteArray(...));

所以isValid() 在套接字试图将某些内容写入已经断开的远程套接字连接的情况下会有所帮助。

【讨论】:

This can be avoided by following: ...。不幸的是,这种方式无法避免,因为在调用刷新时报告的套接字状态(通过调用 isValid() 或 state() 方法检索)是可以的,即使远程连接已经关闭。它是唯一能够解决问题的 Qt 内部实现。 Qt 错误?与此同时,我认为 flush() 方法已损坏。 Qt 实现应该调用带有 MSG_NOSIGNAL 标志的 send()。 @ArtemPisarenko,有大量证据,您可以考虑向 Qt 团队提出一个错误。到今天为止,我已经失去了这个问题的踪迹。但请记住在我的代码中修复问题后发布此答案。 我刚刚发现我错了(部分)。实际上,Qt 实现是正确的。这只是我的应用程序中的一个错误,让我认为 SIGPIPE 导致了崩溃。不,它没有。 就我而言,混淆的根源在于调试器捕获 SIGPIPE、暂停执行以及在继续执行后立即触发我的错误(以崩溃结束)。只需将调试器配置为忽略 SIGPIPE。所以它不会造成任何伤害。但是无论您是否进行 isValid() 检查,您都可能会抓住它。此外,在这种情况下,flush() 调用将发出 stateChanged 信号,因此在连接到它的插槽中要小心(至少,这让我感到惊讶并导致我提到的错误)。

以上是关于QTcpSocket 或 QSslSocket 会自动创建读/写线程吗?的主要内容,如果未能解决你的问题,请参考以下文章

QSslSocket 等待数据时超时(但 QTcpSocket 不会)

QTcpSocket 不加载 ssl

QTcpSocket 客户端自动重连

QTcpSocket 不发出错误信号

QTcpSocket 不会发出任何错误或连接信号

Qt:QSslSocket::bytesWritten() 信号发出过于频繁