Trying to implement a multi thread server in Qt, just consider this:
in SocketThread class:
SocketThread::SocketThread(qintptr descriptor, QObject *parent)
: QThread(parent), socketDescriptor(descriptor)
{
socket = new QTcpSocket();
socket->setSocketDescriptor(socketDescriptor);
socket->moveToThread(this);
connect(socket, &QTcpSocket::readyRead, [this]() { qDebug() << socket->readAll(); }); //trying to read messages from clients
}
in Connection class: creating thread in incomingConnection()
void Connection::incomingConnection(qintptr socketDescriptor)
{
SocketThread *socketThread = new SocketThread(socketDescriptor);
socketThread->start();
connect(socketThread, &SocketThread::started, [&]() { socketThread->socket->write("Hello!"); }); //write a message to client when thread is created
}
Strange thing is, if I add this line:
connect(socketThread, &SocketThread::started, & { socketThread->socket->write("Hello!"); }); //write a message to client when thread is created
Then the socket will not read message from client. If I remove that line, the socket will read message from client.
I want the server to send message to client when a thread is created and to read message from client as well. How can do solve the problem?
Edit:
SocketThread header file:
class SocketThread : public QThread
{
Q_OBJECT
public:
SocketThread(qintptr descriptor, QObject *parent = 0);
~SocketThread();
QTcpSocket *socket;
qintptr socketDescriptor;
};
in the Connection constructor:
Connection::Connection(QObject *parent) : QTcpServer(parent)
{
this->listen(QHostAddress::Any, 6666);
}
And creating a connection object in the main.cpp file. That's really like all the code.
Related
I have a class with a serial interface which transmit some data and emit a MessageReceived signal when some valid data are received:
Serial.c
void Serial::on_serialPort_readData()
{
... Other stuff...
else
{
qDebug() << "Received in serial";
emit MessageReceived(_mReceivedData);
}
}
}
And I have my main window which opens the serial connection and start the message transaction. But I have to receive a lot of data and the transmission will take a while, so I decided to use a QThread for the processing of the received data.
I created a thread and a worker in my mainwindow.cpp (code from here):
mainwindow.cpp
_mThread = new QThread(this);
_mThreadWorker = new Worker();
_mThreadWorker->moveToThread(_mThread);
// Like in the example
connect(_mThread, &QThread::started, _mThreadWorker, &Worker::Progress);
connect(_mThreadWorker, &Worker::Finished, _mThread, &QThread::quit);
connect(_mThreadWorker, &Worker::Finished, _mThreadWorker, &Worker::deleteLater);
connect(_mThread, &QThread::finished, _mThread, &QObject::deleteLater);
connect(_mProtocolInterface, &Serial::MessageReceived, _mThreadWorker, &Worker::UpdateData);
_mThread->start();
// Send the first packet so the receiver will answer
The serial interface will send the data in different packages and I read here that it is a bad idea to use the serial port in a seperate thread, so I decided to use the serial port in the main thread and connect the MessageReceived signal with an UpdateData slot of my worker:
worker.h
class Worker : public QObject
{
Q_OBJECT
...
public slots:
void Progress(void);
void UpdateData(QByteArray Data);
public:
Worker(QObject* parent = nullptr);
~Worker();
...
};
worker.cpp
void Worker::UpdateData(QByteArray Data)
{
qDebug() << "Received in Worker";
_mData = Data;
}
void Worker::Progress(void)
{
while(_mActive)
{
switch(_mState)
{
case STATE_INIT:
{
_mState = STATE_INIT;
// Just a idle to simulate the data transfer during debugging
break;
}
}
}
}
But only the slot on_serialPort_readData gets called after I receive the data from the receiver:
>> Received in serial
So why the slot UpdateData of the class Worker doesn´t get called?
See EDIT1 at the end of the question for a possible solution - It would be great if somebody could comment on my interpretation, so that I can understand better what's happening
I'm writing a simple TCP client, based on QTcpSocket and managed by a QStateMachine (connect to server -> transmit data -> if disconnected for any reason, reconnect to server).
I noticed that if the connection is shut down on the server side (client is notified with RemoteHostClosedError), after reconnection the QTcpSocket write() method succeeds but no data is transmitted on the wire - nothing is received by the server, and the bytesWritten() signal on the client side does not fire up.
I found in the documentation for error() signal (https://doc.qt.io/qt-5/qabstractsocket.html#error) that
When this signal is emitted, the socket may not be ready for a reconnect attempt. In that case,
attempts to reconnect should be done from the event loop".
I think I'm already ok with that, as the reconnection happens in one of the QStateMachine states, and QStateMachine should have its own event loop according to the QT docs.
Below some simplified code to reproduce the issue (sorry, not so minimal but I could not find a simpler way to show the problem):
testclient.h
#ifndef TESTCLIENT_H
#define TESTCLIENT_H
#include <QObject>
#include <QTcpSocket>
#include <QDebug>
#include <QStateMachine>
class TestClient : public QObject
{
Q_OBJECT
public:
explicit TestClient(QObject *parent = nullptr);
public slots:
void start();
signals:
// FSM events
void fsmEvtConnected();
void fsmEvtError();
private slots:
void onSocketConnected(); // Notify connection to TCP server
void onSocketDisconnected(); // Notify disconnection from TCP server
void onSocketBytesWritten(qint64 bytes); // Notify number of bytes written to TCP server
void onSocketError(QAbstractSocket::SocketError err);
// FSM state enter/exit actions
void onfsmConnectEntered();
void onfsmTransmitEntered();
void onfsmTransmitExited();
private:
// Member variables
QTcpSocket* m_socket; // TCP socket used for communications to server
QStateMachine* m_clientFsm; // FSM defining general client behaviour
private:
void createClientFsm(); // Create client FSM
};
#endif // TESTCLIENT_H
testclient.cpp
#include "testclient.h"
#include <QState>
#include <QThread> // Sleep
//-----------------------------------------------------------------------------
// PUBLIC METHODS
//-----------------------------------------------------------------------------
TestClient::TestClient(QObject *parent) : QObject(parent)
{
m_socket = new QTcpSocket(this);
connect(m_socket, SIGNAL(connected()),this, SLOT(onSocketConnected()));
connect(m_socket, SIGNAL(disconnected()),this, SLOT(onSocketDisconnected()));
connect(m_socket, SIGNAL(bytesWritten(qint64)),this, SLOT(onSocketBytesWritten(qint64)));
connect(m_socket, SIGNAL(error(QAbstractSocket::SocketError)), this, SLOT(onSocketError(QAbstractSocket::SocketError)));
}
void TestClient::start()
{
createClientFsm();
m_clientFsm->start();
}
//-----------------------------------------------------------------------------
// TCP CONNECTION MANAGEMENT SLOTS
//-----------------------------------------------------------------------------
void TestClient::onSocketConnected()
{
qDebug() << "connected...";
emit fsmEvtConnected();
}
void TestClient::onSocketDisconnected()
{
qDebug() << "disconnected...";
emit fsmEvtError();
}
void TestClient::onSocketBytesWritten(qint64 bytes)
{
qDebug() << bytes << " bytes written...";
}
void TestClient::onSocketError(QAbstractSocket::SocketError err)
{
qDebug() << "socket error " << err;
}
//-----------------------------------------------------------------------------
// FSM MANAGEMENT
//-----------------------------------------------------------------------------
void TestClient::createClientFsm()
{
m_clientFsm = new QStateMachine(this);
// Create states
QState* sConnect = new QState();
QState* sTransmit = new QState();
// Add transitions between states
sConnect->addTransition(this, SIGNAL(fsmEvtConnected()), sTransmit);
sTransmit->addTransition(this, SIGNAL(fsmEvtError()), sConnect);
// Add entry actions to states
connect(sConnect, SIGNAL(entered()), this, SLOT(onfsmConnectEntered()));
connect(sTransmit, SIGNAL(entered()), this, SLOT(onfsmTransmitEntered()));
// Add exit actions to states
connect(sTransmit, SIGNAL(exited()), this, SLOT(onfsmTransmitExited()));
// Create state machine
m_clientFsm->addState(sConnect);
m_clientFsm->addState(sTransmit);
m_clientFsm->setInitialState(sConnect);
}
void TestClient::onfsmConnectEntered()
{
qDebug() << "connecting...";
m_socket->connectToHost("localhost", 11000);
// Wait for connection result
if(!m_socket->waitForConnected(10000))
{
qDebug() << "Error: " << m_socket->errorString();
emit fsmEvtError();
}
}
void TestClient::onfsmTransmitEntered()
{
qDebug() << "sending data...";
m_socket->write("TEST MESSAGE");
}
void TestClient::onfsmTransmitExited()
{
qDebug() << "waiting before reconnection attempt...";
QThread::sleep(2);
}
main.cpp
#include <QCoreApplication>
#include "testclient.h"
int main(int argc, char *argv[])
{
QCoreApplication a(argc, argv);
TestClient client(&a);
client.start();
return a.exec();
}
To test, you can just launch netcat (nc -l -p 11000) , then close the nc process after receiving TEST MESSAGE and finally relaunch it again. The second time, TEST MESSAGE is not received, and we don't have the onSocketBytesWritten() printout, see below:
connecting...
connected...
sending data...
12 bytes written... <<<<<<<<<< Correct transmission, event fires up
socket error QAbstractSocket::RemoteHostClosedError
disconnected...
waiting before reconnection attempt...
connecting...
connected...
sending data... <<<<<<<<<< No transmission, event does not fire up, no socket errors!
EDIT1: I found out that if I create the QTcpSocket on connection and destroy it on disconnection, the problem does not happen. Is this the expected/proper way to use sockets?
Wouldn't it be possible instead to create the socket just once and just connect/disconnect? Maybe it is just a matter of flushing or cleaning up in a specific manner, but I could not find it so far.
Here are the modifications that make the code above work on server-side disconnection:
Move socket creation from class constructor to onfsmConnectEntered() - handler for entry in the "Connect" QState:
void TestClient::onfsmConnectEntered()
{
m_socket = new QTcpSocket(this);
connect(m_socket, SIGNAL(connected()),this, SLOT(onSocketConnected()));
connect(m_socket, SIGNAL(disconnected()),this, SLOT(onSocketDisconnected()));
connect(m_socket, SIGNAL(bytesWritten(qint64)),this, SLOT(onSocketBytesWritten(qint64)));
connect(m_socket, SIGNAL(error(QAbstractSocket::SocketError)), this, SLOT(onSocketError(QAbstractSocket::SocketError)));
qDebug() << "connecting...";
m_socket->connectToHost("localhost", 11000);
// The rest of the method is the same
}
Delete the socket on disconnection, so that it is deallocated and will be created again on reconnection:
void TestClient::onSocketDisconnected()
{
qDebug() << "disconnected...";
m_socket->deleteLater();
m_socket = nullptr;
emit fsmEvtError();
}
Do not use waitForX methods as they block the event loop and prevent them from using that resource as the signals do not do their job correctly or the QStateMachine.
Considering the above, the solution is:
void TestClient::onfsmConnectEntered()
{
m_socket->connectToHost("localhost", 11000);
}
But even so your code has errors since it does not consider other cases such as:
If when you start the client the server is not running, your application will try to connect the error will be launched and nothing else.
If the server fails for a longer time than the 10000 ms timeout set to waitForConnected(), the same will happen as in the previous case.
Then the idea is to try to connect until you are sure of the connection and that can be done through a QTimer with an appropriate period.
testclient.h
#ifndef TESTCLIENT_H
#define TESTCLIENT_H
#include <QObject>
class QTcpSocket;
class QStateMachine;
class QTimer;
#include <QAbstractSocket>
class TestClient : public QObject
{
Q_OBJECT
public:
explicit TestClient(QObject *parent = nullptr);
public slots:
void start();
signals:
// FSM events
void fsmEvtConnected();
void fsmEvtError();
private slots:
void onSocketConnected(); // Notify connection to TCP server
void onSocketDisconnected(); // Notify disconnection from TCP server
void onSocketBytesWritten(qint64 bytes); // Notify number of bytes written to TCP server
void onSocketError(QAbstractSocket::SocketError err);
// FSM state enter/exit actions
void onfsmConnectEntered();
void onfsmTransmitEntered();
private:
// Member variables
QTcpSocket* m_socket; // TCP socket used for communications to server
QStateMachine* m_clientFsm; // FSM defining general client behaviour
QTimer* m_timer;
private:
void createClientFsm(); // Create client FSM
void tryConnect();
};
#endif // TESTCLIENT_H
testclient.cpp
#include "testclient.h"
#include <QState>
#include <QStateMachine>
#include <QTcpSocket>
#include <QThread> // Sleep
#include <QTimer>
//-----------------------------------------------------------------------------
// PUBLIC METHODS
//-----------------------------------------------------------------------------
TestClient::TestClient(QObject *parent) : QObject(parent)
{
m_socket = new QTcpSocket(this);
m_timer = new QTimer(this);
m_timer->setInterval(100);
connect(m_timer, &QTimer::timeout, this, &TestClient::tryConnect);
connect(m_socket, &QAbstractSocket::connected,this, &TestClient::onSocketConnected);
connect(m_socket, &QAbstractSocket::disconnected,this, &TestClient::onSocketDisconnected);
connect(m_socket, &QIODevice::bytesWritten,this, &TestClient::onSocketBytesWritten);
connect(m_socket, QOverload<QAbstractSocket::SocketError>::of(&QAbstractSocket::error), this, &TestClient::onSocketError);
}
void TestClient::start()
{
createClientFsm();
m_clientFsm->start();
}
//-----------------------------------------------------------------------------
// TCP CONNECTION MANAGEMENT SLOTS
//-----------------------------------------------------------------------------
void TestClient::onSocketConnected()
{
m_timer->stop();
qDebug() << "connected...";
emit fsmEvtConnected();
}
void TestClient::onSocketDisconnected()
{
qDebug() << "disconnected...";
emit fsmEvtError();
}
void TestClient::onSocketBytesWritten(qint64 bytes)
{
qDebug() << bytes << " bytes written...";
}
void TestClient::onSocketError(QAbstractSocket::SocketError err)
{
qDebug() << "socket error " << err;
}
//-----------------------------------------------------------------------------
// FSM MANAGEMENT
//-----------------------------------------------------------------------------
void TestClient::createClientFsm()
{
m_clientFsm = new QStateMachine(this);
// Create states
QState* sConnect = new QState();
QState* sTransmit = new QState();
// Add transitions between states
sConnect->addTransition(this, SIGNAL(fsmEvtConnected()), sTransmit);
sTransmit->addTransition(this, SIGNAL(fsmEvtError()), sConnect);
// Add entry actions to states
connect(sConnect, &QAbstractState::entered, this, &TestClient::onfsmConnectEntered);
connect(sTransmit, &QAbstractState::entered, this, &TestClient::onfsmTransmitEntered);
// Create state machine
m_clientFsm->addState(sConnect);
m_clientFsm->addState(sTransmit);
m_clientFsm->setInitialState(sConnect);
}
void TestClient::tryConnect(){
m_socket->connectToHost("localhost", 11000);
}
void TestClient::onfsmConnectEntered()
{
m_timer->start();
}
void TestClient::onfsmTransmitEntered()
{
qDebug() << "sending data...";
m_socket->write("TEST MESSAGE");
}
I am having trouble to connect a Signal to a Slot in the following code:
#include "myserver.h"
MyServer::MyServer(QObject *parent) :
QTcpServer(parent)
{
}
void MyServer::StartServer()
{
if(listen(QHostAddress::Any, 45451))
{
qDebug() << "Server: started";
emit servComando("Server: started");
}
else
{
qDebug() << "Server: not started!";
emit servComando("Server: not started!");
}
}
void MyServer::incomingConnection(int handle)
{
emit servComando("server: incoming connection, make a client...");
// at the incoming connection, make a client
MyClient *client = new MyClient(this);
client->SetSocket(handle);
//clientes.append(client);
//clientes << client;
connect(client, SIGNAL(cliComando(const QString&)),this, SLOT(servProcesarComando(const QString&)));
// para probar
emit client->cliComando("prueba");
}
void MyServer::servProcesarComando(const QString& texto)
{
emit servComando(texto);
}
The emit client->cliComando("prueba"); works, but the real "emits" don't.
The console does not show any connection error, and the QDebug texts shows everything works well.
Original code was copied from http://www.bogotobogo.com/cplusplus/sockets_server_client_QT.php
I found the problem, Im sending a signal BEFORE connecting:
client->SetSocket(handle);
sends the signal, and Im CONNECTing after it... Now it is:
// at the incoming connection, make a client
MyClient *client = new MyClient(this);
connect(client, SIGNAL(cliComando(const QString&)),this, SLOT(servProcesarComando(const QString&)));
client->SetSocket(handle);
And it works. I noticed it after read the following:
13. Put all connect statements before functions calls that may fire their signals, to ensure that the connections are made before the signals are fired. For example:
_myObj = new MyClass();
connect(_myObj, SIGNAL(somethingHappend()), SLOT(doSomething()));
_myObj->init();
not
_myObj = new MyClass();
_myObj->init();
connect(_myObj, SIGNAL(somethingHappend()), SLOT(doSomething()));
I found it at https://samdutton.wordpress.com/2008/10/03/debugging-signals-and-slots-in-qt/
Anyway, thanks for your answers!
I am writing a client server program that server is multi thread , The code compile without any error, but it doesn't show any message from client.
just it run up to the "qDebug() << " Client connected";"
Here is my code .I would be grateful if you can say where is the problem.
myclient.cpp
#include "myclient.h"
#include "QTcpsocket"
#include "QTcpServer"
#include "mainwindow.h"
#include "QHostAddress"
myclient::myclient(QObject* parent): QObject(parent)
{
}
void myclient::start(QString address, quint16 port)
{
QHostAddress LocalHost;
LocalHost.setAddress(address);
m_client.connectToHost(LocalHost, 6666);
QObject::connect(&m_client, SIGNAL(connected()),this, SLOT(startTransfer()));
}
void myclient::startTransfer()
{
m_client.write("Hello", 5);
}
mythread.cpp
#include "mythread.h"
#include "myserver.h"
mythread::mythread(QTcpSocket*, QObject *parent) :
QThread(parent)
{
}
void mythread::run()
{
qDebug() << " Thread started";
if (m_client)
{
connect(m_client, SIGNAL(connected()), this, SLOT(readyRead()), Qt::DirectConnection);
}
qDebug() << " Client connected";
exec();
}
void mythread::readyRead()
{
QByteArray Data = m_client->readAll();
qDebug()<< " Data in: " << Data;
m_client->write(Data);
}
void mythread::disconnected()
{
qDebug() << " Disconnected";
m_client->deleteLater();
exit(0);
}
myserver.cpp
#include "myserver.h"
#include "mythread.h"
myserver::myserver(QObject *parent) :
QObject(parent)
{
}
void myserver::startserver()
{
connect(&m_server,SIGNAL(newConnection()), this ,SLOT(newConnection()));
int port = 6666;
if(m_server.listen(QHostAddress::Any, port))
{
qDebug() << "Listening to port " ;
}
else
{
qDebug() << "Could not start server "<<m_server.errorString();
}
}
void myserver::newConnection()
{
m_client = m_server.nextPendingConnection();
qDebug() << " Connecting...";
mythread *thread = new mythread(m_client,this);
thread->start();
}
Documentation about nextPendingConnection() says:
Note: The returned QTcpSocket object cannot be used from another
thread. If you want to use an incoming connection from another thread,
you need to override incomingConnection().
So, you can't use that socket in another thread. As the docs say, you can subclass QTcpServer and override incomingConnection(), this method is invoked whenever a client tries to connect to your server.
incomingConnection() method provides a socket descriptor (just like regular file descriptors). And then you can pass that socket descriptor to another thread and create the QTcpSocket completely over there.
Inside that thread, you would need something like this:
QTcpSocket client = new QTcpSocket();
client.setSocketDescriptor(sockId);
// Now, you can use this socket as a connected socket.
// Make sure to connect its ready read signal to your local slot.
Once you get in newConnection the client is already connected, and you just need to start a thread that calls readAll and then replies.
There's no need to wait for a connected() signal again.
EDIT
The QSocket class is designed to work asynchronously on the main thread, based on events. From the documentation:
Warning: QSocket is not suitable for use in threads. If you need to uses sockets in threads use the lower-level QSocketDevice class.
What I'm having is a strange problem in typical scenario: QTcpServer's method incomingConnection is overrided in custom class, and any received connection is planned for processing in separate thread on QThreadPool.
Server:
void FooS::incomingConnection(qintptr socketDescriptor)
{
QThreadPool *thread_pool = QThreadPool::globalInstance();
FooSocket *fs = new FooSocket();
fs->setSocket(socketDescriptor);
thread_pool->start(fs);
}
Task:
class FooSocket: public QObject, public QRunnable;
...
private slots:
void connectionIncomingData();
...
void FooSocket::run() {
QTcpSocket *socket = new QTcpSocket();
qDebug() << "SD: " << socketDescriptor; // is correct
if (!socket->setSocketDescriptor(socketDescriptor)) {
qDebug() << "Can't set socket descriptor";
emit error(socket->error());
return;
}
// -- had no effect here
// socket->moveToThread(QThread::currentThread());
connect(socket, SIGNAL(readyRead()), this, SLOT(connectionIncomingData()));
connect(socket, SIGNAL(disconnected()), this, SLOT(connectionClosed()));
}
readyRead signal doesn't gets triggered, but socket client is confirmed (tcpdump) to send data..
After making QRunnable to spawn a QThread object with socket logics inside, and toying with setAutoDelete, moveToThread - still no effect.
In order to process events in a QRunnable, a thread needs to have its own event loop, it mustn't rely on the one from the main thread. From what you've shown in your code, your thread quickly starts, then exits without running a loop.
Try adding
QEventLoop loop;
// connect a signal to the event loop's quit() slot
loop.exec();