c++qtqtwebsockets

Qt QWebsocket::open blocks user interface


I'm working on a small system, it's made by several clients and one admin application. Each client has a QWebSocket server to listen admin's requests so admin app needs to connect to different clients.

This is my Login Dialog:

Login dialog

Before login I don't know which is client ip address so every time that I send login credentials I need try to open a connection to that IP address. The problem is that in Windows UI blocks until socket server responds or timeout its reached but in Windows its works fine.

EDIT 1: I followed Tung Le Thanh suggestions so the code includes his tips. Now the main problem is that ConnectionHelper can't emmit any signal without getting QSocketNotifier: Socket notifiers cannot be enabled or disabled from another thread

I have an ConnectionHelper that is in charge to send an receive data to and from 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's constructor :

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

Slot on accepted:

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);
}

I do disable OK button until any response is received and works fine on Linux but in Windows the entire UI block and become unresponsive until a response is received.

I also try to move ConnectionHelper instance to another Thread but I got this response: QSocketNotifier: Socket notifiers cannot be enabled or disabled from another thread

I'm out of ideas I need to find a way to make webSocket->open(url) async or any thing like that.

Thanks.


Solution

  • I realize that QWebSocket::open its the only async function that I'm using. So I only need to have two threads before set the URL and open the socket connection.

    Following Tung Le Thanh answers' and a little trick now everything works fine. My solution was to back to default Threat once connection is open and start to emitting signals.

    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);
    }
    

    And now LoginDialog needs to send it's Thread

    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())
        );
    }