如果 QTcpServer 尚未关闭,则从 QTcpSocket 读取失败

Reading from QTcpSocket fails if QTcpServer has not been closed

这是一个简单的最小工作示例,它重现了我觉得奇怪的行为(可能是由于我对某些事情的误解):

main.cpp

#include <QApplication>
#include "dialog.h"

int main(int argc, char *argv[]) {
   QApplication app(argc, argv);
   Dialog dialog;
   dialog.show();
   return app.exec();
}

dialog.h

#ifndef DIALOG_H
#define DIALOG_H

#include <QDialog>
class QLabel;
class QTcpServer;
class QTcpSocket;

class Dialog : public QDialog {
   Q_OBJECT

public:
   Dialog(QWidget *parent = 0);

public slots:

   void acceptConnection();
   void readClientData();

private:
   QLabel *label;
   QTcpServer *tcpServer;
   QTcpSocket *socket;
};

#endif

dialog.cpp

#include <QtWidgets>
#include <QtNetwork>
#include "dialog.h"

Dialog::Dialog(QWidget *parent) : QDialog(parent) {
   label = new QLabel("Server is listening...");
   QVBoxLayout *mainLayout = new QVBoxLayout;
   mainLayout->addWidget(label);
   setLayout(mainLayout);

   tcpServer = new QTcpServer;
   connect(tcpServer, SIGNAL(newConnection()), this, SLOT(acceptConnection()));
   tcpServer->listen(QHostAddress::LocalHost, 10055);
}


void Dialog::acceptConnection() {
   socket = tcpServer->nextPendingConnection();
   connect(socket, SIGNAL(readyRead()),  this, SLOT(readClientData()));
}


void Dialog::readClientData() {

   QString data;
   while (socket->canReadLine())
    data += socket->readLine();

   label->setText(data);

   socket->close();
   tcpServer->close();
   tcpServer->deleteLater();
}

编译和 运行 之后,我得到对话框,然后我转到我的浏览器,输入 URL http://localhost:10055 然后...什么都没有。服务器接受连接,但没有数据 (HTTP headers) 被读取并显示在标签中。

只有我将tcpServer->close();放在acceptConnection()槽的末尾(而不是在readClientData()中),数据才能正常读取(标签显示header:GET /HTTP/1.1等)。

完全不明白:为什么服务器要停止监听第一次连接才能正常读取数据?

在 Web 请求中执行多个事务,因此连接不是唯一的,在您的情况下,您正在等待一个套接字存在,但实际上可以同时存在多个套接字,这就是发生在你的情况下,解决方案是使用 sender() 动态处理套接字以获得 readClientData() 中的正确套接字,如下所示:

dialog.h

#ifndef DIALOG_H
#define DIALOG_H

#include <QDialog>

QT_BEGIN_NAMESPACE
class QLabel;
class QTcpServer;
class QTcpSocket;
QT_END_NAMESPACE

class Dialog : public QDialog {
   Q_OBJECT

public:
   Dialog(QWidget *parent = nullptr);

private slots:
   void acceptConnection();
   void readClientData();

private:
   QLabel *label;
   QTcpServer *tcpServer;
};

#endif

dialog.cpp

#include "dialog.h"

#include <QLabel>
#include <QTcpServer>
#include <QVBoxLayout>
#include <QTcpSocket>

Dialog::Dialog(QWidget *parent) : QDialog(parent) {
    label = new QLabel("Server is listening...");
    QVBoxLayout *mainLayout = new QVBoxLayout(this);
    mainLayout->addWidget(label);

    tcpServer = new QTcpServer;
    connect(tcpServer, &QTcpServer::newConnection, this, &Dialog::acceptConnection);
    tcpServer->listen(QHostAddress::LocalHost, 10055);
}


void Dialog::acceptConnection() {
    QTcpSocket *socket = tcpServer->nextPendingConnection();
    connect(socket, &QTcpSocket::readyRead,  this, &Dialog::readClientData);
}


