Skip to content
  • Categories
  • Recent
  • Tags
  • Popular
  • Users
  • Groups
  • Search
  • Get Qt Extensions
  • Unsolved
Collapse
Brand Logo
  1. Home
  2. Qt Development
  3. General and Desktop
  4. Multithreaded qwebsocket - competing server
Forum Updated to NodeBB v4.3 + New Features

Multithreaded qwebsocket - competing server

Scheduled Pinned Locked Moved General and Desktop
7 Posts 4 Posters 6.7k Views 2 Watching
  • Oldest to Newest
  • Newest to Oldest
  • Most Votes
Reply
  • Reply as topic
Log in to reply
This topic has been deleted. Only users with topic management privileges can see it.
  • N Offline
    N Offline
    nohoo77
    wrote on last edited by
    #1

    Hi,
    I do have a problem with a multithreaded websocket server:
    So far I have successfully implemented a single threaded websocket server using the qwebsocketserver class. Pretty much following the qt example for the chat server.
    Processing my specific server side business logic can take quite some time and therefore I would like to spawn separate threads for incoming requests to stay responsive to my clients.
    The QWebSocketServer documentation for "QWebSocketServer::nextPendingConnection()" states: "The returned QWebSocket object cannot be used from another thread."

    How am I supposed to implement a competing web socket server?

    I appreciate any inputs
    Thanks a lot in advance

    1 Reply Last reply
    0
    • G Offline
      G Offline
      gyll
      wrote on last edited by
      #2

      -i guess you can just keep a single socket and for all data you receive, and then spawn a new thread for each incoming data and send it the data; then, when the thread completes its job, make it send a signal to the main thread with the result, and from the main thread send out the result on the socket.-

      scratch the above, it's nonsense, you need multiple sockets, sorry

      1 Reply Last reply
      0
      • P Offline
        P Offline
        primem0ver
        wrote on last edited by
        #3

        I have not used the QWebSocketServer so I am not familiar with it. However, I have used QTcpSocket and QTcpServer classes which I imagine are similar in structure. If not, then you could consider using them because they are capable of the tasks you seem to be wanting.

        Most examples I found when researching how to do this followed a basic pattern: generally speaking, they accomplished what you seem to be doing by having the server (listener) itself running in the main thread. Then, when an incoming connection comes, a new thread is created for the incoming connection so that all communication is done separately from the server's thread. That way it can continue to listen and repeat this thread spawning for each new connection that comes in.

        There are a few different ways to do this and examples do exist around the web. If I remember correctly, some move the socket manually using a command that I don't recall (because I used a different method). Some use the socket's identifier (an int ptr or an int) to actually pull (or reproduce) the socket in the new thread. The example I loosely followed used this latter method and can be found "here":http://www.bogotobogo.com/Qt/Qt5_QTcpServer_Multithreaded_Client_Server.php. I know this doesn't use the same classes but the basic idea is probably reproducible using the classes you are currently using (since most of these networking classes are based on the same abstract/base classes).

        1 Reply Last reply
        0
        • N Offline
          N Offline
          nohoo77
          wrote on last edited by
          #4

          Thank you for your response.

          I’d prefer using websockets over standard tcp sockets (due to easier firewall configuration and the possibility of bi-directional communication)

          Of course I tried spawning a thread on each new connection and have the QWebSocket live in this separate thread. But this does not seem to work:

          I am instantiating a new QThread-derivitive object in my "newConnection" slot.
          In this separate thread (run-message) I am calling nextPendingConnection to get a new QWebSocket, which I am moving to my new thread ("moveToThread"). Then I am hooking up all the websocket’s relevant signals to my corresponding slots within this thread.
          Inside my textMessageReceived(QString) slot I am calling QWebSocket::sendTextMessage(“blabla”) to send some response back to the client.

          Now all the QWebSocket signals (ex. textMessageReceived) are triggering the right slot in my new thread. The only problem is, that the response I am sending does not find its way back to the client anymore. (This works in the single threaded scenario).

          Side note: When I am calling QObject::moveToThread(myThread) I get the following warning:
          “QSocketNotifier: socket notifiers cannot be enabled from another thread”
          Since the documentation says: “Note: The returned QWebSocket object cannot be used from another thread” I wonder if the common approach of spawning new threads on incoming connections is even possible with the current QT QWebSockets implementation?

          1 Reply Last reply
          0
          • P Offline
            P Offline
            primem0ver
            wrote on last edited by
            #5

            I am not sure about the answer to your final question since I have not used the QWebSocket version. However I am also not sure why you feel that standard tcp sockets are easier than web sockets. Tcp sockets are also bidirectional and in my experience, nowadays you don't have to worry about your firewall. The first time I ran my app, the standard windows firewall detected the attempt and asked me if I wanted to allow the app to use the socket. It automatically configured the port and application for later use.

            After doing a quick read of the purpose of the WebSocket protocol, I think the only major difference is that communication can go both ways at the same time. It also doesn't look like you can transfer the socket to a new thread by using the socket id. Too bad because that makes Tcp sockets easier to bypass the having to transfer the socket manually to another thread.

            1 Reply Last reply
            0
            • J Offline
              J Offline
              Josueh_Rodrigues
              wrote on last edited by
              #6

              // ClientThread.h

              #include <QThread>
              #include <QWebSocket>

              class ClientThread : public QThread {
              Q_OBJECT

              public:
              explicit ClientThread(QWebSocket *socket, QObject parent = nullptr);
              ~ClientThread() override;
              QWebSocket
              getSocket() const;
              void processTextMessage(const QString &message);

              signals:
              void messageReceivedFromClient(const QString &message);

              protected:
              void run() override;

              private:
              QWebSocket *m_socket;
              };

              // ClientThread.cpp

              #include "clientthread.h"

              ClientThread::ClientThread(QWebSocket *socket, QObject *parent)
              : QThread(parent), m_socket(socket) {
              connect(m_socket, &QWebSocket::textMessageReceived, this, &ClientThread::processTextMessage);
              }

              ClientThread::~ClientThread() {
              m_socket->close();
              delete m_socket;
              }

              QWebSocket* ClientThread::getSocket() const {
              return m_socket;
              }

              void ClientThread::processTextMessage(const QString &message) {

              qDebug() << "Received message in ClientThread:" << message;
              
              // Aqui você pode processar a mensagem recebida pelo cliente
              // e possivelmente encaminhá-la para o MilharShow ou para outros clientes
              // Por exemplo:
              emit messageReceivedFromClient(message);
              

              }

              void ClientThread::run() {
              exec();
              }

              // MilharShow.h

              #include "clientthread.h"
              #include <QtCore/QObject>
              #include <QList>
              #include <QtNetwork/QSslError>

              #include "QtWebSockets/QWebSocketServer"
              #include "QtWebSockets/QWebSocket"
              #include <QtCore/QDebug>
              #include <QtCore/QFile>
              #include <QtNetwork/QSslCertificate>
              #include <QtNetwork/QSslKey>

              class MilharShow : public QObject {
              Q_OBJECT

              public:
              explicit MilharShow(quint16 port, QObject *parent = nullptr);
              ~MilharShow() override;
              QString getIdentifier(QWebSocket *peer) const;

              private slots:
              void onNewConnection();
              void processMessage(const QString &message);
              void removeClient(ClientThread *client);
              void onSslErrors(const QList<QSslError> &);

              private:
              QWebSocketServer *m_pWebSocketServer;
              QList<ClientThread *> m_clients;
              };

              // MilharShow.cpp

              #include "milharshow.h"
              #include <QTextStream> // ou <QtCore/QTextStream> dependendo da organização do seu projeto

              MilharShow::MilharShow(quint16 port, QObject *parent)
              : QObject(parent), m_pWebSocketServer(new QWebSocketServer(QStringLiteral("Chat Server"),
              QWebSocketServer::SecureMode, this)) {
              QSslConfiguration sslConfiguration;
              //QFile certFile(QStringLiteral("/usr/qt-projetos/milharshow/milharshow.cert"));
              //QFile keyFile(QStringLiteral("/usr/qt-projetos/milharshow/milharshow.key"));
              QFile certFile(QStringLiteral("/etc/letsencrypt/live/jrprogrammer.com.br/fullchain.pem"));
              QFile keyFile(QStringLiteral("/etc/letsencrypt/live/jrprogrammer.com.br/privkey.pem"));
              certFile.open(QIODevice::ReadOnly);
              keyFile.open(QIODevice::ReadOnly);
              QSslCertificate certificate(&certFile, QSsl::Pem);
              QSslKey sslKey(&keyFile, QSsl::Rsa, QSsl::Pem);
              certFile.close();
              keyFile.close();
              sslConfiguration.setPeerVerifyMode(QSslSocket::VerifyNone);
              sslConfiguration.setLocalCertificate(certificate);
              sslConfiguration.setPrivateKey(sslKey);
              sslConfiguration.setProtocol (QSsl::TlsV1SslV3);
              m_pWebSocketServer->setSslConfiguration(sslConfiguration);

              if (m_pWebSocketServer->listen(QHostAddress::Any, port)) {
                  connect(m_pWebSocketServer, &QWebSocketServer::newConnection, this, &MilharShow::onNewConnection);
                  connect(m_pWebSocketServer, &QWebSocketServer::sslErrors, this, &MilharShow::onSslErrors);        
              }
              

              }

              MilharShow::~MilharShow() {
              m_pWebSocketServer->close();

              // Como os objetos QWebSocket são gerenciados pelas threads,
              // não excluímos explicitamente os sockets aqui.
              // Em vez disso, encerramos as threads para garantir que elas liberem recursos.
              for (ClientThread *client : qAsConst(m_clients)) {
                  client->quit(); // Solicita a finalização da thread
                  client->wait(); // Aguarda até que a thread seja encerrada
              }
              

              }

              QString MilharShow::getIdentifier(QWebSocket *peer) const {
              return QStringLiteral("%1:%2").arg(peer->peerAddress().toString(),
              QString::number(peer->peerPort()));
              }

              void MilharShow::onNewConnection() {
              auto pSocket = m_pWebSocketServer->nextPendingConnection();
              QTextStream(stdout) << getIdentifier(pSocket) << " connected!\n";

              ClientThread *clientThread = new ClientThread(pSocket);
              connect(clientThread, &ClientThread::finished, clientThread, &ClientThread::deleteLater);
              connect(clientThread, &ClientThread::messageReceivedFromClient, this, &MilharShow::processMessage);
              connect(clientThread, &ClientThread::finished, this, [this, clientThread] { removeClient(clientThread); });
              
              m_clients.append(clientThread);
              clientThread->start();
              

              }

              void MilharShow::processMessage(const QString &message) {
              QWebSocket *pSender = qobject_cast<QWebSocket *>(sender());
              if (!pSender) {
              qDebug() << "Vai retornar!" << pSender ;
              //return;
              }

              QByteArray byteArray = message.toUtf8();
              
              // Adicione a saída de debug para verificar se a função é chamada
              qDebug() << "Received message:" << message;
              
              for (ClientThread *clientThread : qAsConst(m_clients)) {
                  QWebSocket* socket = clientThread->getSocket();
                  if (socket && socket->isValid() && socket != pSender) {
                      socket->sendTextMessage(QString::fromUtf8(byteArray));
                  }
              }
              

              }

              void MilharShow::removeClient(ClientThread *client) {
              m_clients.removeAll(client);
              }

              void MilharShow::onSslErrors(const QList<QSslError> &errors) {
              qDebug() << "SSL errors occurred:";
              for (const QSslError &error : errors) {
              qDebug() << "Error: " << error.errorString();
              }
              }

              #include <QtCore/QCoreApplication>
              #include <QDebug>
              #include "milharshow.h"

              int main(int argc, char *argv[])
              {
              QCoreApplication a(argc, argv);

              MilharShow server(8000);
              
              //Mostra onde está procurando os drivers, libs
              foreach (const QString &path, a.libraryPaths()) qDebug() << path;
              
              return a.exec();
              

              }

              J 1 Reply Last reply
              0
              • J Josueh_Rodrigues

                // ClientThread.h

                #include <QThread>
                #include <QWebSocket>

                class ClientThread : public QThread {
                Q_OBJECT

                public:
                explicit ClientThread(QWebSocket *socket, QObject parent = nullptr);
                ~ClientThread() override;
                QWebSocket
                getSocket() const;
                void processTextMessage(const QString &message);

                signals:
                void messageReceivedFromClient(const QString &message);

                protected:
                void run() override;

                private:
                QWebSocket *m_socket;
                };

                // ClientThread.cpp

                #include "clientthread.h"

                ClientThread::ClientThread(QWebSocket *socket, QObject *parent)
                : QThread(parent), m_socket(socket) {
                connect(m_socket, &QWebSocket::textMessageReceived, this, &ClientThread::processTextMessage);
                }

                ClientThread::~ClientThread() {
                m_socket->close();
                delete m_socket;
                }

                QWebSocket* ClientThread::getSocket() const {
                return m_socket;
                }

                void ClientThread::processTextMessage(const QString &message) {

                qDebug() << "Received message in ClientThread:" << message;
                
                // Aqui você pode processar a mensagem recebida pelo cliente
                // e possivelmente encaminhá-la para o MilharShow ou para outros clientes
                // Por exemplo:
                emit messageReceivedFromClient(message);
                

                }

                void ClientThread::run() {
                exec();
                }

                // MilharShow.h

                #include "clientthread.h"
                #include <QtCore/QObject>
                #include <QList>
                #include <QtNetwork/QSslError>

                #include "QtWebSockets/QWebSocketServer"
                #include "QtWebSockets/QWebSocket"
                #include <QtCore/QDebug>
                #include <QtCore/QFile>
                #include <QtNetwork/QSslCertificate>
                #include <QtNetwork/QSslKey>

                class MilharShow : public QObject {
                Q_OBJECT

                public:
                explicit MilharShow(quint16 port, QObject *parent = nullptr);
                ~MilharShow() override;
                QString getIdentifier(QWebSocket *peer) const;

                private slots:
                void onNewConnection();
                void processMessage(const QString &message);
                void removeClient(ClientThread *client);
                void onSslErrors(const QList<QSslError> &);

                private:
                QWebSocketServer *m_pWebSocketServer;
                QList<ClientThread *> m_clients;
                };

                // MilharShow.cpp

                #include "milharshow.h"
                #include <QTextStream> // ou <QtCore/QTextStream> dependendo da organização do seu projeto

                MilharShow::MilharShow(quint16 port, QObject *parent)
                : QObject(parent), m_pWebSocketServer(new QWebSocketServer(QStringLiteral("Chat Server"),
                QWebSocketServer::SecureMode, this)) {
                QSslConfiguration sslConfiguration;
                //QFile certFile(QStringLiteral("/usr/qt-projetos/milharshow/milharshow.cert"));
                //QFile keyFile(QStringLiteral("/usr/qt-projetos/milharshow/milharshow.key"));
                QFile certFile(QStringLiteral("/etc/letsencrypt/live/jrprogrammer.com.br/fullchain.pem"));
                QFile keyFile(QStringLiteral("/etc/letsencrypt/live/jrprogrammer.com.br/privkey.pem"));
                certFile.open(QIODevice::ReadOnly);
                keyFile.open(QIODevice::ReadOnly);
                QSslCertificate certificate(&certFile, QSsl::Pem);
                QSslKey sslKey(&keyFile, QSsl::Rsa, QSsl::Pem);
                certFile.close();
                keyFile.close();
                sslConfiguration.setPeerVerifyMode(QSslSocket::VerifyNone);
                sslConfiguration.setLocalCertificate(certificate);
                sslConfiguration.setPrivateKey(sslKey);
                sslConfiguration.setProtocol (QSsl::TlsV1SslV3);
                m_pWebSocketServer->setSslConfiguration(sslConfiguration);

                if (m_pWebSocketServer->listen(QHostAddress::Any, port)) {
                    connect(m_pWebSocketServer, &QWebSocketServer::newConnection, this, &MilharShow::onNewConnection);
                    connect(m_pWebSocketServer, &QWebSocketServer::sslErrors, this, &MilharShow::onSslErrors);        
                }
                

                }

                MilharShow::~MilharShow() {
                m_pWebSocketServer->close();

                // Como os objetos QWebSocket são gerenciados pelas threads,
                // não excluímos explicitamente os sockets aqui.
                // Em vez disso, encerramos as threads para garantir que elas liberem recursos.
                for (ClientThread *client : qAsConst(m_clients)) {
                    client->quit(); // Solicita a finalização da thread
                    client->wait(); // Aguarda até que a thread seja encerrada
                }
                

                }

                QString MilharShow::getIdentifier(QWebSocket *peer) const {
                return QStringLiteral("%1:%2").arg(peer->peerAddress().toString(),
                QString::number(peer->peerPort()));
                }

                void MilharShow::onNewConnection() {
                auto pSocket = m_pWebSocketServer->nextPendingConnection();
                QTextStream(stdout) << getIdentifier(pSocket) << " connected!\n";

                ClientThread *clientThread = new ClientThread(pSocket);
                connect(clientThread, &ClientThread::finished, clientThread, &ClientThread::deleteLater);
                connect(clientThread, &ClientThread::messageReceivedFromClient, this, &MilharShow::processMessage);
                connect(clientThread, &ClientThread::finished, this, [this, clientThread] { removeClient(clientThread); });
                
                m_clients.append(clientThread);
                clientThread->start();
                

                }

                void MilharShow::processMessage(const QString &message) {
                QWebSocket *pSender = qobject_cast<QWebSocket *>(sender());
                if (!pSender) {
                qDebug() << "Vai retornar!" << pSender ;
                //return;
                }

                QByteArray byteArray = message.toUtf8();
                
                // Adicione a saída de debug para verificar se a função é chamada
                qDebug() << "Received message:" << message;
                
                for (ClientThread *clientThread : qAsConst(m_clients)) {
                    QWebSocket* socket = clientThread->getSocket();
                    if (socket && socket->isValid() && socket != pSender) {
                        socket->sendTextMessage(QString::fromUtf8(byteArray));
                    }
                }
                

                }

                void MilharShow::removeClient(ClientThread *client) {
                m_clients.removeAll(client);
                }

                void MilharShow::onSslErrors(const QList<QSslError> &errors) {
                qDebug() << "SSL errors occurred:";
                for (const QSslError &error : errors) {
                qDebug() << "Error: " << error.errorString();
                }
                }

                #include <QtCore/QCoreApplication>
                #include <QDebug>
                #include "milharshow.h"

                int main(int argc, char *argv[])
                {
                QCoreApplication a(argc, argv);

                MilharShow server(8000);
                
                //Mostra onde está procurando os drivers, libs
                foreach (const QString &path, a.libraryPaths()) qDebug() << path;
                
                return a.exec();
                

                }

                J Offline
                J Offline
                Josueh_Rodrigues
                wrote on last edited by
                #7

                @Josueh_Rodrigues A única quetão a ser resolvida é que está emitindo replicado para o próprio cliente em si. E isso está acontecendo porque o Sender está chegando com o valor igual a nulo. Se alguém sabe a solução para este fato, já vai ficar bem melhor o código de exemplo.

                1 Reply Last reply
                0

                • Login

                • Login or register to search.
                • First post
                  Last post
                0
                • Categories
                • Recent
                • Tags
                • Popular
                • Users
                • Groups
                • Search
                • Get Qt Extensions
                • Unsolved