首页
学习
活动
专区
圈层
工具
发布
社区首页 >问答首页 >QTcpSocket在RemoteHostClosedError之后重新连接到服务器时未能传输数据

QTcpSocket在RemoteHostClosedError之后重新连接到服务器时未能传输数据
EN

Stack Overflow用户
提问于 2020-01-04 18:32:56
回答 1查看 962关注 0票数 1

在问题的末尾看到EDIT1,找到一个可能的解决方案--如果有人能对我的解释发表评论,那就太好了,这样我就能更好地理解发生了什么,

我正在编写一个简单的TCP客户端,它基于QTcpSocket,由QStateMachine管理(连接到服务器->传输数据->,如果由于任何原因断开连接,重新连接到服务器)。

我注意到,如果连接在服务器端被关闭(客户端使用RemoteHostClosedError通知),则在重新连接QTcpSocket wire ()方法成功后,但没有在线路上传输数据--服务器没有接收到任何数据,客户端的bytesWritten()信号也不会触发。

我在错误()信号(https://doc.qt.io/qt-5/qabstractsocket.html#error)文档中发现

当发出此信号时,套接字可能无法准备重新连接尝试。在这种情况下,重新连接的尝试应该从事件循环中执行“。

我想我已经同意了,因为重连接发生在一种QStateMachine状态中,而QStateMachine应该根据QT文档有自己的事件循环。

下面是一些用于重现问题的简化代码(抱歉,不是很简单,但我找不到更简单的方法来显示问题):

testclient.h

代码语言:javascript
复制
#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

代码语言:javascript
复制
#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

代码语言:javascript
复制
#include <QCoreApplication>
#include "testclient.h"

int main(int argc, char *argv[])
{
    QCoreApplication a(argc, argv);

    TestClient client(&a);
    client.start();

    return a.exec();
}

要进行测试,只需启动netcat (nc -l -p 11000),然后在收到测试消息后关闭nc进程,然后再次重新启动它。第二次,没有接收到测试消息,而且我们没有onSocketBytesWritten()打印输出,请参见下面的内容:

代码语言:javascript
复制
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!

EDIT1: --我发现,如果在连接上创建QTcpSocket,在断开连接时销毁它,问题就不会发生。这是使用套接字的预期/正确方式吗?

难道不可能只创建一次套接字而只连接/断开连接吗?也许这只是一个以特定方式冲洗或清理的问题,但到目前为止我还没有找到。

下面是使上面的代码能够在服务器端断开连接时工作的修改:

将套接字创建从类构造函数移动到onfsmConnectEntered()处理程序,以便输入"Connect“QState:

代码语言:javascript
复制
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
}

在断开连接时删除套接字,以便将其解除分配,并在重新连接时再次创建:

代码语言:javascript
复制
void TestClient::onSocketDisconnected()
{
    qDebug() << "disconnected...";
    m_socket->deleteLater();
    m_socket = nullptr;

    emit fsmEvtError();
}
EN

回答 1

Stack Overflow用户

回答已采纳

发布于 2020-01-04 21:25:10

不要使用waitForX方法,因为它们阻止事件循环,并阻止它们使用该资源,因为信号没有正确地完成它们的工作或QStateMachine。

考虑到上述情况,解决办法是:

代码语言:javascript
复制
void TestClient::onfsmConnectEntered()
{
    m_socket->connectToHost("localhost", 11000);
}

但即便如此,您的代码也有错误,因为它不考虑其他情况,例如:

如果当您启动服务器没有运行的客户端时,您的应用程序将尝试连接错误,而没有其他任何错误。如果服务器故障的时间比设置为的10000 ms超时时间长,则会发生与上一次情况相同的情况。

然后,我们的想法是尝试连接,直到您确定了连接,这可以通过一个具有适当时间的QTimer来完成。

testclient.h

代码语言:javascript
复制
#ifndef TESTCLIENT_H
#define TESTCLIENT_H

#include <QObject>

class QTcpSocket;
class QStateMachine;
class QTimer;

#include <QAbstractSocket>

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();
private:
    // Member variables
    QTcpSocket*         m_socket;       // TCP socket used for communications to server
    QStateMachine*      m_clientFsm;      // FSM defining general client behaviour
    QTimer*             m_timer;
private:
    void createClientFsm();             // Create client FSM
    void tryConnect();
};

#endif // TESTCLIENT_H

testclient.cpp

代码语言:javascript
复制
#include "testclient.h"
#include <QState>
#include <QStateMachine>
#include <QTcpSocket>
#include <QThread>      // Sleep
#include <QTimer>

//-----------------------------------------------------------------------------
// PUBLIC METHODS
//-----------------------------------------------------------------------------
TestClient::TestClient(QObject *parent) : QObject(parent)
{
    m_socket = new QTcpSocket(this);
    m_timer = new QTimer(this);
    m_timer->setInterval(100);
    connect(m_timer, &QTimer::timeout, this, &TestClient::tryConnect);
    connect(m_socket, &QAbstractSocket::connected,this, &TestClient::onSocketConnected);
    connect(m_socket, &QAbstractSocket::disconnected,this, &TestClient::onSocketDisconnected);
    connect(m_socket, &QIODevice::bytesWritten,this, &TestClient::onSocketBytesWritten);
    connect(m_socket, QOverload<QAbstractSocket::SocketError>::of(&QAbstractSocket::error), this, &TestClient::onSocketError);
}
void TestClient::start()
{
    createClientFsm();
    m_clientFsm->start();
}
//-----------------------------------------------------------------------------
// TCP CONNECTION MANAGEMENT SLOTS
//-----------------------------------------------------------------------------
void TestClient::onSocketConnected()
{
    m_timer->stop();
    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, &QAbstractState::entered, this, &TestClient::onfsmConnectEntered);
    connect(sTransmit, &QAbstractState::entered, this, &TestClient::onfsmTransmitEntered);
    // Create state machine
    m_clientFsm->addState(sConnect);
    m_clientFsm->addState(sTransmit);
    m_clientFsm->setInitialState(sConnect);
}
void TestClient::tryConnect(){
    m_socket->connectToHost("localhost", 11000);
}
void TestClient::onfsmConnectEntered()
{
    m_timer->start();
}
void TestClient::onfsmTransmitEntered()
{
    qDebug() << "sending data...";
    m_socket->write("TEST MESSAGE");
}
票数 1
EN
页面原文内容由Stack Overflow提供。腾讯云小微IT领域专用引擎提供翻译支持
原文链接:

https://stackoverflow.com/questions/59593665

复制
相关文章

相似问题

领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档