26

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;
}
Chilledrat
  • 2,593
  • 3
  • 28
  • 38
Roman Rdgz
  • 12,836
  • 41
  • 131
  • 207
  • 3
    How long did you wait with the cable unplugged? – Mat May 04 '12 at 08:19
  • Don't know... 10 seconds? Much less was necessary for POSIX sockets to determine they were disconnected. How much should I wait? – Roman Rdgz May 04 '12 at 08:31
  • TCP timeouts are way above that. Wait for a couple minutes at least. – Mat May 04 '12 at 08:32
  • In this similar question http://stackoverflow.com/questions/10331016, I mentioned (comments after my answer) that TCP_KEEPALIVE might help detecting the network disconnection after some time. Did that help? Are you looking for a faster way? – stefaanv May 04 '12 at 08:37
  • But with POSIX sockets, it takes about a second for them to start saying "network unreachable". Why QTcpSockets doesn't? Isn't there any way of detecting this before those 2 minutes? (without using a keepalive protocol) – Roman Rdgz May 04 '12 at 08:39
  • can you provide more code about the construction of the socket? – andrea.marangoni May 04 '12 at 08:40
  • Sure @riskio I have added that piece of code – Roman Rdgz May 04 '12 at 08:48
  • @RomanRdgz: okay, I'd be interested to know how you get the "network unreachable" in a second with POSIX sockets. It might give a clue on how to do it with qt-sockets, because in the end on unix platforms, qt-sockets use POSIX sockets. – stefaanv May 04 '12 at 09:10
  • @stefaanv I got the network unreachable when reading the errno ENETUNREACH after calling read (it returned -1). But now I can't go that way any more, because I must wait until data is ready to call read, so if disconnection happens while waiting, no -1 will be returned. If I check read all the time, then it's now receiving, that was a problem I solved this morning, so when read returns 0 I wait until new data is ready to be read. – Roman Rdgz May 04 '12 at 09:29

4 Answers4

23

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));
Roman Rdgz
  • 12,836
  • 41
  • 131
  • 207
  • 10
    For anyone else trying this: `#include #include #include ` – kwahn Aug 07 '14 at 17:41
  • 2
    Does this code go right after you have created the QTcpSocket? And do you have to call another function to set these option's on Qt's socket (or do they take effect on the Qt socket immediately). – TSG Sep 23 '14 at 22:17
  • @TSG I have the same question as you. – developer01 Oct 07 '21 at 23:03
13

I've been facing similar problems with a QT client app. Basically I handle it with Timers, signals and slots. When the app starts up, it starts a 4 second checkConnectionTimer. Every 4 seconds the timer expires, if the client socket state != AbstractSocket::Connected or Connecting, it attempt to connect with clientSocket->connectToHost

When the socket signals "connected()", it starts a 5 second server heartbeat timer. The server should send a one byte heartbeat message to its clients every 4 seconds. When I get the heartbeat (or any type of message signaled by readyRead()), I restart the heartbeat timer. So if the heartbeat timer ever has a timeout, I assume the connection to be down and it calls clientSocket->disconnectFromHost ();

This is working very well for all different kinds of disconnects on the server, graceful or otherwise (yanking cable). Yes it requires custom heartbeat type of stuff, but at the end of the day it was the quickest and most portable solution.

I wasn't to keen on setting KEEPALIVE timeouts in the kernel. This way its more portable. In the constructor:

connect(clientSocket, SIGNAL(readyRead()), this, SLOT(readMessage()));
connect(clientSocket, SIGNAL(connected()), this, SLOT(socketConnected()));
connect(clientSocket, SIGNAL(disconnected()), this, SLOT(socketDisconnected()));
connect(heartbeatTimer, SIGNAL(timeout()), this, SLOT(serverTimeout()));
...
// Other Methods

void NetworkClient::checkConnection(){
    if (clientSocket->state() != QAbstractSocket::ConnectedState &&
            clientSocket->state() != QAbstractSocket::ConnectingState){
        connectSocketToHost(clientSocket, hostAddress, port);
    } 
}

void NetworkClient::readMessage()
{
    // Restart the timer by calling start.
    heartbeatTimer->start(5000);
    //Read the data from the socket
    ...
}

void NetworkClient::socketConnected (){
    heartbeatTimer->start(5000);
}

void NetworkClient::socketDisconnected (){
    prioResponseTimer->stop();
}

void NetworkClient::serverTimeout () {
    clientSocket->disconnectFromHost();
}
Matt Brown
  • 435
  • 4
  • 17
1

try this signal slot connection:

connect(this, SIGNAL(stateChanged(QAbstractSocket::SocketState)), this, SLOT(onStateChanged(QAbstractSocket::SocketState)));

at slot implementation:

void TCPWorker::onStateChanged(QAbstractSocket::SocketState socketState ){
qDebug()<< "|GSTCPWorkerThread::onStateChanged|"<<socketState;
...}

I have the same problem, but instead your problem ( always connected ), i have delay 4-5 seconds to receive disconnect signals, after unplugget ethernet wire.

Still looking solution, post answer if find.

ecoretchi
  • 127
  • 1
  • 3
  • 6
    For others reading, this does not work. onStateChanged() does not get called when the wire is unplugged. – kwahn Aug 07 '14 at 17:13
0

try my template of client in Qt:

class Client: public QTcpSocket {
   Q_OBJECT
public:
    Client(const QHostAddress&, int port, QObject* parent= 0);
    ~Client();
    void Client::sendMessage(const QString& );
private slots:
    void readyRead();
    void connected();
public slots:
    void doConnect();
};

on cpp:

void Client::readyRead() {

    // if you need to read the answer of server..
    while (this->canReadLine()) {
    }
}

void Client::doConnect() {
    this->connectToHost(ip_, port_);
    qDebug() << " INFO : " << QDateTime::currentDateTime()
            << " : CONNESSIONE...";
}

void Client::connected() {
    qDebug() << " INFO : " << QDateTime::currentDateTime() << " : CONNESSO a "
            << ip_ << " e PORTA " << port_;
    //do stuff if you need
}


void Client::sendMessage(const QString& message) {
    this->write(message.toUtf8());
    this->write("\n"); //every message ends with a new line
}

i omitted some code as constructor and slots connections.. try with this and if it doesn t work maybe there is something wrong on server side..

andrea.marangoni
  • 1,499
  • 8
  • 22
  • 1
    I'm sure this works, it's more or less what I have. But connection already works, what I want is to detect disconnection, and that's not happening at your template either. Besides, server side is OK because it worked with POSIX sockets – Roman Rdgz May 04 '12 at 09:24
  • do you use `void QAbstractSocket::disconnectFromHost ()` somewhere? – andrea.marangoni May 04 '12 at 10:22