Qt QWebsocket::open 阻塞用户界面

Posted

技术标签:

【中文标题】Qt QWebsocket::open 阻塞用户界面【英文标题】:Qt QWebsocket::open blocks user interface 【发布时间】:2018-11-22 14:54:25 【问题描述】:

我正在开发一个小型系统,它由多个客户端和一个管理应用程序组成。每个客户端都有一个 QWebSocket 服务器来监听管理员的请求,因此管理员应用需要连接到不同的客户端。

这是我的登录对话框:

在登录之前,我不知道哪个是客户端 IP 地址,因此每次发送登录凭据时,我都需要尝试打开与该 IP 地址的连接。问题是在 Windows UI 中阻塞直到套接字服务器响应或超时,但在 Windows 中它工作正常。

编辑 1: 我遵循了Tung Le Thanh 的建议,因此代码中包含了他的提示。现在主要的问题是ConnectionHelper 不能在没有得到QSocketNotifier 的情况下发出任何信号:不能从另一个线程启用或禁用套接字通知器

我有一个 ConnectionHelper 负责向 WebSocket setver 发送接收数据。

main.cpp

ConnectionHelper *helper = new ConnectionHelper();
LoginDialog dialog(helper);

QThread* thread = new QThread();
helper->moveToThread(thread);
thread->start();

dialog.show();
return a.exec();

LoginDialog 的构造函数:

connect(helper, &ConnectionHelper::onConnectionError, this, &LoginDialog::onCxnError);
connect(helper, &ConnectionHelper::loginInformationReceived, this, &LoginDialog::onLoginInfo);
connect(helper, &ConnectionHelper::cxnEstablished, this, &LoginDialog::onConnected);

已接受的位置:

void LoginDialog::on_buttonBox_accepted()

    ui->buttonBox->button(QDialogButtonBox::Ok)->setEnabled(false);
    QString host = ui->lineEditServer->text();
    QString port = ui->lineEditPort->text();
    QString ws = "ws://" + host + ":" + port;
    helper->setUrl(QUrl(ws));


void ConnectionHelper::setUrl(QUrl url)

        if(!webSocket)

    webSocket = new QWebSocket();

    connect(webSocket, &QWebSocket::textMessageReceived, this, &ConnectionHelper::processTextMessage, Qt::QueuedConnection);
    connect(webSocket, &QWebSocket::binaryMessageReceived, this, &ConnectionHelper::processBinaryMessage);
    connect(webSocket, &QWebSocket::disconnected , this, &ConnectionHelper::socketDisconnected);

    connect(webSocket, QOverload<QAbstractSocket::SocketError>::of(&QWebSocket::error)
            , this, [this](QAbstractSocket::SocketError error)
        Q_UNUSED(error)
        emit onConnectionError();
    );

    connect(webSocket, &QWebSocket::connected, this, [=]() 
        emit cxnEstablished();
    );


webSocket->open(url);
    webSocket->open(url);


void ConnectionHelper::processTextMessage(QString message)

    QJsonDocument response = QJsonDocument::fromJson(message.toUtf8());
    QJsonObject objResponse = response.object();

    QString action = objResponse[ACTION_KEY].toString();

    if (action == ACTION_LOGIN)
        emit loginInformationReceived(objResponse);

在收到任何响应之前,我会禁用“确定”按钮,并且在 Linux 上可以正常工作,但在 Windows 中,整个 UI 会阻塞并且在收到响应之前变得无响应。

我也尝试将ConnectionHelper 实例移动到另一个线程,但我得到了这样的响应:QSocketNotifier: Socket notifiers cannot be enabled or disabled from another thread

我没有想法,我需要找到一种方法使 webSocket-&gt;open(url) 异步或任何类似的东西。

谢谢。

【问题讨论】:

