Threaded TCP server for persistent connection
-
We are writing an app to receive and store data from IoT devices, which acts as a tcp server. We found some examples including the official threaded fortune server one, but different from those examples, in our case the tcp connection would last forever unless:
- the tcp client is not our device (no valid response when expected),
- the device is shut down (maybe out of power),
- the app is closed (on server maintenance), or
- the connection is simply lost due to network problem.
We want to make sure that:
- the resources are released as soon as possible, and
- when the connection is closed by the app, it is closed properly (with FIN not RST).
What's more, the communication class need to interact with the ui to show information to user and respond to change in configuration like request interval.
We subclassed QTcpServer and:
void MyServer::incomingConnection(qintptr socketDescriptor) { auto *worker = new MyWorker(socketDescriptor); auto *thread = new MyThread(this); worker->moveToThread(thread); connect(thread , &MyThread::started, worker, &MyWorker::init); connect(thread , &MyThread::finished, worker, &MyWorker::deleteLater); connect(worker, &MyWorker::showMsg, this, &MyServer::showMsg); connect(this, &MyServer::setReqInterval, worker, &MyWorker::setReqInterval); thread->start(); emit setReqInterval(reqInterval); }
QThread is subclassed to stop the thread on destruction:
MyThread::~MyThread() { qDebug("in ~MyThread()"); quit(); wait(); }
Finally, in MyWorker:
MyWorker::MyWorker(qintptr socketDescriptor, QObject *parent) : QObject(parent), socket(new QTcpSocket(this)) connectionName(QString::number(socketDescriptor)) { socket->setSocketDescriptor(socketDescriptor); connect(socket, &QTcpSocket::disconnected, this->thread(), &QThread::deleteLater); } void MyWorker::init() { // make sure the sql connection is created in the correct thread with a unique name auto db = QSqlDatabase::addDatabase("QMYSQL", connectionName); /* set database connection parameters */ connect(socket, &QTcpSocket::readyRead, this, &MyWorker::dataReceived); connect(&sendTimer, &QTimer::timeout, this, &MyWorker::sendRequest); // sendTimer will be started in setReqInterval() recvTimer.setSingleShot(true); connect(&recvTimer, &QTimer::timeout, this, &MyWorker::recvTimeout); // recvTimer will be started in sendRequest() and stopped in dataReceived() if the data is valid sendRequest(); } void MyWorker::recvTimeout() { QString message; /* construct message */ emit showMsg(message); socket->disconnectFromHost(); }
The code above (modified for brevity so there may be typo) seems to work, but we are not sure if it is done correctly, especially:
- the QTcpSocket objects are created and initialized (setSocketDescriptor called) in MyWorker constructor, which runs in the ui thread,
- the worker object is deleted after the thread quits, probably when the thread object is deleted, and
- we connected the socket's disconnected signal to &QThread::deleteLater of this->thread() in MyWorker constructor (though the debug output shows that ~MyThread() is called as expected), maybe we should change it to the following?
// in MyServer::incomingConnection connect(worker, &MyWorker::destroyed, thread, &MyThread::deleteLater); // in MyWorker::MyWorker connect(socket, &QTcpSocket::disconnected, this, &MyWorker::deleteLater);
-
the QTcpSocket objects are created and initialized (setSocketDescriptor called) in MyWorker constructor, which runs in the ui thread
That's fine, setting the socket descriptor is not an intensive task
the worker object is deleted after the thread quits, probably when the thread object is deleted
It's deleted when the thread finishes, not when it is deleted. Qt internals take care of this case, even if technically the thread finished already, Qt will still run any delayed deletes
we connected the socket's disconnected signal to &QThread::deleteLater of this->thread() in MyWorker constructor
You normally
connect(thread, &MyThread::finished, thread, &MyThread::deleteLater);
and then connect a signal to&MyThread::quit
. Puttingconnect(socket, &QTcpSocket::disconnected, this->thread(), &QThread::deleteLater);
violates OOD as makes the worker dependent on living in a QThread. The worker should just emit a signal when the sochet is disconnected and you should connect that signal to&MyThread::quit
.P.S.
Using 1 thread per client will quickly add too much overhead to your server. If you don't use that many clients you can even not use threads at all. We [users of the forum] recognise Qt examples for TCP are sub-optimal and we are working on a better example: You can have a look at what we have done so far, we have a basic version and a more advanced one.