Multithreaded qwebsocket - competing server
-
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 -
-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
-
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).
-
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? -
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.
-
// ClientThread.h
#include <QThread>
#include <QWebSocket>class ClientThread : public QThread {
Q_OBJECTpublic:
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_OBJECTpublic:
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 projetoMilharShow::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();
}
-
@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.