Como o QTcpServer está realmente ouvindo conexões?
-
11-12-2019 - |
Pergunta
Estou interessado em saber como o QTcpServer funciona nos bastidores em relação a threads e bloqueios. QTcpServer
tem um listen()
método que retorna imediatamente.Se a escuta for iniciada com sucesso, o servidor emitirá o sinal newConnection()
.Estou interessado em saber como o servidor está escutando (está no thread principal) quando o listen()
método retornou.O exemplo usual de um aplicativo de console com QTcpServer é mais ou menos assim:
//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...
}
É QTcpServer
dependente de um QCoreApplication
(ou talvez um QRunLoop
) existente e em execução para receber eventos de rede.Pode funcionar corretamente sem um QCoreApplication::exec()
sendo chamado?
Solução
Estive analisando o código-fonte do QtCore
e QtNetwork
módulos.
Aparentemente, QTcpServer
pode funcionar em dois modos: síncrono e assíncrono.
Em síncrono modo depois de ligar listen()
o chamador pode ligar waitForNewConnection()
que é um método de bloqueio (o thread ficará suspenso até que alguém se conecte à porta de escuta).Por aqui QTcpServer
pode funcionar em um thread sem um loop de eventos.
Em assíncrono modo QTcpServer
emitirá o newConnection()
sinal quando uma nova conexão foi aceita.Mas para poder fazer isso, deve haver um loop de eventos em execução.Subjacente ao QCoreApplication
são as QEventLoop
e QAbstractEventDispatcher
(uma classe abstrata, tipo concreto depende do sistema operacional, por exemplo QEventDispatcherUNIX
).Este despachante de eventos pode monitorar condições em soquetes (representados por descritores de arquivo).Tem um método registerSocketNotifier(QSocketNotifier*)
.Este método é chamado pelo construtor do QSocketNotifier
aula, que QTcpServer
cria uma instância de cada vez listen()
é chamado.A única chamada de sistema que é chamada quando o QTcpServer::listen()
é invocado é, claro, listen()
que retorna imediatamente, toda a verdadeira mágica acontece quando o loop de eventos começa a ser executado.O loop de eventos (usando o despachante) monitorará se há uma determinada condição nos soquetes que foram registrados.Ele chama o select()
chamada de sistema que recebe um ou mais descritores de arquivo a serem monitorados (pelo kernel) para determinadas condições (se há dados para serem lidos, se os dados podem ser gravados ou se ocorreu um erro).A chamada pode bloquear o thread até que as condições nos soquetes sejam atendidas ou pode retornar após algum tempo e as condições nos soquetes não forem atendidas.Não tenho certeza se o Qt está ligando select()
com tempo de espera fornecido ou sem (para bloquear indefinidamente), acho que é determinado de alguma forma complicada e mutável.Então, quando finalmente a condição no soquete for atendida, o despachante do evento notificará o QSocketNotifier
para esse soquete, que irá, em tern, notificar o QTcpServer
quem está ouvindo o soquete, que aceitará a conexão e emitirá o newConnection()
sinal.
Então o QTcpServer
não chama o sistema de loop de eventos/monitoramento de soquete, mas depende dele por meio do QSocketNotifier
que ele usa para recebimento assíncrono de conexões.
Quando método síncrono waitForNewConnection()
é chamado, apenas ignora todos os QSocketNotifier
coisas e chamadas accept()
que bloqueia o thread até que haja uma conexão de entrada.
Outras dicas
A maior parte da funcionalidade "nos bastidores" do Qt acontece dentro do loop de eventos principal do QCoreApplication:sinais/slots, temporizadores, etc.
Um exemplo seria JavaScript - você vincula um evento, mas o loop de eventos é processado pelo navegador.