How is QTcpServer really listening for connections

irpbc picture irpbc · Jul 26, 2012 · Viewed 10.5k times · Source

I am interested in how QTcpServer works behind the scenes regarding threads and blocking. QTcpServer has a listen() method which returns immediately. If listening started successfully the server will emit the signal newConnection(). I'm interested into how is the server listening (is it on the main thread) when the listen() method has returned. The usual example of a console application with a QTcpServer is something like this:

//main.cpp
int main(int argc, char* argv[])
{
    QCoreApplication app;
    MyServer server;
    app.exec();
}

//MyServer.cpp
MyServer::MyServer(QObject *parent) : QObject(parent)
{
    this->server = new QTcpServer(this);
    connect(server, SIGNAL(newConnection()), this, SLOT(on_newConnection()));
    if (!server->listen(QHostAddress::Any, 1234))
        //do something in case of error
}
void MyServer::on_newConnection()
{
    QTcpSocket* socket = server->nextPendingConnection();
    //do some communication...
}

Is QTcpServer dependent upon a QCoreApplication (or maybe a QRunLoop) existing and running to receive network events. Can it work properly without a QCoreApplication::exec() being called?

Answer

irpbc picture irpbc · Jul 27, 2012

I've been drilling through the source code of the QtCore and QtNetwork modules.

Aperantly, QTcpServer can work in two modes: synchronous and asynchronous.

In synchronous mode after calling listen() the caller can call waitForNewConnection() which is a blocking method (the thread will sleep until someone connects to the listening port). This way QTcpServer can work in a thread without an event loop.

In asynchronous mode QTcpServer will emit the newConnection() signal when a new connection was accepted. But to be able to do this there must be an event loop runing. Underlying the QCoreApplication are the QEventLoop and QAbstractEventDispatcher (an abstract class, concrete type is dependent on the OS, for example QEventDispatcherUNIX). This event dispatcher can monitor for conditions on sockets (represented by file descriptors). It has a method registerSocketNotifier(QSocketNotifier*). This method is called by the constructor of the QSocketNotifier class, which QTcpServer creates an instance of every time listen() is called. The only system call that is called when the QTcpServer::listen() is invoked is, of course, listen() which just returns immediately, all the real magic happens when the event loop starts running. The event loop (using the dispatcher) will monitor if there is a certain condition on sockets that have been registered. It calls the select() system call which receives one or more file descriptors to be monitored (by the kernel) for certain conditions (if there is data to be read, if data can be written, or if an error has occurred). The call can block the thread until the conditions on sockets are met, or it can return after some amount of time passes and the conditions on sockets were not met. I am not sure if Qt is calling select() with a waiting time supplied or without (to block indefinitely), I think it is determined in some complicated way and changeable. So when finally the condition on socket has been met, the event dispatcher will notify the QSocketNotifier for that socket, which will, in tern, notify the QTcpServer who is listening to the socket, which will accept the connection, and emit the newConnection() signal.


So the QTcpServer does not itself call into the event-loop/socket-monitoring system, but it is dependent upon it via the QSocketNotifier which it uses for asynchronous recieving of connections.

When synchronous method waitForNewConnection() is called it just bypasses all the QSocketNotifier stuff and calls accept() which blocks the thread until there is an incoming connection.