QTcpSocket state always connected, even unplugging ethernet wire

Roman Rdgz picture Roman Rdgz · May 4, 2012 · Viewed 25.9k times · Source

I have a QTcpSocket and I am reading into a loop. Each time a full packet has been read, or there has been an error, I manually check the status of the socket inside the loop, with:

    while(true){
    if(socket->state()==QAbstractSocket::ConnectedState){
        qDebug()<<"Socket status: connected. Looking for packets...";
        if(socket->waitForReadyRead(2000)){
        //...
    }

When I execute de program, once connected and the loop starts, it always prints qDebug()<<"Socket status: connected. Looking for packets..."; and then stucks at waitForReadyRead until some data is ready to be read.

The problem is that disconnections are not detected. If I disconnect from network from the OS options, or even if I unplug the ethernet wire, it behaves the same: Socket state equals QAbstractSocket::ConnectedState, so it goes on, but without receiving anything of course.

I also tried to detect disconnections connecting disconnected() signal (after fist connection) to a reconnect function:

// Detect disconnection in order to reconnect
    connect(socket, SIGNAL(disconnected()), this, SLOT(reconnect()));

void MyClass::reconnect(){
    qDebug()<<"Signal DISCONNECTED emitted. Now trying to reconnect";
    panelGUI->mostrarValueOffline();
    socket->close();
    prepareSocket((Global::directionIPSerialServer).toLocal8Bit().data(), 8008, socket);
    qDebug()<<"Reconnected? Status: "<<socket->state();
}

But signal is never emited, because this code is never executed. Which is logical, since it looks like socket state is always ConnectedState.

If I plug again, connection is restored and starts to receive data again, but I do want to detect disconnections to show "Disconnected" at the GUI.

Why is QTcpSocket behaving this way, and how can I solve this problem?

EDIT: I'm creating socket at the class constructor, and then initialising calling prepareSocket function:

socket = new QTcpSocket();
socket->moveToThread(this);

bool prepareSocket(QString address, int port, QTcpSocket *socket) {
    socket->connectToHost(address, port);
    if(!socket->waitForConnected(2000)){
        qDebug()<<"Error creating socket: "<<socket->errorString();
        sleep(1);
        return false;
    }
    return true;
}

Answer

Roman Rdgz picture Roman Rdgz · May 4, 2012

Finally found the solution in this Qt forum:

If no data is exchanged for a certain while, TCP will start sending keep-alive segments (basically, ACK segments with the acknowledgement number set to the current sequence number less one). The other peer then replies with another acknowledgement. If this acknowledgment is not received within a certain number of probe segments, the connection is automatically dropped. The little problem is that the kernel starts sending keep-alive segments after 2 hours since when the connection becomes idle! Therefore, you need to change this value (if your OS allows that) or implement your own keep-alive mechanism in your protocol (like many protocols do, e.g. SSH). Linux allows you to change it using setsockopt:

int enableKeepAlive = 1;
int fd = socket->socketDescriptor();
setsockopt(fd, SOL_SOCKET, SO_KEEPALIVE, &enableKeepAlive, sizeof(enableKeepAlive));

int maxIdle = 10; /* seconds */
setsockopt(fd, IPPROTO_TCP, TCP_KEEPIDLE, &maxIdle, sizeof(maxIdle));

int count = 3;  // send up to 3 keepalive packets out, then disconnect if no response
setsockopt(fd, SOL_TCP, TCP_KEEPCNT, &count, sizeof(count));

int interval = 2;   // send a keepalive packet out every 2 seconds (after the 5 second idle period)
setsockopt(fd, SOL_TCP, TCP_KEEPINTVL, &interval, sizeof(interval));