void Dialog::readClientData() {
    QTcpSocket *socket = dynamic_cast<QTcpSocket *>(sender());
    if(socket){
        QString data;
        while (socket->canReadLine())
            data += socket->readLine();

        label->setText(data);

        socket->close();
    }
}

为什么服务器要停止监听第一个连接才能正常读取数据?

因为在关闭服务器之前,你必须处理所有的事件,比如获取数据,所以你得到了那个效果。

解释:

为了更好地理解操作,我将添加一些打印:

void Dialog::acceptConnection() {
    QTcpSocket *socket = tcpServer->nextPendingConnection();
    connect(socket, &QTcpSocket::readyRead,  this, &Dialog::readClientData);
    qDebug()<< __PRETTY_FUNCTION__<< socket;
}


void Dialog::readClientData() {
    QTcpSocket *socket = dynamic_cast<QTcpSocket *>(sender());
    if(socket){

        QString data;
        while (socket->canReadLine())
            data += socket->readLine();

        qDebug()<< __PRETTY_FUNCTION__<< socket<<data;

        label->setText(data);
         socket->close();
    }
}

输出:

void Dialog::acceptConnection() QTcpSocket(0x7fb1e4007600)
void Dialog::acceptConnection() QTcpSocket(0x559d7f3cb830)
void Dialog::readClientData() QTcpSocket(0x7fb1e4007600) "GET / HTTP/1.1\r\nHost: localhost:10055\r\nConnection: keep-alive\r\nCache-Control: max-age=0\r\nUpgrade-Insecure-Requests: 1\r\nUser-Agent: Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/67.0.3396.99 Safari/537.36\r\nDNT: 1\r\nAccept: text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8\r\nAccept-Encoding: gzip, deflate, br\r\nAccept-Language: en-US,en;q=0.9,hu;q=0.8\r\n\r\n"
void Dialog::readClientData() QTcpSocket(0x559d7f3cb830) "GET / HTTP/1.1\r\nHost: localhost:10055\r\nConnection: keep-alive\r\nCache-Control: max-age=0\r\nUpgrade-Insecure-Requests: 1\r\nUser-Agent: Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/67.0.3396.99 Safari/537.36\r\nDNT: 1\r\nAccept: text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8\r\nAccept-Encoding: gzip, deflate, br\r\nAccept-Language: en-US,en;q=0.9,hu;q=0.8\r\n\r\n"
void Dialog::acceptConnection() QTcpSocket(0x7fb1e40071e0)
void Dialog::readClientData() QTcpSocket(0x7fb1e40071e0) "GET / HTTP/1.1\r\nHost: localhost:10055\r\nConnection: keep-alive\r\nCache-Control: max-age=0\r\nUpgrade-Insecure-Requests: 1\r\nUser-Agent: Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/67.0.3396.99 Safari/537.36\r\nDNT: 1\r\nAccept: text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8\r\nAccept-Encoding: gzip, deflate, br\r\nAccept-Language: en-US,en;q=0.9,hu;q=0.8\r\n\r\n"

如果您注意到已经创建了 2 个套接字,为什么要创建 2 个套接字?因为浏览器会持续多次。

在您的第一个代码中,套接字变量将首先取值 (0x7fb1e4007600),然后第二个套接字将其设置为 (0x559d7f3cb830),稍后它将调用插槽 [=15] =] 与 (0x7fb1e4007600) 相关联,但您将读取 (0x559d7f3cb830) 的数据,即使该调用将丢失,也没有数据,然后第三个套接字将出现,并且它将对第二个套接字执行相同的操作.这就是他的方法失败的原因

另一方面,我的方法不会覆盖变量套接字,因为它不保存它,而是直接将其放入槽中

总而言之,如果具有调用槽的信号的对象有多个,在这种情况下,最好使用 sender() 获取发出信号的对象。

但其中一些(无论出于何种原因)将 return 来自 readClientData() 中的 sender() 的空指针?

如果它是一个槽可能不是,但它可能会发生,例如你可以直接调用 readClientData() 这样就不会有 sender() 或对任何其他对象的引用,所以为了安全我验证它。