QTcpServer won't receive client data on connected QTcpSocket
-
@dmitttri Please read https://wiki.qt.io/QThreads_general_usage
@jsulm said in QTcpServer won't receive client data on connected QTcpSocket:
@dmitttri Please read https://wiki.qt.io/QThreads_general_usage
Yep, I did it already. In the provided example,
process
is very simple:void Worker::process() { // Process. Start processing data. // allocate resources using new here qDebug("Hello World!"); emit finished(); }
This method simply return immediately, while my processing (
doWork
in the code above) requires networking (socket read/write) and several discrete steps to complete.Worker approach creates a thread object, which
run()
method will just performexec()
, allowing thread event loop to run. This is perfectly clear.Then it creates
Worker
object which hasprocess()
method, and movesWorker
object to the thread context. Nowprocess()
will execute in the thread context too.Key questions are:
- How run() and process() now coexists and executes in the same thread?"
- When
process()
is running, what's happening with therun
method? It hangs until process finishes?
It seems that in my case, where
process()
method uses Networking, I should not useprocess()
method at all.More precise, instead of the
process()
method it seems that I need a socketreadyRead
slot handler which executes in the worker thread, process incoming data, keep processing state variables, and emitfinished()
signal. once whole client related work is done. -
@jsulm said in QTcpServer won't receive client data on connected QTcpSocket:
@dmitttri Please read https://wiki.qt.io/QThreads_general_usage
Yep, I did it already. In the provided example,
process
is very simple:void Worker::process() { // Process. Start processing data. // allocate resources using new here qDebug("Hello World!"); emit finished(); }
This method simply return immediately, while my processing (
doWork
in the code above) requires networking (socket read/write) and several discrete steps to complete.Worker approach creates a thread object, which
run()
method will just performexec()
, allowing thread event loop to run. This is perfectly clear.Then it creates
Worker
object which hasprocess()
method, and movesWorker
object to the thread context. Nowprocess()
will execute in the thread context too.Key questions are:
- How run() and process() now coexists and executes in the same thread?"
- When
process()
is running, what's happening with therun
method? It hangs until process finishes?
It seems that in my case, where
process()
method uses Networking, I should not useprocess()
method at all.More precise, instead of the
process()
method it seems that I need a socketreadyRead
slot handler which executes in the worker thread, process incoming data, keep processing state variables, and emitfinished()
signal. once whole client related work is done.There was a serious mistake with the client code, in the
pokeServer
which just writes to the socket and checks for number of bytes written. This is something you would normally do with Linux socket API.The issue was, this write operation has to be completed by the Qt event loop, what I was missing was at least to call not so popular synchronous
waitForBytesWritten
, to ensure these bytes really hits the network stack.What made it even worse, is that right after
pokeServer
, my test code enters sleep on themainClient.cpp
, which directly stops Qt event loop of processing written bytes. -
There was a serious mistake with the client code, in the
pokeServer
which just writes to the socket and checks for number of bytes written. This is something you would normally do with Linux socket API.The issue was, this write operation has to be completed by the Qt event loop, what I was missing was at least to call not so popular synchronous
waitForBytesWritten
, to ensure these bytes really hits the network stack.What made it even worse, is that right after
pokeServer
, my test code enters sleep on themainClient.cpp
, which directly stops Qt event loop of processing written bytes.For the sake of completeness of this post, I am providing very simple TCP client/server example with client sending data, and server receiving it.
Server side is not implemented with threads, and will serve me as the starting point to properly implement threaded server, based on all recommendations I got up to now.
CLIENT SIDE
Simple Client.h
#ifndef SIMPLECLIENT_H #define SIMPLECLIENT_H #include <QTcpSocket> #include <QAbstractSocket> #define SOCKET_OPERATION_TIMEOUT_MS 3000 class SimpleClient { public: SimpleClient() {}; bool connectToHost(QString serverIP, qint16 serverPort) { this->socket = new QTcpSocket; socket->connectToHost(QHostAddress(serverIP), serverPort); qDebug() << "Connecting to" << serverIP << ":" << serverPort; socket->connectToHost(QHostAddress(serverIP), serverPort); if (!socket->waitForConnected(SOCKET_OPERATION_TIMEOUT_MS)) { qDebug() << "Error, connection timeout, server not accessible."; return false; } qDebug() << "Connected"; return true; } bool write(const char *data, int len) { if (socket->state() != QAbstractSocket::ConnectedState) { qDebug() << "Write error, client not connected"; return false; } int bytesWritten = this->socket->write(data, len); qDebug() << "Written" << bytesWritten << "bytes"; if(!this->socket->waitForBytesWritten()) { qDebug() << "Write error"; return false; } qDebug() << "Write success"; return true; } void disconnectFromHost() { if (socket->state() == QAbstractSocket::ConnectedState) { this->socket->disconnectFromHost(); qDebug() << "Disconnected"; } } private: QTcpSocket *socket = nullptr; }; #endif // SIMPLECLIENT_H
MainClient.cpp
#include <QThread> #include <QDebug> #include "SimpleClient.h" int main(int argc, char *argv[]) { SimpleClient client; client.connectToHost("127.0.0.1", 9000); client.write("Hello Server", sizeof("Hello Server")); // NOTE: It's fine to sleep for some time now, we don't have GUI // or any networking async operations, required to be handled by the event loop QThread::sleep(2); client.disconnectFromHost(); return 0; }
SERVER SIDE (not threaded)
SimpleServer.h
#ifndef SIMPLESERVER_H #define SIMPLESERVER_H #include <QDebug> #include <QTcpServer> #include <QTcpSocket> class SimpleServer : public QObject { Q_OBJECT; public: SimpleServer() {}; bool start(QString serverIP, qint16 serverPort) { tcpServer = new QTcpServer; connect (tcpServer, &QTcpServer::newConnection, this, &SimpleServer::onNewConnection); qDebug() << "Awaiting for new connections " << "IP" << serverIP << "PORT" << serverPort; tcpServer->listen(QHostAddress(serverIP), serverPort); return true; } private slots: void onNewConnection() { qDebug() << "New connection"; QTcpSocket *socket = tcpServer->nextPendingConnection(); connect(socket, &QAbstractSocket::disconnected, socket, &QObject::deleteLater); qDebug() << "Socket info:" << socket->state() << socket->openMode(); socket->waitForReadyRead(); int bytesAvailable = socket->bytesAvailable(); qDebug() << "Received" << bytesAvailable << "bytes"; } private: QTcpServer *tcpServer = nullptr; }; #endif // SIMPLESERVER_H
MainServer.cpp
#include <QCoreApplication> #include <QtCore> #include "SimpleServer.h" int main(int argc, char *argv[]) { QCoreApplication app(argc, argv); SimpleServer server; server.start("127.0.0.1", 9000); return app.exec(); }
-
For the sake of completeness of this post, I am providing very simple TCP client/server example with client sending data, and server receiving it.
Server side is not implemented with threads, and will serve me as the starting point to properly implement threaded server, based on all recommendations I got up to now.
CLIENT SIDE
Simple Client.h
#ifndef SIMPLECLIENT_H #define SIMPLECLIENT_H #include <QTcpSocket> #include <QAbstractSocket> #define SOCKET_OPERATION_TIMEOUT_MS 3000 class SimpleClient { public: SimpleClient() {}; bool connectToHost(QString serverIP, qint16 serverPort) { this->socket = new QTcpSocket; socket->connectToHost(QHostAddress(serverIP), serverPort); qDebug() << "Connecting to" << serverIP << ":" << serverPort; socket->connectToHost(QHostAddress(serverIP), serverPort); if (!socket->waitForConnected(SOCKET_OPERATION_TIMEOUT_MS)) { qDebug() << "Error, connection timeout, server not accessible."; return false; } qDebug() << "Connected"; return true; } bool write(const char *data, int len) { if (socket->state() != QAbstractSocket::ConnectedState) { qDebug() << "Write error, client not connected"; return false; } int bytesWritten = this->socket->write(data, len); qDebug() << "Written" << bytesWritten << "bytes"; if(!this->socket->waitForBytesWritten()) { qDebug() << "Write error"; return false; } qDebug() << "Write success"; return true; } void disconnectFromHost() { if (socket->state() == QAbstractSocket::ConnectedState) { this->socket->disconnectFromHost(); qDebug() << "Disconnected"; } } private: QTcpSocket *socket = nullptr; }; #endif // SIMPLECLIENT_H
MainClient.cpp
#include <QThread> #include <QDebug> #include "SimpleClient.h" int main(int argc, char *argv[]) { SimpleClient client; client.connectToHost("127.0.0.1", 9000); client.write("Hello Server", sizeof("Hello Server")); // NOTE: It's fine to sleep for some time now, we don't have GUI // or any networking async operations, required to be handled by the event loop QThread::sleep(2); client.disconnectFromHost(); return 0; }
SERVER SIDE (not threaded)
SimpleServer.h
#ifndef SIMPLESERVER_H #define SIMPLESERVER_H #include <QDebug> #include <QTcpServer> #include <QTcpSocket> class SimpleServer : public QObject { Q_OBJECT; public: SimpleServer() {}; bool start(QString serverIP, qint16 serverPort) { tcpServer = new QTcpServer; connect (tcpServer, &QTcpServer::newConnection, this, &SimpleServer::onNewConnection); qDebug() << "Awaiting for new connections " << "IP" << serverIP << "PORT" << serverPort; tcpServer->listen(QHostAddress(serverIP), serverPort); return true; } private slots: void onNewConnection() { qDebug() << "New connection"; QTcpSocket *socket = tcpServer->nextPendingConnection(); connect(socket, &QAbstractSocket::disconnected, socket, &QObject::deleteLater); qDebug() << "Socket info:" << socket->state() << socket->openMode(); socket->waitForReadyRead(); int bytesAvailable = socket->bytesAvailable(); qDebug() << "Received" << bytesAvailable << "bytes"; } private: QTcpServer *tcpServer = nullptr; }; #endif // SIMPLESERVER_H
MainServer.cpp
#include <QCoreApplication> #include <QtCore> #include "SimpleServer.h" int main(int argc, char *argv[]) { QCoreApplication app(argc, argv); SimpleServer server; server.start("127.0.0.1", 9000); return app.exec(); }
[SOLVED]
I am posting the solution for the original post:- Client had an issue, after socket
write
call it's required to call syncwaitForBytesWritten
, which will process actual socket writing - Server had an issue, in the
run
method, we can't just call socketread
, it's required to callwaitForReadyRead
which takes care to process socket and fill it with incoming data.
Thanks everyone for their suggestions and help!
I am also posting working sample code, which is only meant for demo purposes, to show a simple synchronous approach to TCP client and server
DEMO TCP CLIENT AND SERVER WITH THREADS
MainClient.cpp
#include "SimpleClient.h" int main(int argc, char *argv[]) { SimpleClient client; if (!client.connectToServer("127.0.0.1", 9000)) { return -1; } if (!client.pokeServer("Hello Server")) { return -1; } return 0; }
SImpleClient.h
#ifndef SIMPLECLIENT_H #define SIMPLECLIENT_H #include <QTcpSocket> #include <QAbstractSocket> #define SOCKET_OPERATION_TIMEOUT_MS 3000 class SimpleClient { public: SimpleClient(); bool connectToServer(QString serverIP, qint16 serverPort); int pokeServer(const char *pokeMsg); private: QTcpSocket *socket = nullptr; }; #endif // SIMPLECLIENT_H
SImpleClient.cpp
#include <QByteArray> #include "SimpleClient.h" SimpleClient::SimpleClient() { this->socket = new QTcpSocket; } bool SimpleClient::connectToServer(QString serverIP, qint16 serverPort) { qDebug() << "Connecting to" << serverIP << ":" << serverPort; socket->connectToHost(QHostAddress(serverIP), serverPort); if (!socket->waitForConnected(SOCKET_OPERATION_TIMEOUT_MS)) { qDebug() << socket->errorString(); return false; } qDebug() << "Connected"; return true; } int SimpleClient::pokeServer (const char *pokeMsg) { qDebug() << "Sending :" << QString(pokeMsg); this->socket->write(pokeMsg, strlen(pokeMsg)); if (!this->socket->waitForBytesWritten(SOCKET_OPERATION_TIMEOUT_MS)) { qDebug() << socket->errorString(); return false; } if (!this->socket->waitForReadyRead(SOCKET_OPERATION_TIMEOUT_MS)) { qDebug() << this->socket->errorString(); return false; } QByteArray serverReply = this->socket->readAll(); QString replyString(serverReply.data()); qDebug() << "Received:" << replyString; qDebug() << "Success"; return true; }
MainServer.cpp
#include <QCoreApplication> #include <QtCore> #include "SimpleServer.h" int main(int argc, char *argv[]) { QCoreApplication app(argc, argv); SimpleServer server; server.start("127.0.0.1", 9000); return app.exec(); }
SimpleServer.h
#ifndef SIMPLESERVER_H #define SIMPLESERVER_H #include <QDebug> #include <QTcpServer> class SimpleServer : public QTcpServer { Q_OBJECT; public: SimpleServer(QObject *parent = nullptr); bool start(QString serverIP, qint16 serverPort); protected: void incomingConnection(qintptr socketDescriptor) override; \ }; #endif // SIMPLESERVER_H
SimpleServer.cpp
#include <QHostAddress> #include "SimpleServer.h" #include "ServerJobThread.h" SimpleServer::SimpleServer(QObject *parent) : QTcpServer(parent) {} bool SimpleServer::start(QString serverIP, qint16 serverPort) { qDebug() << "Awaiting for new connections " << "IP" << serverIP << "PORT" << serverPort; this->listen(QHostAddress(serverIP), serverPort); return true; } void SimpleServer::incomingConnection(qintptr socketDescriptor) { qDebug() << "New incoming connection"; ServerJobThread *thread = new ServerJobThread(socketDescriptor, this); connect(thread, SIGNAL(finished()), thread, SLOT(deleteLater())); thread->start(); }
ServerJobThread.h
#ifndef SERVERJOBTHREAD_H #define SERVERJOBTHREAD_H #include <QThread> #include <QTcpSocket> class ServerJobThread : public QThread { Q_OBJECT public: ServerJobThread(qintptr socketDescriptor, QObject *parent); void run() override; private: qintptr socketDescriptor; }; #endif // SERVERJOBTHREAD_H
ServerJobThread.cpp
#include <QByteArray> #include <QTime> #include "ServerJobThread.h" #define SOCKET_OPERATION_TIMEOUT_MS 3000 #define THREAD_MAX_RUNNING_TIME_S 5 #define THREAD_ID this->currentThreadId() ServerJobThread::ServerJobThread(qintptr socketDescriptor, QObject *parent) : QThread(parent) { this->socketDescriptor = socketDescriptor; } void ServerJobThread::run() { bool workerThreadShouldRun = true; QTcpSocket tcpSocket; if (!tcpSocket.setSocketDescriptor(this->socketDescriptor)) { qDebug() << THREAD_ID << "setSocketDescriptor fail"; return; } QTime workerThreadTimeoutTime = QTime::currentTime().addSecs(THREAD_MAX_RUNNING_TIME_S); while (workerThreadShouldRun) { if (tcpSocket.bytesAvailable() == 0) { tcpSocket.waitForReadyRead(SOCKET_OPERATION_TIMEOUT_MS); } else { QByteArray buffer = tcpSocket.readAll(); if (buffer.size() > 0) { qDebug() << THREAD_ID << "Received:" << QString(buffer.data()); qDebug() << THREAD_ID << "Sending :" << QString("Hello Client"); tcpSocket.write("Hello Client"); tcpSocket.waitForBytesWritten(SOCKET_OPERATION_TIMEOUT_MS); workerThreadShouldRun = false; } if (QTime::currentTime() > workerThreadTimeoutTime) { qDebug() << THREAD_ID << "Thread max running time expired"; workerThreadShouldRun = false; } } } qDebug() << THREAD_ID << "Thread finished"; }
CMakeLists.txt
cmake_minimum_required(VERSION 3.14) project(Simple-TCP-Client-Server LANGUAGES CXX) set(CMAKE_AUTOUIC ON) set(CMAKE_AUTOMOC ON) set(CMAKE_AUTORCC ON) set(CMAKE_CXX_STANDARD 17) set(CMAKE_CXX_STANDARD_REQUIRED ON) find_package(QT NAMES Qt6 Qt5 REQUIRED COMPONENTS Core Network) find_package(Qt${QT_VERSION_MAJOR} REQUIRED COMPONENTS core Network) add_executable(Server ServerJobThread.h ServerJobThread.cpp SimpleServer.h SimpleServer.cpp MainServer.cpp ) target_link_libraries(Server Qt6::Core Qt6::Network) add_executable(Client SimpleClient.h SimpleClient.cpp MainClient.cpp ) target_link_libraries(Client Qt6::Core Qt6::Network)
CLIENT STDOUT
$ ./Client Connecting to "127.0.0.1" : 9000 Connected Sending : "Hello Server" Received: "Hello Client" Success
SERVER STDOUT
$ ./Server Awaiting for new connections IP "127.0.0.1" PORT 9000 New incoming connection 0x9a60 Received: "Hello Server" 0x9a60 Sending : "Hello Client" 0x9a60 Thread finished
- Client had an issue, after socket
-