QTcpSocket状态始终为已连接,即使拔掉以太网线。

26

我有一个QTcpSocket,并且正在循环读取。每次读取完整的数据包或发生错误时,我都会在循环内手动检查套接字的状态:

    while(true){
    if(socket->state()==QAbstractSocket::ConnectedState){
        qDebug()<<"Socket status: connected. Looking for packets...";
        if(socket->waitForReadyRead(2000)){
        //...
    }
当我执行程序时,一旦连接并启动循环,它总是打印qDebug()<<"Socket status: connected. Looking for packets..."; 然后停在waitForReadyRead直到有数据准备好读取。
问题在于无法检测到断开连接。如果我从操作系统选项中断开网络连接,甚至拔掉以太网线,它的行为都相同:套接字状态等于QAbstractSocket::ConnectedState,所以继续运行,但当然不会接收任何东西。
我还尝试通过将disconnected()信号(在第一次连接之后)连接到重新连接函数来检测断开连接:
// 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();
}

但是信号从未被发射,因为这段代码从未被执行。这很合理,因为套接字状态似乎始终是ConnectedState

如果我重新插入插头,连接就会恢复,并再次开始接收数据,但我确实想检测到断开连接并在 GUI 上显示“已断开连接”。

为��么 QTcpSocket 会以这种方式运行,我该如何解决这个问题?

编辑:我在类构造函数中创建套接字,然后调用 prepareSocket 函数进行初始化:

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;
}

3
你拔掉电缆后等了多久? - Mat
不知道...10秒钟吗?POSIX套接字只需要更少的时间就可以确定它们已经断开连接了。我应该等多久? - Roman Rdgz
TCP超时时间远远超过了预期。请至少等待几分钟。 - Mat
你能提供更多有关socket构建的代码吗? - andrea.marangoni
当然,@riskio,我已经添加了那段代码。 - Roman Rdgz
显示剩余4条评论
4个回答

23

Qt论坛上终于找到了解决方案:

如果一段时间内没有交换数据,TCP将开始发送保持活动分节(基本上是具有确认号设置为当前序列号减1的ACK分节)。另一个对等方然后回复他们自己的另一个确认。如果这个确认在一定数量的探测分节内未收到,则自动断开连接。小问题是内核在连接变得空闲2小时后开始发送保持活动分节!因此,您需要更改此值(如果您的操作系统允许),或在协议中实现自己的保持活动机制(像许多协议一样,例如SSH)。Linux允许您使用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));

10
如果有人想尝试这个代码,需要包含以下库: #include <sys/socket.h> #include <netinet/in.h> #include <netinet/tcp.h> - kwahn
2
这段代码是在您创建QTcpSocket之后立即执行的吗?您是否需要调用另一个函数来设置Qt套接字上的这些选项(或者它们会立即生效)? - TSG
@TSG 我和你有同样的问题。 - developer01

14

我在QT客户端应用程序中遇到了类似的问题。基本上,我使用定时器、信号和槽来处理它。当应用程序启动时,它会启动一个4秒的checkConnectionTimer。每4秒钟,定时器过期一次,如果客户端套接字的状态!=AbstractSocket::Connected或Connecting,则尝试使用clientSocket->connectToHost连接。

当套接字发出"connected()"信号时,它会启动一个5秒的服务器心跳定时器。服务器应该每4秒向其客户端发送一个一字节的心跳消息。当我收到心跳(或任何类型的readyRead()信号通知的消息)时,我重新启动心跳定时器。因此,如果心跳定时器有超时,我就认为连接已断开,并调用clientSocket->disconnectFromHost ();

对于服务器上的所有不同类型的断开连接(优雅或其他方式),这都运作得很好。是的,它需要自定义的心跳类型的东西,但最终它是最快捷和可移植的解决方案。

我不太愿意在内核中设置KEEPALIVE超时。这样更具可移植性。在构造函数中:

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();
}

3
我尝试了页面上所有的答案,这是唯一一个对我有用的。 - kwahn
如果网络过载,心跳包被延迟了2或3秒钟,然后才到达,那该怎么办? - Xsmael
你的 prioResponseTimer 是从哪里来的? - developer01

1

尝试使用这个信号槽连接:

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

在槽函数实现中:

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

我有同样的问题,但与你的问题不同(始终连接),我在拔掉以太网线后接收到断开信号需要延迟4-5秒。仍在寻找解决方案,如果找到请发布答案。

6
阅读此信息的其他人请注意,这种方法行不通。当电线拔掉时,onStateChanged() 不会被调用。请留意。 - kwahn

0

尝试使用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();
};

在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
}

我省略了一些构造函数和插槽连接的代码...尝试使用这个,如果不起作用,可能是服务器端出了问题...


1
我相信这个代码是可行的,基本上和我的差不多。但连接已经建立了,我想要的是检测断开连接,而你的模板也没有实现这一点。此外,服务器端没问题,因为它在 POSIX sockets 上运行正常。 - Roman Rdgz
你是否在某处使用了 void QAbstractSocket::disconnectFromHost() - andrea.marangoni

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接