Search code examples
c++qtsslqtcpserverqt-signals

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


I want to create an SSL server, so I subclass QTcpServer and I override incomingConnection(), where I create a QSslSocket, set its descriptor, and call QSslSocket::startServerEncryption. At this point I need to wait for QSslSocket::encrypted() signal to be emitted, and only after that should my server emit the newConnection() signal. The client code would then think it's using a QTcpSocket, but will in fact be using a secure socket.

But QTcpServer always emits newConnection() after calling incomingConnection() (I looked in the source of QTcpServer):

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

So my question is, is there a way I can prevent QTcpServer from emitting newConnection(), until I'm ready to emit it myself?

The reason I want this is that I want my class to be able to be used as a drop-in replacement of QTcpServer, by code that is unaware it's using it, so it must behave exactly as a QTcpServer:

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

My code for the SslServer class is currently this:

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;
    }
}

Solution

  • Here's an idea that could work in this case: redefine the newConnection signal in your QTcpServer subclass.

    If you do that, objects that connected with an instance of your server won't receive QTcpServer's "version" of the signal, only the one you emit directly from your sub-class.

    Here's a proof of concept: class A is the QTcpServer, foo is the signal you're trying to "hijack", bar is just another (hypothetical) of QTcpServer's signals you don't need to touch.

    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);
    };
    

    Class B is your subclass. Notice that it redefines signal foo, but doesn't do anything to 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);
    };
    

    Class C is a potential client, connects the signals/slots from a B instance exactly like it would for an A instance.

    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;
            }
    };
    

    Here's the output from constructing a C:

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

    ... and nothing else. A's emit foo(1) isn't connected to C's foo slot, it will never arrive to C. A's emit bar(1) worked as expected, that signal is untouched.

    With that setup, you can emit newConnection when your class is ready, QTcpServer's version of the signal will not be received by your user's objects.