Important: Please read the Qt Code of Conduct - https://forum.qt.io/topic/113070/qt-code-of-conduct

QTcpSocket fails to transmit data when reconnecting to server after RemoteHostClosedError



  • I'm writing a simple TCP client, based on QTcpSocket and managed by a QStateMachine (connect to server -> transmit data -> if disconnected for any reason, reconnect to server).

    I noticed that if the connection is shut down on the server side (client is notified with RemoteHostClosedError), after reconnection the QTcpSocket write() method succeeds but no data is transmitted on the wire - nothing is received by the server, and the bytesWritten() signal on the client side does not fire up.

    I found in the documentation for error() signal (https://doc.qt.io/qt-5/qabstractsocket.html#error) that "When this signal is emitted, the socket may not be ready for a reconnect attempt. In that case, attempts to reconnect should be done from the event loop". I think I'm already ok with that, as the reconnection happens in one of the QStateMachine states, and QStateMachine has its own event loop.

    Below some simplified code to reproduce the issue:

    testclient.h

    #ifndef TESTCLIENT_H
    #define TESTCLIENT_H
    
    #include <QObject>
    #include <QTcpSocket>
    #include <QDebug>
    #include <QStateMachine>
    
    class TestClient : public QObject
    {
        Q_OBJECT
    
    public:
        explicit TestClient(QObject *parent = nullptr);
    
    public slots:
        void start();
    
    signals:
        // FSM events
        void fsmEvtConnected();
        void fsmEvtError();
    
    private slots:
        void onSocketConnected();                       // Notify connection to TCP server
        void onSocketDisconnected();                    // Notify disconnection from TCP server
        void onSocketBytesWritten(qint64 bytes);        // Notify number of bytes written to TCP server
        void onSocketError(QAbstractSocket::SocketError err);
    
        // FSM state enter/exit actions
        void onfsmConnectEntered();
        void onfsmTransmitEntered();
        void onfsmTransmitExited();
    
    private:
        // Member variables
        QTcpSocket*         m_socket;       // TCP socket used for communications to server
        QStateMachine*      m_clientFsm;      // FSM defining general client behaviour
    
    private:
        void createClientFsm();             // Create client FSM
    };
    
    #endif // TESTCLIENT_H
    
    

    testclient.cpp

    #include "testclient.h"
    #include <QState>
    #include <QThread>      // Sleep
    
    //-----------------------------------------------------------------------------
    // PUBLIC METHODS
    //-----------------------------------------------------------------------------
    
    TestClient::TestClient(QObject *parent) : QObject(parent)
    {
        m_socket = new QTcpSocket(this);
    
        connect(m_socket, SIGNAL(connected()),this, SLOT(onSocketConnected()));
        connect(m_socket, SIGNAL(disconnected()),this, SLOT(onSocketDisconnected()));
        connect(m_socket, SIGNAL(bytesWritten(qint64)),this, SLOT(onSocketBytesWritten(qint64)));
        connect(m_socket, SIGNAL(error(QAbstractSocket::SocketError)), this, SLOT(onSocketError(QAbstractSocket::SocketError)));
    }
    
    void TestClient::start()
    {
        createClientFsm();
        m_clientFsm->start();
    }
    
    
    //-----------------------------------------------------------------------------
    // TCP CONNECTION MANAGEMENT SLOTS
    //-----------------------------------------------------------------------------
    void TestClient::onSocketConnected()
    {
        qDebug() << "connected...";
        emit fsmEvtConnected();
    }
    
    void TestClient::onSocketDisconnected()
    {
        qDebug() << "disconnected...";
        emit fsmEvtError();
    }
    
    void TestClient::onSocketBytesWritten(qint64 bytes)
    {
        qDebug() << bytes << " bytes written...";
    }
    
    void TestClient::onSocketError(QAbstractSocket::SocketError err)
    {
        qDebug() << "socket error " << err;
    }
    
    //-----------------------------------------------------------------------------
    // FSM MANAGEMENT
    //-----------------------------------------------------------------------------
    void TestClient::createClientFsm()
    {
        m_clientFsm = new QStateMachine(this);
    
        // Create states
        QState* sConnect = new QState();
        QState* sTransmit = new QState();
    
        // Add transitions between states
        sConnect->addTransition(this, SIGNAL(fsmEvtConnected()), sTransmit);
        sTransmit->addTransition(this, SIGNAL(fsmEvtError()), sConnect);
    
        // Add entry actions to states
        connect(sConnect, SIGNAL(entered()), this, SLOT(onfsmConnectEntered()));
        connect(sTransmit, SIGNAL(entered()), this, SLOT(onfsmTransmitEntered()));
    
        // Add exit actions to states
        connect(sTransmit, SIGNAL(exited()), this, SLOT(onfsmTransmitExited()));
    
        // Create state machine
        m_clientFsm->addState(sConnect);
        m_clientFsm->addState(sTransmit);
        m_clientFsm->setInitialState(sConnect);
    }
    
    
    void TestClient::onfsmConnectEntered()
    {
        qDebug() << "connecting...";
        m_socket->connectToHost("localhost", 11000);
    
        // Wait for connection result
        if(!m_socket->waitForConnected(10000))
        {
            qDebug() << "Error: " << m_socket->errorString();
            emit fsmEvtError();
        }
    }
    
    void TestClient::onfsmTransmitEntered()
    {
        qDebug() << "sending data...";
        m_socket->write("TEST MESSAGE");
    }
    
    void TestClient::onfsmTransmitExited()
    {
        qDebug() <<  "waiting before reconnection attempt...";
        QThread::sleep(2);
    }
    

    main.cpp

    #include <QCoreApplication>
    #include "testclient.h"
    
    int main(int argc, char *argv[])
    {
        QCoreApplication a(argc, argv);
    
        TestClient client(&a);
        client.start();
    
        return a.exec();
    }
    

    To test, you can just launch netcat (nc -l -p 11000) , then close the nc process after receiving TEST MESSAGE and finally relaunch it again. The second time, TEST MESSAGE is not received, and we don't have the onSocketBytesWritten() printout, see below:

    connecting...
    connected...
    sending data...
    12  bytes written...    <<<<<<<<<< Correct transmission, event fires up
    socket error  QAbstractSocket::RemoteHostClosedError
    disconnected...
    waiting before reconnection attempt...
    connecting...
    connected...
    sending data...    <<<<<<<<<< No transmission, event does not fire up, no socket errors!
    

    Regards



  • To sum up and close the topic: the proper solution would be Solution 1 from a couple of posts above:

    @Andrea-Narciso said in QTcpSocket fails to transmit data when reconnecting to server after RemoteHostClosedError:

    Not using waitForConnected() function and completely relying on the connected() event plus some QTimer to manage the timeout for a connection attempt. Apparently, the use of such blocking function messes up something in the event loop, even if it is not clear to me how - the connected() event and all the state machine events are still fired up! Maybe this is a QT bug...

    Anyway, to test this solution simply remove the following lines from onfsmConnectEntered():

        // Wait for connection result
        if(!m_socket->waitForConnected(10000))
        {
            qDebug() << "Error: " << m_socket->errorString();
            emit fsmEvtError();
        }
    

    By adding a QTimer, a connection timeout can be handled so that waitForConnected() can be entirely replaced.



  • I found out that if I create the QTcpSocket on connection and destroy it on disconnection, the problem does not happen. Is this the expected/proper way to use sockets?

    Wouldn't it be possible instead to create the socket just once and just connect/disconnect? Maybe it is just a matter of flushing or cleaning up in a specific manner, but I could not find it so far.

    It would be great if somebody could comment on my interpretation, so that I can understand better what's happening.

    BTW, here are the modifications that make the code above work on server-side disconnection:

    Move socket creation from class constructor to onfsmConnectEntered() - handler for entry in the "Connect" QState:

    void TestClient::onfsmConnectEntered()
    {    
        m_socket = new QTcpSocket(this);
    
        connect(m_socket, SIGNAL(connected()),this, SLOT(onSocketConnected()));
        connect(m_socket, SIGNAL(disconnected()),this, SLOT(onSocketDisconnected()));
        connect(m_socket, SIGNAL(bytesWritten(qint64)),this, SLOT(onSocketBytesWritten(qint64)));
        connect(m_socket, SIGNAL(error(QAbstractSocket::SocketError)), this, SLOT(onSocketError(QAbstractSocket::SocketError)));
    
        qDebug() << "connecting...";
        m_socket->connectToHost("localhost", 11000);
        // The rest of the method is the same
    }
    

    Delete the socket on disconnection, so that it is deallocated and will be created again on reconnection:

    void TestClient::onSocketDisconnected()
    {
        qDebug() << "disconnected...";
        m_socket->deleteLater();
        m_socket = nullptr;
    
        emit fsmEvtError();
    }
    

  • Moderators

    Hey, I'm relatively sure you've hit a bug. Your code looks fine to me, so I suggest posting it on the bugtracker.



  • Hi kshegunov, in the meantime by experimenting and by some crossposting (https://stackoverflow.com/questions/59593665/qtcpsocket-fails-to-transmit-data-when-reconnecting-to-server-after-remotehostcl) a couple of solutions came out, reported below.

    However, such solutions tackle the problem with different approach than the one shown in my original code, so if there is a bug in the QT codebase, they could actually circumvent it.

    Asking you a favor: please have a look at the solutions below too, if you still think my original code was legit and that the solutions work around a possible bug in QT, I'll go on posting the issue in the bugtracker

    Solution 1
    Not using waitForConnected() function and completely relying on the connected() event plus some QTimer to manage the timeout for a connection attempt. Apparently, the use of such blocking function messes up something in the event loop, even if it is not clear to me how - the connected() event and all the state machine events are still fired up! Maybe this is a QT bug...

    Anyway, to test this solution simply remove the following lines from onfsmConnectEntered():

        // Wait for connection result
        if(!m_socket->waitForConnected(10000))
        {
            qDebug() << "Error: " << m_socket->errorString();
            emit fsmEvtError();
        }
    

    By adding a QTimer, a connection timeout can be handled so that waitForConnected() can be entirely replaced.

    Solution 2
    Creating the QTcpSocket on connection and destroying it on disconnection, so that the socket context is completely discarded and freshly created at every reconnection. Modifications to my original code:

    Move socket creation from class constructor to onfsmConnectEntered() - handler for entry in the "Connect" QState:

    void TestClient::onfsmConnectEntered()
    {    
        m_socket = new QTcpSocket(this);
    
        connect(m_socket, SIGNAL(connected()),this, SLOT(onSocketConnected()));
        connect(m_socket, SIGNAL(disconnected()),this, SLOT(onSocketDisconnected()));
        connect(m_socket, SIGNAL(bytesWritten(qint64)),this, SLOT(onSocketBytesWritten(qint64)));
        connect(m_socket, SIGNAL(error(QAbstractSocket::SocketError)), this, SLOT(onSocketError(QAbstractSocket::SocketError)));
    
        qDebug() << "connecting...";
        m_socket->connectToHost("localhost", 11000);
        // The rest of the method is the same
    }
    

    Delete the socket on disconnection, so that it is deallocated and will be created again on reconnection:

    void TestClient::onSocketDisconnected()
    {
        qDebug() << "disconnected...";
        m_socket->deleteLater();
        m_socket = nullptr;
    
        emit fsmEvtError();
    }
    

  • Moderators

    @Andrea-Narciso said in QTcpSocket fails to transmit data when reconnecting to server after RemoteHostClosedError:

    Not using waitForConnected() function and completely relying on the connected() event plus some QTimer to manage the timeout for a connection attempt.

    This is a good advice. The waitFor*** have been finicky forever and even the docs acknowledge in reality they're not to be used. If that works for you, then this is the way to go.

    Creating the QTcpSocket on connection and destroying it on disconnection, so that the socket context is completely discarded and freshly created at every reconnection.

    I don't consider this normal behavior, no. After the disconnect runs the socket should be available for reuse. Recreating the object should not be necessary.



  • To sum up and close the topic: the proper solution would be Solution 1 from a couple of posts above:

    @Andrea-Narciso said in QTcpSocket fails to transmit data when reconnecting to server after RemoteHostClosedError:

    Not using waitForConnected() function and completely relying on the connected() event plus some QTimer to manage the timeout for a connection attempt. Apparently, the use of such blocking function messes up something in the event loop, even if it is not clear to me how - the connected() event and all the state machine events are still fired up! Maybe this is a QT bug...

    Anyway, to test this solution simply remove the following lines from onfsmConnectEntered():

        // Wait for connection result
        if(!m_socket->waitForConnected(10000))
        {
            qDebug() << "Error: " << m_socket->errorString();
            emit fsmEvtError();
        }
    

    By adding a QTimer, a connection timeout can be handled so that waitForConnected() can be entirely replaced.


Log in to reply