Ok, primeiro de tudo, se você tem algo e está funcionando, geralmente é uma boa ideia deixar as coisas assim. Por que consertar o que não está quebrado?
Mas se você está tendo problemas e realmente gostaria de reescrever seu código de rede, acho que você tem quatro opções principais:
- Código de bloqueio multithread (o que você está fazendo agora)
- Soquetes sem bloqueio com notificação acionada por nível
- Soquetes sem bloqueio com notificação de alteração de prontidão
- Soquetes assíncronos
Tendo escrito muitos clientes e servidores multiplayer (de ponto a ponto para multiplayer em massa), gosto de pensar que a opção 2 leva à menor complexidade, com desempenho muito bom, tanto para as partes do servidor quanto para o cliente. Como um segundo próximo, eu iria com a opção 4, mas isso geralmente exige que você repense todo o seu programa, e acho isso útil principalmente para servidores e não para clientes.
Em particular, eu gostaria de aconselhar contra o bloqueio de soquetes em um ambiente multithread, pois isso geralmente leva ao bloqueio e a outros recursos de sincronização que não apenas aumentam bastante a complexidade do código, mas também podem prejudicar seu desempenho, pois alguns threads aguardam outras.
Mas, acima de tudo, a maioria das implementações de soquete (e a maioria das implementações de E / S) não bloqueiam no nível mais baixo. As operações de bloqueio são simplesmente fornecidas para simplificar o desenvolvimento de programas triviais. Quando um soquete está bloqueando, a CPU nesse encadeamento fica completamente ociosa; então, por que criar uma abstração sem bloqueio sobre a abstração de bloqueio sobre uma tarefa já sem bloqueio?
A programação de soquetes sem bloqueio é um pouco assustadora se você ainda não o experimentou, mas é bem simples e, se você já está fazendo uma pesquisa de entrada, já tem a mentalidade de fazer soquetes sem bloqueio.
A primeira coisa que você deseja fazer é definir o soquete para não-bloqueio. Você faz isso com fcntl()
.
Depois disso, antes de o fazer send()
, recv()
, sendto()
, recvfrom()
, accept()
( connect()
é um pouco diferente) ou outras chamadas que podem bloquear o thread, você chama select()
no soquete. select()
informa se uma operação de leitura ou gravação subseqüente pode ou não ser executada no soquete sem bloquear. Se for esse o caso, você pode executar com segurança a operação desejada e o soquete não bloqueará.
Incluir isso em um jogo é bastante simples. Se você já possui um loop de jogo, por exemplo, assim:
while game_is_running do
poll_input()
update_world()
do_sounds()
draw_world()
end
você pode alterá-lo para ficar assim:
while game_is_running do
poll_input()
read_network()
update_world()
do_sounds()
write_network()
draw_world()
end
Onde
function read_network()
while select(socket, READ) do
game.net_input.enqueue(recv(socket))
end
end
e
function write_network()
while not game.net_output.empty and select(socket, WRITE) do
send(socket, game.net_output.dequeue())
end
end
Em termos de recursos, o único livro que acho que todo mundo deve ter em suas estantes, mesmo que seja o único livro que têm, é " Unix Network Programming, Vol. 1 ", do falecido Richard Stevens. Não importa se você executa o Windows ou outro SO ou programação de soquete de idioma. Não pense que entende soquetes até ler este livro.
Outro recurso onde você pode encontrar uma visão geral das soluções disponíveis em termos de programação de múltiplos soquetes (principalmente relevantes para a programação de servidores) é esta página .