当子类化QTcpServer时,我如何延迟发出newConnection()信号?

When subclassing QTcpServer, how can I delay emitting the newConnection() signal?

本文关键字:延迟 信号 newConnection 何延迟 QTcpServer 子类      更新时间:2023-10-16

我想创建一个SSL服务器,所以我子类QTcpServer并覆盖incomingConnection(),在那里我创建一个QSslSocket,设置其描述符,并调用QSslSocket::startServerEncryption。此时,我需要等待QSslSocket::encrypted()信号发出,只有在此之后,我的服务器才应该发出newConnection()信号。客户端代码会认为它使用的是QTcpSocket,但实际上它使用的是一个安全套接字。

但是QTcpServer在调用incomingConnection()后总是发出 newConnection()(我查看了QTcpServer的源代码):

void QTcpServerPrivate::readNotification()
{
    // .........
        q->incomingConnection(descriptor);
        QPointer<QTcpServer> that = q;
        emit q->newConnection();
    // .........
}

所以我的问题是,是否有一种方法可以阻止QTcpServer发射newConnection(),直到我准备好自己发射它?

我想要这样做的原因是,我希望我的类能够被用作QTcpServer的插入式替代品,由代码不知道它正在使用它,所以它必须完全作为QTcpServer:

QTcpServer* getServer(bool ssl)
{
    return ssl ? new SslServer : new QTcpServer;
}

我的SslServer类的代码目前是这样的:

void SslServer::ready()
{
    QSslSocket *socket = (QSslSocket *) sender();
    addPendingConnection(socket);
    emit newConnection();
}
void SslServer::incomingConnection(int socketDescriptor)
{
    QSslSocket *serverSocket = new QSslSocket;
    if (serverSocket->setSocketDescriptor(socketDescriptor)) {
        connect(serverSocket, SIGNAL(encrypted()), this, SLOT(ready()));
        serverSocket->startServerEncryption();
    } else {
        delete serverSocket;
    }
}

这里有一个可以在这种情况下工作的想法:在您的QTcpServer子类中重新定义newConnection信号。

如果你这样做,连接到服务器实例的对象将不会接收到QTcpServer的"版本"信号,只接收到你直接从子类发出的信号。

这里有一个概念证明:A类是QTcpServer, foo是你试图"劫持"的信号,bar只是QTcpServer的另一个(假设的)信号,你不需要触摸。

class A: public QObject
{
    Q_OBJECT
    public:
        A() {};
        virtual void doit() {
            qDebug() << "A::doit";
            emit foo(1);
            emit bar(1);
        }
    signals:
        void foo(int);
        void bar(int);
};

B是你的子类。注意,它重新定义了信号foo,但没有对bar做任何操作。

class B: public A
{
    Q_OBJECT
    public:
        B() {};
        virtual void doit() {
            qDebug() << "B::doit";
            emit foo(2);
            emit bar(2);
        }
    signals:
        void foo(int);
};

C是一个潜在的客户端,连接B实例的信号/插槽,就像A实例一样。

class C: public QObject
{
    Q_OBJECT
    public:
        C() {
            B *b = new B;
            connect(b, SIGNAL(foo(int)), this, SLOT(foo(int)));
            connect(b, SIGNAL(bar(int)), this, SLOT(bar(int)));
            /* 1 */
            b->doit();
            /* 2 */
            b->A::doit(); // call parent class's function
        };
    public slots:
        void foo(int i) {
            qDebug() << "foo: " << i;
        }
        void bar(int i) {
            qDebug() << "bar: " << i;
        }
};

下面是构造C的输出:

B::doit       // this is /* 1 */
foo:  2 
bar:  2 
A::doit       // this is /* 2 */
bar:  1 

…没有别的了。Aemit foo(1)没有连接到Cfoo插槽,它永远不会到达CAemit bar(1)工作正常,信号未动

有了这样的设置,当你的类准备好时,你可以发出newConnection, QTcpServer版本的信号将不会被你的用户对象接收。

要真正减少替换,您可能需要编辑Qt的实际源代码,因为您通常无法重新实现任何Private类调用。

如果你是唯一一个使用替换的,并且你控制连接到newConnection信号的类…

只需将newConnection连接到您自己的插槽handleNewConnection。当安全连接准备好时,发出myNewConnection并将其连接到将连接到newConnection的元素。

编辑:经过一番挖掘,我找到了一个重新连接信号的选项:

http://qt-project.org/forums/viewthread/6820

基本上,你重新实现QObject::connect,然后你跟踪连接并以你需要的方式处理它们。因此,在这种情况下,您将保留newConnection信号的所有连接的列表,并将其保存在列表中,以便在断开它时可以重新连接它。请确保在重新实现结束时调用QObject::connect

当走这条路时,另一个选择是去那里重新路由连接。当从newConnection请求连接时,将其移到myNewConnection

希望对你有帮助。

一个肮脏的黑客将非常短暂地阻止来自QTcpServer的信号。因为您知道newConnection()将在您从SslServer::incomingConnection()返回之后立即发出,所以在您返回之前调用this->blockSignals(true);。这将阻止newConnection()调用它所连接的任何插槽。

为了确保您接收到后续信号,请尽快解除阻塞信号。我想最早可用的时间将是正确的,当控制回到事件循环,所以QTimer::singleShot可以做到这一点。

void SslServer::incomingConnection(int socketDescriptor)
{
    QSslSocket *serverSocket = new QSslSocket;
    if (serverSocket->setSocketDescriptor(socketDescriptor)) {
        connect(serverSocket, SIGNAL(encrypted()), this, SLOT(ready()));
        serverSocket->startServerEncryption();
    } else {
        delete serverSocket;
    }
    this -> blockSignals(true);
    QTimer::singleShot(0, this, SLOT(unblockSignals());
}
void SslServer::unblockSignals()
{
    this->blockSignals(false);
}

这样做的缺点是,您将失去在incomingConnection()unblockSignals()之间可以合法发射的所有信号。就像我说的,这是一个肮脏的黑客。