您好,不确定这是否有帮助,但您是否尝试过QCoreApplication::processEvents() 您好,您可以在 lambda 中捕获 this 并使用 emit this-&gt;cxnEstablished(); 来查看它是否有效。请注意,setUrl 必须以 invokeMethod 形式调用,而不是从 UI 线程调用。 没用...是的,我使用你的建议QMetaObject::invokeMethod( helper, "setUrl", Qt::QueuedConnection, Q_ARG(QUrl, QUrl(ws)) 【参考方案1】:

我意识到QWebSocket::open 它是我使用的唯一异步函数。所以我只需要在设置URL和打开socket连接之前有两个线程。

按照 Tung Le Thanh 的回答和一个小技巧,现在一切正常。我的解决方案是在连接打开并开始发出信号后恢复默认威胁。

void ConnectionHelper::setUrl(QUrl url, QThread* thread)

    if(!webSocket)
    
        webSocket = new QWebSocket();
        connect(webSocket, &QWebSocket::connected, this, [this, thread]() 
            this->moveToThread(thread);
            webSocket->moveToThread(thread);

            emit this->cxnEstablished();
        );

    
    webSocket->open(url);

现在LoginDialog 需要发送它的线程

void LoginDialog::on_buttonBox_accepted()

    ui->buttonBox->button(QDialogButtonBox::Ok)->setEnabled(false);
    QString host = ui->lineEditServer->text();
    QString port = ui->lineEditPort->text();
    QString ws = "ws://" + host + ":" + port;
    QMetaObject::invokeMethod(  helper, "setUrl", Qt::QueueConnection,
                Q_ARG( QUrl, QUrl(ws)),
                Q_ARG( QThread*, QThread::currentThread())
    );

【讨论】:

很高兴您找到了解决方案。我还发布了我用于测试的我的。 很高兴看到 webSocket 可以这样移动到另一个线程。 我认为找到了问题的根源,代码现在可以不用花招了。【参考方案2】:

错误:

QSocketNotifier:不能从另一个线程启用或禁用套接字通知器

当您尝试直接从另一个线程调用网络函数时发生(助手及其 webSocket 在另一个线程中)。请改用 invokeMethod 或信号/槽。

EDIT 1 : 实际上,webSocket 是在调用 ConnectionHelper 构造函数时创建的,它属于主线程。如果 ConnectionHelper 未设置为其父级,则 moveToThread 不允许移动 webSocket。为避免这种情况,必须以 ConnectionHelper 作为父项或在线程已经启动时初始化 webSocket。

注意:如果您的应用程序在对话框 accepted() 被触发(主窗口关闭)后退出,您将看不到发出的信号。

更新 2

    ConnectionHelper::ConnectionHelper(QObject *parent) : QObject(parent)
    
        webSocket = new QWebSocket(QString(), QWebSocketProtocol::VersionLatest, this);

        connect( webSocket, &QWebSocket::stateChanged, this, [=](QAbstractSocket::SocketState s)
            qDebug() << "Socket state changed : " << s;
          );
        connect( webSocket, &QWebSocket::connected, this, [=]()
            emit cxnOk();
            webSocket->sendTextMessage("HELLO");
         );

        void (QWebSocket::*error_signal)(QAbstractSocket::SocketError err) = &QWebSocket::error;

        connect( webSocket, error_signal, this, [=](QAbstractSocket::SocketError err)
            qDebug() << "On socket error : " << err;
          );

        connect( webSocket, &QWebSocket::textMessageReceived, this, [=](QString s)
            qDebug() << "text message received: " << s;
         );
    

    void ConnectionHelper::setUrl(QUrl url)
    
        if( webSocket->state() == QAbstractSocket::ConnectedState )
            webSocket->close();
        

        qDebug() << "Open URL: " << url; 
        webSocket->open( url );
    

ConnectionHelper 实例的初始化:

    QThread * pThread = new QThread();
    m_pHelper = new ConnectionHelper();

    connect( m_pHelper, &ConnectionHelper::cxnOk, this, &MainWindow::onConnectionConnected, Qt::QueuedConnection );

    m_pHelper->moveToThread( pThread );
    pThread->start();

将 setUrl 更改为 slot,然后使用 invokeMethod 将命令发送到帮助程序实例。

    void MainWindow::on_pushButton_clicked()
    
        QString ws = "ws://echo.websocket.org";
        QMetaObject::invokeMethod( m_pHelper, "setUrl", Qt::QueuedConnection, Q_ARG( QUrl, QUrl(ws) ) );
        qDebug() << "Invoke setUrl ended" ;

    

    void MainWindow::onConnectionConnected()
    
        qDebug() << "[MainWindow] On connection connected !!!!";
    

结果:

    Invoke setUrl ended
    Open URL:  QUrl("ws://echo.websocket.org")
    Socket state changed :  QAbstractSocket::ConnectingState
    Socket state changed :  QAbstractSocket::ConnectedState
    [MainWindow] On connection connected !!!!
    text message received:  "HELLO"

【讨论】:

我做到了,但仍然有同样的警告QSocketNotifier: Socket notifiers cannot be enabled or disabled from another thread 在Windows上试过,没有锁定界面但警告仍然存在。 @Engel:检测到问题,我重新编辑了答案,请试一试,我在 Windows 和 Linux 上测试了该解决方案,它可以工作。 @Thung Le Thanh 我试过了,但没有成功,因为:connect(webSocket, &QWebSocket::connected, this, [=]() emit cxnEstablished(); );我无法发出信号,我将尝试其他方法,但接缝要接近。 您的解决方案可以解决问题,但不能解决整个问题,因为现在ConnectionHelper 在没有警告的情况下无法发出任何信号:'(

以上是关于Qt QWebsocket::open 阻塞用户界面的主要内容,如果未能解决你的问题,请参考以下文章

java - 阻塞队列

实现有界缓冲区(读取器和写入器之间的非阻塞,读取器之间的阻塞,写入器之间的阻塞)

Java里的阻塞队列

Qt:如何创建一个不最小化且不阻塞后台GUI的窗口

「每天一道面试题」Java中的阻塞队列都有哪些

Qt编程求助:有没有啥类似QDialog::exec()之类的能阻塞程序的方法?