诡异的 QTcpSocket 行为

Posted

技术标签:

【中文标题】诡异的 QTcpSocket 行为【英文标题】:Spooky QTcpSocket behaviour 【发布时间】:2020-10-20 15:01:00 【问题描述】:

我在调试接下来的几行代码时一直在做噩梦,一定有一些东西隐藏在我所看到的之外。

这是客户端和服务器之间的连接

QByteArray Client::request(QByteArray cmd)

    //DEBUG << "Command: " << cmd.toStdString().c_str();
    QTcpSocket *socket = new QTcpSocket(this);
    socket->connectToHost(hostAddress, port, QIODevice::ReadWrite);
    if (socket->waitForConnected(TIMEOUT))
    
        socket->write(cmd);
        DEBUG << "Size of bytes written :" << sizeof (cmd);
    
    else DEBUG << "Couldn't connect to socket";
    if (socket->waitForBytesWritten(TIMEOUT)) DEBUG << "Command sent";
    else DEBUG << "Couldn't write to socket";
    if (socket->waitForReadyRead(TIMEOUT))
    
        QByteArray data = socket->readAll();
        socket->close();
        return data;
    
    else DEBUG << "No reply from Server";
    return QByteArray();

对于客户端来说基本上就是这样,对于服务器来说,这里是重要的 sn-ps。

class ConnectionHandler : public QTcpServer

    Q_OBJECT


public:
    explicit ConnectionHandler(QObject *parent = 0);
    void write(QByteArray);

protected:
    void ConnectionHandler::incomingConnection(qintptr descriptor)

    DEBUG << "ConnectionHandler:" << "Incoming Connection :" << descriptor;
    ConnectionThread *thread = new ConnectionThread(this, descriptor);
    connect(thread, &QThread::finished, thread, &QThread::deleteLater);
    connect(thread, &ConnectionThread::signalIncomingMessage, this, &ConnectionHandler::slotIncomingMessage);
    connect(this, &ConnectionHandler::signalOutgoingMessage, thread, &ConnectionThread::slotOutgoingMessage);
    thread->start();


public slots:
    void slotIncomingMessage(QByteArray);
    void slotListen(bool checked)
    if (checked)
    
        if (!this->listen(QHostAddress::LocalHost, PORT_NUMBER)) 
            DEBUG << "ConnectionHandler:" << "Could not start the server!";
         else 
            DEBUG << "ConnectionHandler:" << "Listening...";
        
     else 
        this->close();
        DEBUG << "ConnectionHandler:" << "Connection Closed!";
    


signals:
    void signalOutgoingMessage(QByteArray);
;
class ConnectionThread : public QThread

    Q_OBJECT
public:
    ConnectionThread(QObject* parent = 0, qintptr descriptor = -1)
    if (descriptor != -1)
    
        socket = new QTcpSocket();

        DEBUG << "ConnectionThread: Connecting to socket number" << descriptor;
        if (!socket->setSocketDescriptor(descriptor))
        
            DEBUG << "ConnectionThread: Connection failed.";
            DEBUG << socket->errorString();
        
        else
        
            DEBUG << "ConnectionThread: Connected Successfully.";
            connect(socket, &QAbstractSocket::disconnected, this, &ConnectionThread::slotSocketDisconnected);
        
    
    else 
        DEBUG << "ConnectionThread: Please provide a descriptor for the connection thread";
    

    void run() override
    if (socket->state() != QAbstractSocket::ConnectedState)
    
        DEBUG << "ConnectionThread: Socket is not connected!";
        DEBUG << "ConnectionThread: Closing Thread!";
        emit signalThreadError(socket->errorString());
    
    else
    
        DEBUG << "ConnectionThread: Socket is Connected.";
        connect(socket, &QIODevice::readyRead, this, &ConnectionThread::slotThreadReadyRead, Qt::DirectConnection);
        exec();
    


signals:
    void signalThreadError(QString);
    void signalIncomingMessage(QByteArray);

public slots:
    void slotThreadReadyRead()
    QByteArray msg = socket->readAll();

    if (!msg.isEmpty()) 
        emit signalIncomingMessage(msg);
    

    DEBUG << "ConnectionThread: Data in:" << msg;

    void slotSocketDisconnected();
    void slotOutgoingMessage(QByteArray msg)

    if (socket != nullptr) 
        socket->write(msg);
        if (socket->waitForBytesWritten(TIMEOUT)) 
            DEBUG << "ConnectionThread: Outgoing Message: " << msg;
         else 
            DEBUG << "ConnectionThread: Outgoing Message Timeout";
        
    


private:
    QTcpSocket *socket = nullptr;
;

基本上会发生什么,它有时会收到来自客户端的传入命令,而大多数时候它不会,不仅如此,它实际上会接收一些命令并忽略一些不同的命令。

这是一个更诡异的行为,注意Client::request() 中的//DEBUG &lt;&lt; "Command: " &lt;&lt; cmd.toStdString().c_str(); 行,当这行被注释掉时,服务器实际上收到了一些命令,如果没有,服务器就聋了。

这是注释掉该行时的输出:

Size of bytes written : 8
Command sent
target num = 4  (a reply was received)
Size of bytes written : 8
Command sent
No reply from Server

当提到的行没有被注释掉时,这是另一个输出:

Command:  NUMB

Size of bytes written : 8
Command sent
No reply from Server
target num = 0
Command:  TRAN

Size of bytes written : 8
Command sent
No reply from Server

更新 解决客户端套接字写入和线程打开之间发生的竞争对我来说是这样,在我的情况下线程使用是不必要的,所以我放弃了它。由@Botje 回答

【问题讨论】:

命名表明您在服务器代码中使用线程,这会使事情变得无止境地复杂化。请提供minimal reproducible example。 这是服务器端的一个奇怪线程,它是否从另一个线程接收套接字? QObjects 是线程感知的。实际上,连接的形成方式和信号的连接方式确实很重要。那里可能会发生很多错误,甚至是一些 UB。 Qt 也有问题在于 QTcpServer 和 run() 覆盖的使用被认为是使用 Qthread 的过时方法 至少存在一个竞争条件:线程开始与您的套接字接收数据,从而触发其 readyRead 信号。请显示您的其余代码! 不相关:sizeof cmd 将始终为 8。您的意思是 cmd.size() @G.M.刚刚做了,Client不过是它的request() 【参考方案1】:

问题是由竞争条件引起的:

    (主线程)ConnectionHandler::incomingConnection 创建一个新的ConnectionThread (主线程)ConnectionThread构造函数创建一个socket,它也归主线程所有 (主线程)返回事件循环,轮询套接字上的传入数据 (ConnectionThread) run 方法将一个槽连接到 readyRead 信号

如果数据在 2 和 4 之间的窗口中到达,readyRead 信号将不会接收到它。 有几种可能的解决方案,但最简单的可能是在构造之后将套接字移动到 ConnectionThread,或者将其构造延迟到 ConnectionThread::run 方法。

【讨论】:

以上是关于诡异的 QTcpSocket 行为的主要内容,如果未能解决你的问题,请参考以下文章

iOS 15.3+ SwiftUI的Form视图中嵌入List及Button若干诡异行为的解决

同一对象中的 QTcpSocket 到 QTcpSocket

Python QtNetwork.QTcpSocket.readAll() 和 QtNetwork.QTcpSocket.write() 不起作用

无法使用 QtNetwork,因为应用程序使用不同的线程

C++ Qt - QTcpSocket - 找不到文件

Qt - 当一个类中有多个 QTcpSocket 时,我如何知道哪个 QTcpSocket 发出了 readyRead 信号?