[SOLVED] QDataStream and QTcpSocket problem in multi-threaded application



  • Hi,

    I'm a beginner programmer and I'm writing small client - server application (something like IRC) and i started to modify server which was not fully working. I found a problem with sending/receiving data through QTcpSocket (it previously worked but wasn't in separate threads).

    As shown in part of code reading from QDataStream on QTcpSocket device works but writing to this stream - not. I test it on working client and there were no problems with old server.

    I have tried to move the code from second thread to first and using only one thread but the problem still existed.

    QTcpSocket::isOpen(), QTcpSocket::isReadable(), QTcpSocket::isValid() and QTcpSocket::isWritable() returns true. There is no error after writing to stream. Everything seems to be working but client application does not receive any data.

    Here is part of code:

    @
    //class ListenerThread extends QThread
    //class LoginThread extends QThread

    void ListenerThread::run(){
    this->serverSocket = new QTcpServer();
    if(!(serverSocket->listen(hostAddr,port))){
    return;
    }
    while(true){
    if(!(serverSocket->hasPendingConnections())){
    serverSocket->waitForNewConnection(-1);
    }
    (new LoginThread(serverSocket->nextPendingConnection()->socketDescriptor()))->start();
    }
    }

    LoginThread::LoginThread(int sockDsc, QObject *parent) :
    QThread(parent)
    {
    this->sockDsc = sockDsc;
    }

    void LoginThread::run(){
    QTcpSocket* socket = new QTcpSocket();
    socket->setSocketDescriptor(this->sockDsc);
    QDataStream clientStream(socket);
    QString data = QString();
    clientStream>>data; //it works
    clientStream<<QString("-10"); //and it doesn't
    }
    @

    I also tried to send more data to be sure that there is no problem with the short time between read and write.
    Can anyone help me to solve this?



  • Have you tried stepping into #29 to find out if there is any obvious reason why the data isn't sent; have you tried sniffing the network traffic to make sure the data isn't sent?

    Have you tried just sending data, not receiving?

    A few annotations:

    • You do not need a secondary thread; QTcpServer already works asynchronously (as long as you have a spinning event loop) and emits the newConnection() signal each time a client connects
    • You are leaking memory, as <code>this->serverSocket</code> is neither deleted explicitily nor implicitly (as it has no parent set); the same goes for <code>socket</code> in <code>LoginThread</code>
    • You should delete the QTcpSocket returned from nextPendingConnection(); it is implicitly deleted with the server, but until the server is deleted QTcpSocket objects will stack and waste memory
    • I assume you take the detour by the socket descriptior to cross thread boundaries. I recommend using incomingConnection() then, as it prevents you from creating a temporary QTcpSocket object in the first place
    • And, as always when it comes to threading in Qt make sure you've read "Threads, Events and QObjects":http://qt-project.org/wiki/Threads_Events_QObjects, "QThreads General Usage":http://qt-project.org/wiki/QThreads_general_usage and "You’re Doing It Wrong":http://labs.qt.nokia.com/2010/06/17/youre-doing-it-wrong/


  • 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 correct

    SharedData::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_OBJECT

    public:
    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&#40;&#41;;
    

    }
    @

    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.png

    From new server (no data)
    "new":http://bronexproduction.pl/shared/new.png



  • Use socket.flush() after writing to the stream or using write() directly -- it seems that the socket is not being sent because there is no event loop that does the work for you, since the socket uses buffered I/O.



  • 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?



  • For what reason?



  • 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



  • You're welcome. If you want to mark this thread [SOLVED] everyone will be happy.


Log in to reply
 

Looks like your connection to Qt Forum was lost, please wait while we try to reconnect.