[SOLVED] QDataStream and QTcpSocket problem in multi-threaded application
-
ListenerThread now is Listener and inherits from QObject, LoginThread is removed and all operations from LoginThread::run() are moved to Listener::login() slot. Reading and writing to stream works (client receives data correctly) but it's only part of code and there is one small question. I decided to create threads to be able to handle multiple login requests simultaneously, eg. 3 clients are trying to log in but have to wait in queue. As i think every signal being sent in this case is executed in the same thread what makes it unable to handle multiple requests at the same time.
@
void Listener::Init(){
QObject::connect(this,SIGNAL(newConnection()),this,SLOT(login()));
if(!(listen(hostAddr,port))){
SharedData::error(errorString());
}
}void Listener::login(){
//some login procedures
}
@After successful login every client (represented by it's own class) is moved to something what I called "Message Router" which handles transferring messages from client X to client Y.
@
void Listener::login(){//waiting for login data
//reading login data from stream
//checking if login data isn't empty
//checking if server is full
//checking client type (for future use)
//checking client version
//connecting to users database
//checking if user exists
//checking if password is correctSharedData::router->addClient(socketDescriptor,id,clientType);
}
@Message Router is also a thread and the socket which client is communicating by have to be moved to another thread, to prevent messages from waiting.
How to make QTcpSocket working in thread which is not a thread where it was created? Should I reimplement QTcpServer::incomingConnection()? I didn't try sniffing yet but I'll check if find that stream sends data correctly. Debugger don't show any problems with writing to stream.
This program can run without using threads and then the problem with QTcpSocket will be solved by using signals and slots but it appears to me that it doesn't ensure continuous work regardless of current client request.
-
You can 'pass' a connection between threads the way you already did; by passing the socket descriptor and then creating a new socket in the new thread using this descriptor.
Although nextPendingConnection() also works, I would use incomingConnection() instead, as it directly provides the socket descriptor and saves you from creating an (unneccessary) QTcpSocket object.
I just want to mention that Qt also provides high level concurrency, which allows for executing stuff asynchronosly without having to mess with low level threads.
@
class Listener : public QTcpServer
{
Q_OBJECTpublic:
Listener(QObject *parent = 0) : QTcpServer(parent)
{
...
}protected:
void incomingConnection(int handle)
{
// execute login in a different thread (taken from global thread pool)
QtConcurrent::run(this, &Listener::login, handle);
}private:
void login(int handle)
{
QTcpSocket socket;
socket.setSocketDescriptor(handle);
...
}
};
@
Brain to terminal. -
I used your solution and everything works fine except the QTcpSocket... As i posted before reading works and writing does not. I compared Wireshark results for old server and this code. There's no ACK packet with data coming from "new server". I don't know what may cause this problem.
Here's my code:
main.cpp
@
#include <QtCore/QCoreApplication>
#include <iostream>#include "listener.h"
#include "msgrouterthread.h"
#include "shareddata.h"using namespace std;
int main(int argc, char *argv[])
{
QCoreApplication a(argc, argv);if(SharedData::Init()){ cout<<"Unable to initialize server data"<<endl; return 1; } Listener listener; listener.Init(); SharedData::router->start(); cout<<"Server started successfully"<<endl; return a.exec();
}
@listener.h
@
#ifndef LISTENER_H
#define LISTENER_H#include <QtCore>
#include <QTcpServer>#include "clienttypes.h"
#include "errorcodes.h"
#include "shareddata.h"class Listener : public QTcpServer
{
Q_OBJECT
public:
explicit Listener(QObject *parent = 0);
void Init();protected:
void incomingConnection(int);private:
void login(int);};
#endif // LISTENER_H
@listener.cpp (shortened)
@
#include "listener.h"Listener::Listener(QObject *parent) :
QTcpServer(parent)
{
}void Listener::Init(){
if(!(listen(SharedData::hostAddr,SharedData::port))){
SharedData::error(this->errorString());
return;
}
}void Listener::incomingConnection(int handle){
QtConcurrent::run(this, &Listener::login, handle);
}void Listener::login(int handle){
std::cout<<"Login requested"<<std::endl;//login QTcpSocket socket; socket.setSocketDescriptor(handle); if(!(socket.bytesAvailable())){ if(!(socket.waitForReadyRead())){ std::cout<<"Connection timed out"<<std::endl; socket.disconnectFromHost(); return; } } QDataStream clientStream(&socket); QString data = QString(); clientStream>>data; //works fine // ... some code ... if(userPwd != passwd){ std::cout<<"Incorrect password"<<std::endl; QString errMsg(INCORRECT_PASSWORD); errMsg.append('\n'); clientStream<<errMsg; //doesn't work socket.disconnectFromHost();
// ... some code ...
return; }
// ... some code ...
}
@I also tried to send some data immediately after reading from stream, but it doesn't change anything.
@
QDataStream clientStream(&socket);
QString data = QString();
clientStream>>data; //works fine
clientStream<<"some data which won't be received by client";
clientStream<<QString("other data with the same problem");
@QTcpSocket::write() doesn't work too.
Screenshots from Wireshark:
From old server (packet with data)
"old":http://bronexproduction.pl/shared/old.pngFrom new server (no data)
"new":http://bronexproduction.pl/shared/new.png -
I'm glad to say that socket transfer works :) thank you very much. But I have one more question about socket descriptors relative to this code:
@
void Listener::incomingConnection(int handle){
QtConcurrent::run(this, &Listener::login, handle);
}void Listener::login(int handle){
std::cout<<"Login requested"<<std::endl;QTcpSocket socket; socket.setSocketDescriptor(handle); // ... some code ... emit addClient(handle,id,clientType);
}
@I pass the descriptor by emitting Listener::addClient signal and when the function ends this socket
@
QTcpSocket socket;
@
is deleted and the file descriptor is closed so I can't use it in next function. Creating this socket by using a pointer and not deleting it might be not so good. Is there any way to keep descriptor opened when deleting QTcpSocket object? -
I'd like to be able to use this connection in different thread, here's a sample code
@
void MsgRouterThread::addClientSlot(int handler, QString ID, qint64 clientType){
QtConcurrent::run(this,&MsgRouterThread::addClient,handler,ID,clientType);
}void MsgRouterThread::addClient(int handler, QString ID, qint64 clientType){
Client client(handler,ID,clientType); clientByID.insert(ID,Client(handler,ID,clientType)); QTcpSocket socket; socket.setSocketDescriptor(handler); QDataStream stream(&socket); stream<<QString(LOGIN_SUCCESSFUL); socket.flush(); // ... some code ...
}
// ... some other methods ...
@I can't use here the descriptor unless socket from Listener::login() exists
-
The tcp socket destructor will close the connection, see here: "QTcpSocket":http://qt-project.org/doc/qt-4.8/qtcpsocket.html#dtor.QTcpSocket.
Creating a tcp socket on the heap won't help because you cannot use the same socket in more than one thread. Why don't you use the same thread for all work?
-
Finally I used one thread for QTcpSocket, as you suggested. I had to change my plans a bit for that reason. Everything uses signal/slot communication and didn't crash yet. I hope it won't suprise me too much.
Thanks a lot