Temos usado o excelente cpp-httlib para fazer solicitações REST. Agora gostaríamos de usá-lo para um servidor. Bem no topo da documentação está:
Importante
Esta biblioteca usa E/S de soquete 'bloqueantes'. Se você está procurando uma biblioteca com E/S de soquete 'não bloqueadoras', esta não é a que você quer.
Não tenho certeza do que isso significa em um sentido prático, pois, embora eu entenda os princípios básicos de IP/UDP/TCP etc., não estou familiarizado com programação de socket. Testei o servidor disparando várias solicitações para ele, por exemplo, se a primeira solicitação for implementada dormindo por 30s, uma segunda solicitação "hello world" ainda retorna imediatamente.
Depurando o código, vejo que o thread principal é executado:
while (svr_sock_ != INVALID_SOCKET) {
// socket operations
if (!task_queue->enqueue(
[this, sock]() { process_and_close_socket(sock); })) {
detail::shutdown_socket(sock);
detail::close_socket(sock);
}
}
Os threads de trabalho são executados
for (;;) {
std::function<void()> fn;
{
// Lock / wait for new request
fn = pool_.jobs_.front();
pool_.jobs_.pop_front();
}
fn();
}
ou seja, o thread principal é como um loop de eventos em um aplicativo GUI, recebendo cada solicitação recebida, mas em vez de executar a solicitação em si, ele a coloca em uma fila para que os threads de trabalho a peguem.
Anteriormente eu estava encerrando chamadas para C++ em um servidor web Python FastAPI, o que é bom, mas executar Python e C++ claramente adiciona sobrecarga/complexidade, e não é totalmente simples entender como as solicitações são executadas em relação ao bloqueio global do interpretador. A abordagem direta do C++ usando cpp-httlib parece muito melhor, mas o comentário sobre 'bloquear' E/S de socket me preocupa. O que estou perdendo ao não usar E/S de socket não bloqueantes?
(O servidor será construído para Windows e Linux; no entanto, a produção será Linux.)
Uma biblioteca de IO de bloqueio usa threads para fazer multitarefa preemptiva , enquanto uma não bloqueante faz multitarefa cooperativa . Veja também Qual é a diferença entre multitarefa cooperativa e multitarefa preemptiva?
com multitarefa preventiva (cpp-httplib), se você quiser que 1000 soquetes sejam manipulados de uma vez, precisará de 1000 threads ativas, e a maior parte do tempo da CPU será desperdiçado no agendamento do sistema operacional em vez de fazer o trabalho real; uma troca de contexto pode levar alguns microssegundos; pode levar até mais tempo em um sistema operacional virtualizado, que é usado por provedores de nuvem.
com multitarefa cooperativa ( asio ), se você tem um servidor com 4 núcleos, você só precisa de 4 threads para gerenciar os 1000 soquetes, o sistema operacional não terá que fazer nenhum agendamento excessivo, e seu aplicativo lidará com esse agendamento extra, mas no ambiente do usuário com menos chamadas de sistema, o que acaba sendo mais rápido. (porque os sistemas operacionais têm agendadores complicados, enquanto os agendadores no ambiente do usuário são simples e, portanto, rápidos).
se você estiver fazendo cálculos pesados por tarefa ou gerenciando um número muito limitado de soquetes ao mesmo tempo e não estiver limitado por E/S, então usar multitarefa preventiva (cpp-httplib) é aceitável, mas se você estiver limitado por E/S (como um servidor HTML ou de arquivos), então você deve usar multitarefa cooperativa (asio).
A multitarefa cooperativa (asio) pode ser mais difícil de usar, mas é mais rápida, enquanto a multitarefa preventiva (cpp-httplib) pode ser mais lenta, mas é mais fácil de usar.
para comparação, o FastApi usa multitarefa cooperativa para
async
caminhos e multitarefa preventiva para caminhos não assíncronos.