1

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.

Engel
  • 189
  • 3
  • 16
  • Hi there, not sure if this helps or not, but have you tried [`QCoreApplication::processEvents()`](http://doc.qt.io/qt-5/qcoreapplication.html#processEvents)? – TrebledJ Nov 22 '18 at 15:18
  • Hi, you can capture `this` in lambda and use `emit this->cxnEstablished();` to see if it works. Note that setUrl must be invoke form invokeMethod, not being called from UI thread. – tunglt Nov 22 '18 at 18:58
  • Didn't work... and yes, I use your suggestion `QMetaObject::invokeMethod( helper, "setUrl", Qt::QueuedConnection, Q_ARG(QUrl, QUrl(ws))` – Engel Nov 22 '18 at 19:18

2 Answers2

1

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())
    );
}
Engel
  • 189
  • 3
  • 16
0

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"
tunglt
  • 1,022
  • 1
  • 9
  • 16
  • I did it but still having the same warning `QSocketNotifier: Socket notifiers cannot be enabled or disabled from another thread` – Engel Nov 22 '18 at 15:59
  • Tried on Windows, doesn't lock the interface but warning still exists. – Engel Nov 22 '18 at 16:33
  • @Engel: the issue was detected and I re-edit the answer, please take a try, I tested the solution on Windows and Linux and it works. – tunglt Nov 22 '18 at 16:46
  • @Thung Le Thanh I'd tried and didn't work, because this: connect(webSocket, &QWebSocket::connected, this, [=](){ emit cxnEstablished(); }); I can't emit the signal, I'm going to try other approach but is seams to be close. – Engel Nov 22 '18 at 17:11
  • Your solution fix the problem but not the entire problem, because now `ConnectionHelper` can't emit any signal without having the warning :'( – Engel Nov 22 '18 at 17:26
  • @Engel: you connect webSocket when it was initialized in setUrl or in a call direct from the main thread ? – tunglt Nov 22 '18 at 17:40
  • `ConnectionHelper` has three slots connected to websocket signal's `textMessageReceived`, `disconnected` and `connected` those connections are made on `websocket` initialization as you suggested. inside those slots several signals are emmited. I will edit the post so you can see the code. Thanks – Engel Nov 22 '18 at 17:55