Skip to content
  • Categories
  • Recent
  • Tags
  • Popular
  • Users
  • Groups
  • Search
  • Get Qt Extensions
  • Unsolved
Collapse
Brand Logo
  1. Home
  2. Qt Development
  3. General and Desktop
  4. QTcpSocket fails to transmit data when reconnecting to server after RemoteHostClosedError
Forum Updated to NodeBB v4.3 + New Features

QTcpSocket fails to transmit data when reconnecting to server after RemoteHostClosedError

Scheduled Pinned Locked Moved Solved General and Desktop
6 Posts 2 Posters 2.0k Views 1 Watching
  • Oldest to Newest
  • Newest to Oldest
  • Most Votes
Reply
  • Reply as topic
Log in to reply
This topic has been deleted. Only users with topic management privileges can see it.
  • A Offline
    A Offline
    Andrea Narciso
    wrote on 4 Jan 2020, 18:26 last edited by
    #1

    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

    1 Reply Last reply
    0
    • A Andrea Narciso
      6 Jan 2020, 08:26

      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();
      }
      
      A Offline
      A Offline
      Andrea Narciso
      wrote on 6 Jan 2020, 23:18 last edited by
      #6

      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.

      1 Reply Last reply
      1
      • A Offline
        A Offline
        Andrea Narciso
        wrote on 4 Jan 2020, 21:28 last edited by
        #2

        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();
        }
        
        kshegunovK 1 Reply Last reply 5 Jan 2020, 22:27
        0
        • A Andrea Narciso
          4 Jan 2020, 21:28

          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();
          }
          
          kshegunovK Offline
          kshegunovK Offline
          kshegunov
          Moderators
          wrote on 5 Jan 2020, 22:27 last edited by
          #3

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

          Read and abide by the Qt Code of Conduct

          1 Reply Last reply
          1
          • A Offline
            A Offline
            Andrea Narciso
            wrote on 6 Jan 2020, 08:26 last edited by
            #4

            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();
            }
            
            kshegunovK A 2 Replies Last reply 6 Jan 2020, 10:35
            0
            • A Andrea Narciso
              6 Jan 2020, 08:26

              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();
              }
              
              kshegunovK Offline
              kshegunovK Offline
              kshegunov
              Moderators
              wrote on 6 Jan 2020, 10:35 last edited by
              #5

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

              Read and abide by the Qt Code of Conduct

              1 Reply Last reply
              1
              • A Andrea Narciso
                6 Jan 2020, 08:26

                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();
                }
                
                A Offline
                A Offline
                Andrea Narciso
                wrote on 6 Jan 2020, 23:18 last edited by
                #6

                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.

                1 Reply Last reply
                1

                1/6

                4 Jan 2020, 18:26

                • Login

                • Login or register to search.
                1 out of 6
                • First post
                  1/6
                  Last post
                0
                • Categories
                • Recent
                • Tags
                • Popular
                • Users
                • Groups
                • Search
                • Get Qt Extensions
                • Unsolved