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

时间:2018-07-25 13:51:48

标签: qt qt5 qtcpsocket qtcpserver

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

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标头),也没有在标签中显示该数据。

仅当我将tcpServer->close();放在acceptConnection()插槽的末尾(而不是放在readClientData()中)时,数据才能正常读取(标签显示标题:{{1} }等。

我一点都不明白:为什么服务器应该停止监听第一个连接以正常读取数据?

1 个答案:

答案 0 :(得分:0)

在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),一段时间后它将调用插槽readClientData()(0x7fb1e4007600)关联的数据,但是即使该调用将丢失,您仍将读取(0x559d7f3cb830)的数据,即使该数据将丢失,然后第三个套接字也会出现,并且对第二个套接字也将执行相同的操作。这就是为什么他的方法失败了

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

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

,但是其中某些(无论出于何种原因)会从readClientData()中的sender()返回空指针?

如果它可能不是插槽,但是可能会发生,例如,您可以直接调用readClientData(),这样就不会有sender()或对任何其他对象的引用,因此出于安全性考虑,我对此进行了验证。