Engel
Engel

Reputation: 199

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.

Upvotes: 1

Views: 2329

Answers (2)

tunglt
tunglt

Reputation: 1072

The error :

QSocketNotifier: Socket notifiers cannot be enabled or disabled from another thread

occurred while you try to call a network function directly from another thread (helper and its webSocket were in the other thread). Use invokeMethod or signal/slot instead.

EDIT 1 : in fact, the webSocket was created while ConnectionHelper constructor was called, and its belong to the main thread. The moveToThread does not allow the webSocket to be moved if ConnectionHelper was not set as its parent. To avoid that, the webSocket must be initialized with ConnectionHelper as a parent or when the thread was already started.

NOTE: if your application quits just after the dialog accepted() was fired (main window closed), you cannot see your signals emited.

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

Initialization of ConnectionHelper instance :

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

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

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

Change setUrl to slot then use invokeMethod to send the command to the helper instance.

    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 !!!!";
    }

RESULTS:

    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"

Upvotes: 0

Engel
Engel

Reputation: 199

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

Upvotes: 1

Related Questions