Impossible to bind QTcpSocket again after connection error



  • Hi,

    in one our application we encountered a problem with reconnecting to the server over a TCP connection after accidentally disconnecting network cable. After some examination I found that the problem is related to binding QTcpSocket. After a fixed network cable QTcpSocket::bind() got stuck on QAbstract::SocketError = 7 (Connection time out) or QAbstract::SocketError = 7 (Host unreachable).

    I created a test class with one QTcpSocket instance for manually call QTcpSocket methods connectToHost(), bind(), disconnectFromHost() etc. The source code is below.

    SocketConnector.h

    #ifndef SOCKETCONNECTOR_H
    #define SOCKETCONNECTOR_H
    
    #include <QTcpSocket>
    #include <QObject>
    #include <QPushButton>
    #include <QLineEdit>
    
    
    class SocketConnector: public QObject
    {
        Q_OBJECT
    
    public:
        SocketConnector();
    
        void setUi(QLineEdit * leBindAddressW, QLineEdit * leHostAddressW,
                   QLineEdit * leHostPortW);
    
    private:
        QTcpSocket socket;
    
        QLineEdit *leBindAddress = nullptr;
        QLineEdit *leHostAddress = nullptr;
        QLineEdit *leHostPort = nullptr;
    
        QString getSocketState() const;
    
    public slots:
        // Slots for QPushButtons
        void onPbState() const;
        void onPbBind();
        void onPbConnect();
        void onPbDisconnect();
        void onPbClose();
        void onPbAbort();
    
    private slots:
        // Slots for QTcpSocket signals
        void onConnect();
        void onDisconnected();
        void onError(QAbstractSocket::SocketError error);
        void onStateChanged(QAbstractSocket::SocketState state);
    };
    
    
    #endif // SOCKETCONNECTOR_H
    

    SocketConnector.cpp

    #include "SocketConnector.h"
    
    #include <QTcpSocket>
    #include <QPushButton>
    #include <QHostAddress>
    #include <QDebug>
    #include <QString>
    
    
    SocketConnector::SocketConnector():
        QObject(nullptr)
    {
        connect(&socket, SIGNAL(connected()), this, SLOT(onConnect()));
        connect(&socket, SIGNAL(disconnected()), this, SLOT(onDisconnected()));
        connect(&socket, SIGNAL(error(QAbstractSocket::SocketError)),
                this, SLOT(onError(QAbstractSocket::SocketError)));
        connect(&socket, SIGNAL(stateChanged(QAbstractSocket::SocketState)),
                this, SLOT(onStateChanged(QAbstractSocket::SocketState)));
    }
    
    void SocketConnector::setUi(QLineEdit *leBindAddressW, QLineEdit *leHostAddressW,
                                QLineEdit *leHostPortW)
    {
        leBindAddress = leBindAddressW;
        leHostAddress = leHostAddressW;
        leHostPort    = leHostPortW;
    }
    
    QString SocketConnector::getSocketState() const
    {
        return QString("SocketState: %1, SocketError: %2 - %3")
                .arg(socket.state())
                .arg(socket.error())
                .arg(socket.errorString());
    }
    
    // -----------------------------------------------------------------------------
    // Slots for QTcpSocket signals
    
    
    void SocketConnector::onConnect()
    {
        qDebug() << "Slot onConnected():" << getSocketState();
    }
    
    void SocketConnector::onDisconnected()
    {
        qDebug() << "Slot onDisconnected():" << getSocketState();
    }
    
    void SocketConnector::onError(QAbstractSocket::SocketError error)
    {
        qDebug() << "Slot onError(): error=" << error
                 << " - " << getSocketState();
    }
    
    void SocketConnector::onStateChanged(QAbstractSocket::SocketState state)
    {
        qDebug() << "Slot onStateChanged(): state=" << state << getSocketState();
    }
    
    
    // -----------------------------------------------------------------------------
    // Slots for QPushButton activating actions bind, connection etc.
    
    
    void SocketConnector::onPbState() const
    {
        qDebug() << "onPbState():" << getSocketState();
    }
    
    void SocketConnector::onPbBind()
    {
        QHostAddress bindAddress = QHostAddress(leBindAddress->text());
    
        qDebug() << "onPbBind(): Pressed";
        bool ok = socket.bind(bindAddress, 0, QAbstractSocket::ReuseAddressHint);
        qDebug() << "onPbBind(): ok =" << ok << getSocketState();
    }
    
    void SocketConnector::onPbConnect()
    {
        QString hostAddress = leHostAddress->text();
        int hostPort = leHostPort->text().toInt();
    
        qDebug() << "onPbConnect(): Pressed";
        socket.connectToHost(hostAddress, hostPort);
        qDebug() << "onPbConnect():" << getSocketState();
    }
    
    void SocketConnector::onPbDisconnect()
    {
        qDebug() << "onPbDisconnect(): Pressed";
        socket.disconnectFromHost();
        qDebug() << "onPbDisconnect():" << getSocketState();
    }
    
    void SocketConnector::onPbClose()
    {
        qDebug() << "onPbClose(): Pressed";
        socket.close();
        qDebug() << "onPbClose():" << getSocketState();
    }
    
    void SocketConnector::onPbAbort()
    {
        qDebug() << "onPbAbort(): Pressed";
        socket.abort();
        qDebug() << "onPbAbort():" << getSocketState();
    }
    

    The methods begins with prefix onPb are slots for signals from QPushButtons which are connected outside of class SocketConnector. This QPushButtons activates actions of QTcpSocket: bind(), connectToHost() etc.

    The scenario what I have been tried was:

    I disconnected the network cable and called bind() and after that I called connectToHost(). The debug output was:

    onPbBind(): Pressed
    Slot onStateChanged(): state= QAbstractSocket::BoundState "SocketState: 4, SocketError: -1 - Unknown error"
    onPbBind(): ok = true "SocketState: 4, SocketError: -1 - Unknown error"
    onPbConnect(): Pressed
    Slot onStateChanged(): state= QAbstractSocket::HostLookupState "SocketState: 1, SocketError: -1 - Unknown error"
    Slot onStateChanged(): state= QAbstractSocket::ConnectingState "SocketState: 2, SocketError: -1 - Unknown error"
    Slot onStateChanged(): state= QAbstractSocket::UnconnectedState "SocketState: 0, SocketError: 7 - Host unreachable"
    Slot onError(): error= QAbstractSocket::NetworkError - "SocketState: 0, SocketError: 7 - Host unreachable"
    onPbConnect(): "SocketState: 0, SocketError: 7 - Host unreachable"

    I connect the network cable again and called bind() with same parameters as before.

    onPbBind(): Pressed
    Slot onError(): error= QAbstractSocket::NetworkError - "SocketState: 0, SocketError: 7 - Host unreachable"
    onPbBind(): ok = false "SocketState: 0, SocketError: 7 - Host unreachable"

    After this step QTcpSocket::bind() is always failing and QTcpSocket::error() signal have been emitted with SocketError: 7 - Host unreachable. I have been tried to call QTcpSocket::disconnectFromHost(), QTcpSocket::close(), QTcpSocket::abort() but nothing helps. The binding keeps failing with the same error emitted.

    But strange thing occurred in this error state when connectToHost() was called.

    onPbConnect(): Pressed
    Slot onStateChanged(): state= QAbstractSocket::HostLookupState "SocketState: 1, SocketError: 7 - Unknown error"
    Slot onStateChanged(): state= QAbstractSocket::ConnectingState "SocketState: 2, SocketError: 7 - Unknown error"
    onPbConnect(): "SocketState: 2, SocketError: 7 - Unknown error"
    Slot onStateChanged(): state= QAbstractSocket::ConnectedState "SocketState: 3, SocketError: 7 - Unknown error"
    Slot onConnected(): "SocketState: 3, SocketError: 7 - Unknown error"
    onPbDisconnect(): Pressed
    Slot onStateChanged(): state= QAbstractSocket::ClosingState "SocketState: 6, SocketError: 7 - Unknown error"
    Slot onStateChanged(): state= QAbstractSocket::UnconnectedState "SocketState: 0, SocketError: 7 - Unknown error"
    Slot onDisconnected(): "SocketState: 0, SocketError: 7 - Unknown error"
    onPbDisconnect(): "SocketState: 0, SocketError: 7 - Unknown error"

    The connection was successful and after successful disconnection the binding started to work again.

    onPbBind(): Pressed
    Slot onStateChanged(): state= QAbstractSocket::BoundState "SocketState: 4, SocketError: 7 - Unknown error"
    onPbBind(): ok = true "SocketState: 4, SocketError: 7 - Unknown error"
    onPbConnect(): Pressed
    Slot onStateChanged(): state= QAbstractSocket::HostLookupState "SocketState: 1, SocketError: 7 - Unknown error"
    Slot onStateChanged(): state= QAbstractSocket::ConnectingState "SocketState: 2, SocketError: 7 - Unknown error"
    onPbConnect(): "SocketState: 2, SocketError: 7 - Unknown error"
    Slot onStateChanged(): state= QAbstractSocket::ConnectedState "SocketState: 3, SocketError: 7 - Unknown error"
    Slot onConnected(): "SocketState: 3, SocketError: 7 - Unknown error"

    onPbDisconnect(): Pressed
    Slot onStateChanged(): state= QAbstractSocket::ClosingState "SocketState: 6, SocketError: 7 - Unknown error"
    Slot onStateChanged(): state= QAbstractSocket::UnconnectedState "SocketState: 0, SocketError: 7 - Unknown error"
    Slot onDisconnected(): "SocketState: 0, SocketError: 7 - Unknown error"
    onPbDisconnect(): "SocketState: 0, SocketError: 7 - Unknown error"

    Only one solution I have been found is to create QTcpSocket dynamically by new operator and after error occurs delete QTcpSocket and create new one for new connection. But IMHO this is rather workaround than solution.

    Why binding is failing after a fixed network cable? Is there any solution to fix binding?


  • Qt Champions 2017

    @Karlor said in Impossible to bind QTcpSocket again after connection error:

    Why binding is failing after a fixed network cable? Is there any solution to fix binding?

    Use QUdpSocket. You bind UDP sockets (as UDP is a conectionless protocol) and you connect TCP sockets to your known host (as the TCP sockets are connection-oriented), so unless I'm missing something you're just doing it wrong.

    Either use bind() with QUdpSocket or connectToHost with QTcpSocket.



  • @Karlor
    Like @kshegunov says, for TCP connections you will use bind(), listen() & accept() only at the server side, and connect() only at the client side. I don't know about his UDP suggestion, that's a different matter.


  • Qt Champions 2017

    Do you mean the OS' functions in that last post, because I meant Qt's API.



  • @kshegunov
    Is that addressed to me?

    In my reply I was attempting to back up your previous reply. Perhaps I gave too much (non-Qt) information. I haven't even looked up what Qt names its functions, sounds like with QTcpSocket you use bind() only at server side and connectToHost() only at client side.


  • Qt Champions 2017

    @JonB said in Impossible to bind QTcpSocket again after connection error:

    Is that addressed to me?

    Yep, says to whom I replied rightwards of the post's date.

    In my reply I was attempting to back up your previous reply. Perhaps I gave too much (non-Qt) information.

    Mhm, I got that. I Just wondered whether the functions you mentioned you meant as the C functions one'd usually use for networking.



  • @kshegunov
    Yes, indeed they are the C functions I used years ago, and still recall fondly :) They were native under UNIX, and provided under same names by WinSock. IMHO, if you want to get anything done and still understand what's going on underneath all the obfuscation layers C++ attempts to pile on, you need to know what it's ultimately calling at the OS level, because that's all the C++ still has available to it ;-)


  • Qt Champions 2017

    @JonB said in Impossible to bind QTcpSocket again after connection error:

    all the obfuscation layers C++

    We call them abstraction layers, you see. ;)



  • Thank you for your answers.

    I have realized I have missed maybe something important. On client side computers are installed multiple network interfaces.

    I have been informed that the binding in the client side part of our software is implemented for purpose to connect with appropriate network interface to connect to the right server and must use QTcpSocket::bind() with QAbstractSocket::ReuseAddressHint.

    But even with multiple network interfaces I think @kshegunov's answer is still right. I have tested connections to servers without binding and everything works fine.

    I would like to ask to some subquestions to this topic.

    For TCP sockets, this function may be used to specify which interface to use for an outgoing connection, which is useful in case of multiple network interfaces.

    Even with multiple network interfaces the binding is not mandatory and only connectToHost() to IP server address should be sufficient, isn't it?

    • Is it true that binding in mode QAbstractSocket::ReuseAddressHint in calling
    socket.bind(bindAddress, 0, QAbstractSocket::ReuseAddressHint)
    

    is useless, because it will use any available port (the second argument is 0) and reusing has no effect in this case?


  • Qt Champions 2017

    @Karlor said in Impossible to bind QTcpSocket again after connection error:

    Even with multiple network interfaces the binding is not mandatory and only connectToHost() to IP server address should be sufficient, isn't it?

    If it connects then it should be sufficient. I haven't had this particular case, however I imagine the bind() is required to specify your outgoing address (a.k.a. interface), so you're using the correct network. If there isn't ambiguity then I suppose connectToHost would do on its own.

    Is it true that binding in mode QAbstractSocket::ReuseAddressHint in calling
    [snippet]

    That line looks correct, yes.



  • Fortunately in our case the connection via QTcpSocket::connectToHost() without need QTcpSocket::bind() is sufficient. However, this may change in the future, and it may need to specify the outgoing network interface, so the problem of stacked binding of QTcpSocket will be there again.

    I found hypothetic scenario in which things might go worse if an error occurred. Imagine having two network interfaces labeled as N1 and N2. N1 is disconnected from the network. If the user accidentally decides to bind via QTcpSocket::bind() to N1, the connection fails. Changing the connection to N2 is now impossible. I guess it is impossible because QTcpSocket is still bounded to N1, so rebind to N2 fails and the connection to N2 is not possible.

    Do you know how to reset QTcpSocket to use QTcpSocket::bind() again?


Log in to reply
 

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