Threaded QTcpSockets: Is it possible?



  • I've read everything I can find on QTcpSockets, QTcpServers, and threading within Qt. I still can't seem to get the behavior I want without generating an error. What I want to do is have a socket that will be able to write out data while simultaneously be able to handle incoming data. For example, let's say I have a program that is monitoring the weather. This program is listening for an incoming socket connection. When it sees one, it establishes the socket link. It then needs to start writing the temperature data out to the socket continuously as fast as possible for as long as the socket is open. At the same time, the program needs to look at incoming data for commands e.g. "start feeding me barometric pressure" or "start feeding me the wind speed"

    Of course, my first instinct is to use the readyRead signal to invoke a routine for reading the incoming command message data. That's great. The hard part is writing out the weather data. My first thought is to spawn a thread that will write to the socket. I can get this to work but no matter how I try to implement this, I always end up with the non-fatal error message QObject: Cannot create children for a parent that is in a different thread. (Parent is QNativeSocketEngine(0x1016077f0), parent's thread is QThread(0x101100410), current thread is Foobar(0x101608360) even though the program runs fine otherwise.

    Foobar is a class that's instantiated by my QTcpServer subclass. I have tried making Foobar a subclass of QThread (even though that's supposed to be wrong). I have tried creating the QTcpSocket object locally in the Foobar class. I have tried various attempts at making worker classes to handle the writing, QRunnable, the global ThreadPool method. No luck.

    So, is there is a right way to do this? Please note, that this isn't a situation where blocking is possible. I know sockets are asynchronous but that's not the same as actually being able to read and write to it from two separate threads of execution.


  • Moderators

    [quote author="RogueWarrior" date="1376872767"]What I want to do is have a socket that will be able to write out data while simultaneously be able to handle incoming data.

    ...

    I know sockets are asynchronous but that's not the same as actually being able to read and write to it from two separate threads of execution.[/quote]Hi,

    Each QTcpSocket (and each QObject in general) can only be accessed from one thread. If you want to read from AND write to the same socket, then both the read and write operations must occur on the same thread.

    Do you truly need separate read and write threads? TCP is buffered, so your data stream possibly won't be interrupted even if your program stops writing for an instant while it reads the command (which I presume is only a few bytes long). What throughput is your system capable of?

    If you do need separate threads, would having 2 dedicated sockets be a viable solution?



  • Hmm...well, if I have a QObject class that is moved into a thread and that class has a DoWork slot which I connect to the started() signal and I also have a readyRead slot which is connected to the QTcpSocket's readyRead signal, that sort of works. As long as nothing in either of the two slots takes too long. If the DoWork routine sleeps, that blocks the readyRead slot.

    Why would I sleep the writer routine? Well, if I want to send messages at a timed interval, say 50 Hz. Perhaps my above example doesn't justify this. Let's say that instead of a weather system it's some sort of process control. You need to be able to send commands to open or close a pressure relief valve on a boiler. Whether or not you open that valve depends on a pressure reading that gets written to the socket periodically. If the socket handler buffers the read which might have an open valve command, the valve might open too late to prevent an explosion.

    What I've just tried is making two nearly identical socket handler QObject classes. One is configured to deal with the readyRead signal. The other has no readyRead connection but has a DoWork routine. Then both are set up to create a QTcpSocket using the same socket descriptor and both are moved into QThreads.

    This works in that I no longer get the thread error. I'm able to create more than one session as well. I use simple text and telnet to test my theories. Where it fails is when I close the telnet connection. One of the threads shuts down fine (the reader) but the writer eventually produces this error: "QSocketNotifier: Invalid socket 9 and type 'Write', disabling..." It doesn't crash but I'm not sure if both threads are ending.

    I suppose reversing my sequence and created the writer thread first instead of the reader. Either that or I need a way to ensure that when one thread sees the disconnect signal, it causes the other to quit.


  • Moderators

    [quote author="RogueWarrior" date="1376883792"]if I have a QObject class that is moved into a thread and that class has a DoWork slot which I connect to the started() signal and I also have a readyRead slot which is connected to the QTcpSocket's readyRead signal, that sort of works.[/quote]Yes, that is the essence of asynchronous, event-driven programming.

    [quote]As long as nothing in either of the two slots takes too long. If the DoWork routine sleeps, that blocks the readyRead slot.

    Why would I sleep the writer routine? Well, if I want to send messages at a timed interval, say 50 Hz.[/quote]Sleeping and event loops don't mix nicely. In general, either use sleep() with no event loops, or use a QTimer with event loops. Qt sockets need a running event loop, so QTimer is your best option here.

    In any case, it's probably a good idea to have network comms on their own dedicated thread(s), and have doWork() run in a separate data processing thread.

    [quote]Let's say that instead of a weather system it's some sort of process control. You need to be able to send commands to open or close a pressure relief valve on a boiler. Whether or not you open that valve depends on a pressure reading that gets written to the socket periodically. If the socket handler buffers the read which might have an open valve command, the valve might open too late to prevent an explosion.[/quote]With such strict timing requirements, you may want a communications protocol that's much more deterministic than TCP, like EtherCAT.

    [quote]What I've just tried is making two nearly identical socket handler QObject classes. One is configured to deal with the readyRead signal. The other has no readyRead connection but has a DoWork routine. Then both are set up to create a QTcpSocket using the same socket descriptor and both are moved into QThreads.

    This works in that I no longer get the thread error. I'm able to create more than one session as well. I use simple text and telnet to test my theories. Where it fails is when I close the telnet connection. One of the threads shuts down fine (the reader) but the writer eventually produces this error: "QSocketNotifier: Invalid socket 9 and type 'Write', disabling..." It doesn't crash but I'm not sure if both threads are ending.

    I suppose reversing my sequence and created the writer thread first instead of the reader. Either that or I need a way to ensure that when one thread sees the disconnect signal, it causes the other to quit.[/quote]I've never used this method before so don't know what's happening there exactly, but you can check if the threads have quit properly by listening for the QThread::finished() signal.



  • Okay, in lieu of designing a dual-socket system (a real possibility), here's what I've come up with so far.

    @MyServer::MyServer(QObject *parent) : QTcpServer(parent)
    {
    }

    void MyServer::StartServer()
    {
    if (this->listen(QHostAddress::Any, 1234)) {
    qDebug() << "Server started.";
    }
    }

    void MyServer::incomingConnection(int handle)
    {
    QThread *t = new QThread();
    QThread *u = new QThread();

    ClientReader *clientReader = new ClientReader(handle, t, u, NULL);
    clientReader->moveToThread(t);
    
    ClientWriter *clientWriter = new ClientWriter(handle, u, NULL);
    clientWriter->moveToThread(u);
    
    t->start();
    u->start();
    

    }
    @

    My derived QTcpServer class incomingConnection sets up two threads for a reader class and a writer class. Earlier testing revealed that the reader class got disconnect signals before the writer class did so I set it up such that the reader thread can tell the writer thread to quit. That makes QSocketNotify error messages go away.

    Here are the two classes:

    @ClientReader::ClientReader(int socketDescriptor, QThread *cThread, QThread *writerThread, QObject *parent)
    {
    m_socketDescriptor = socketDescriptor;
    m_socket = new QTcpSocket(this);
    m_socket->setSocketDescriptor(m_socketDescriptor);

    connect(m_socket, SIGNAL(connected()), this, SLOT(CR_connected()));
    connect(m_socket, SIGNAL(disconnected()), this, SLOT(CR_disconnected()));
    connect(m_socket, SIGNAL(readyRead()), this, SLOT(CR_readyRead()));
    
    myThread = cThread;
    connect(this, SIGNAL(finished()), cThread, SLOT(quit()));
    connect(this, SIGNAL(finished()), this, SLOT(deleteLater()));
    connect(cThread, SIGNAL(finished()), cThread, SLOT(deleteLater()));
    
    myWriterThread = writerThread;
    connect(m_socket, SIGNAL(disconnected()), myWriterThread, SLOT(quit()));
    

    }

    void ClientReader::CR_connected()
    {
    qDebug() << "ClientReader connected event.";
    }

    void ClientReader::CR_disconnected()
    {
    qDebug() << "ClientReader disconnected event.";
    }

    void ClientReader::CR_readyRead()
    {
    qDebug() << m_socket->readAll();
    }

    //===========================

    ClientWriter::ClientWriter(int socketDescriptor, QThread *cThread, QObject *parent)
    {
    m_socketDescriptor = socketDescriptor;
    m_socket = new QTcpSocket(this);
    m_socket->setSocketDescriptor(m_socketDescriptor);

    connect(m_socket, SIGNAL(connected()), this, SLOT(CW_connected()));
    connect(m_socket, SIGNAL(disconnected()), this, SLOT(CW_disconnected()));
    
    myThread = cThread;
    connect(cThread, SIGNAL(started()), this, SLOT(DoWork()));
    connect(this, SIGNAL(finished()), cThread, SLOT(quit()));
    connect(this, SIGNAL(finished()), this, SLOT(deleteLater()));
    connect(cThread, SIGNAL(finished()), cThread, SLOT(deleteLater()));
    

    }

    void ClientWriter::DoWork()
    {
    long i=3000;
    QByteArray theMsg;

    while ((m_socket->state() == QAbstractSocket::ConnectedState) && (i>0))
    {
        i--;
        if (m_socket->state() == QAbstractSocket::ConnectedState) {
            qDebug() << "Sending a message..." << i;
    
            theMsg = "Hello from Socket Writer.";
            theMsg.append(QString::number(i));
            theMsg.append("\r\n");
    
            m_socket->write(theMsg);
            if (m_socket->state() == QAbstractSocket::ConnectedState) {
                m_socket->waitForBytesWritten(50);
                qDebug() << ">>> Wait for bytes written...";
                usleep(20000);
            } else {
                qDebug() << "Trying WFBW: Not connected.";
            }
        } else {
            qDebug() << "Trying write: Not connected.";
        }
    }
    qDebug() << "Leaving DoWork.";
    

    }

    void ClientWriter::CW_connected()
    {
    qDebug() << "ClientWriter connected event.";
    }

    void ClientWriter::CW_disconnected()
    {
    qDebug() << "ClientWriter disconnected event.";
    }
    @

    This all seems to be working. When I have telnet connect, I start getting messages sent and I'm able to type stuff in telnet and send it back without interrupting anything. If I close the telnet session, the reader immediately sees disconnect and the writer DoWork routine eventually completes once the disconnected state is seen. Multiple simultaneous telnet sessions seems to work fine.

    My only concern is that there may be a memory leak somewhere, some object isn't configured to gracefully go away or something, so if you see that I'm doing something wrong, please let me know.


Log in to reply
 

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