【问题标题】:When subclassing QTcpServer, how can I delay emitting the newConnection() signal?子类化 QTcpServer 时,如何延迟发出 newConnection() 信号?
【发布时间】:2012-12-18 02:23:51
【问题描述】:

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

但是QTcpServer 总是在调用incomingConnection() 之后发出 newConnection()(我查看了the source of 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;
    }
}

【问题讨论】:

    标签: c++ qt ssl qtcpserver qt-signals


    【解决方案1】:

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

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

    这是一个概念证明:A 类是 QTcpServerfoo 是您试图“劫持”的信号,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) 按预期工作,该信号未受影响。

    通过该设置,您可以在您的课程准备就绪时发出newConnectionQTcpServer 的信号版本将不会被您的用户对象接收。

    【讨论】:

      【解决方案2】:

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

      如果您是唯一使用替换的人,并且您控制连接到newConnection 信号的类...

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

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

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

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

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

      希望对您有所帮助。

      【讨论】:

      • 断开信号然后重新连接呢?可能吗?不,绝对不会重写 Qt 源代码 :)
      • 你可以试试:QObject::disconnect。我还没有做得足够好。
      • 问题是我需要稍后重新连接信号,所以我需要以某种方式获取该信号的接收器列表。我将此作为一个新问题发布 - stackoverflow.com/q/14144415/492336
      【解决方案3】:

      一个肮脏的黑客将非常短暂地阻止来自 QTcpServer 的信号。因为你知道newConnection() 会在你从SslServer::incomingConnection() 返回后立即发出,所以在你返回之前调用this-&gt;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() 之间可以合法发出的所有信号。就像我说的,这是一个肮脏的黑客攻击。

      【讨论】:

        猜你喜欢
        • 2016-09-18
        • 2020-10-12
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        相关资源
        最近更新 更多