Confiabilidade de reconhecimento usando UDP

16

Eu tenho uma pergunta sobre o UDP. Por contexto, estou trabalhando em um jogo de ação em tempo real.

Eu li bastante sobre as diferenças entre UDP e TCP e sinto que as entendo muito bem, mas há uma que nunca pareceu correta, e que é a confiabilidade e os reconhecimentos específicos . Entendo que o UDP não oferece confiabilidade por padrão (ou seja, os pacotes podem ser descartados ou chegar fora de ordem). Quando alguma confiabilidade é necessária, a solução que eu vi (que faz sentido conceitualmente) é usar confirmações (ou seja, o servidor envia um pacote ao cliente e, quando o cliente recebe essa mensagem, envia de volta uma confirmação ao servidor) .

O que acontece quando o reconhecimento é descartado?

No exemplo acima (um servidor enviando um pacote para um cliente), o servidor lida com a perda potencial de pacotes reenviando pacotes a cada quadro até que sejam recebidos reconhecimentos por esses pacotes. Você ainda pode encontrar problemas de largura de banda ou mensagens fora de ordem, mas, do ponto de vista da perda de pacotes, o servidor é coberto.

No entanto, se o cliente enviar uma confirmação que nunca chega, o servidor não terá outra opção a não ser parar de enviar a mensagem, o que poderia interromper o jogo se as informações contidas nesse pacote fossem necessárias. Você pode adotar uma abordagem semelhante ao servidor (ou seja, continuar enviando agradecimentos até receber uma confirmação pela confirmação?), Mas essa abordagem o faria alternar para sempre (já que você precisaria de uma confirmação para a confirmação para a confirmação) e assim por diante).

Sinto que minha lógica básica está correta aqui, o que me deixa com duas opções.

  1. Envie um único pacote de reconhecimento e espere o melhor.
  2. Envie um punhado de pacotes de reconhecimento (talvez 3-4) e espere o melhor, assumindo que nem todos serão descartados.

Existe uma resposta para esse problema? Estou fundamentalmente interpretando algo errado? Existe alguma garantia de usar UDP que eu não conheço? Sinto-me hesitante em seguir em frente com muito código de rede até me sentir confortável ao saber que minha lógica está correta.

Grimelios
fonte
11
Talvez você esteja perdendo uma idéia de "tempos limite" e "novas tentativas".
Kromster diz apoio Monica
Eu posso ter certeza. Você está sugerindo que minha lógica está correta e, para não parecer muito negativa, mas enquanto estiver programando em rede, não posso garantir nenhuma virtualmente nenhuma informação em rede? Durante um jogo em tempo real, há uma tonelada de informações potencialmente descartadas, o que é bom, mas eu só quero ter certeza de que entendi o problema.
Grimelios
10
Sem garantias. Certo. Nunca inclua "esperança" em seus algoritmos. Eles devem lidar com QUALQUER combinação infeliz. PS Simplesmente mudamos para o TCP em nosso RTS, onde tudo é resolvido, pois precisamos de uma comunicação confiável (para simulação de passo em falso).
Kromster diz apoio Monica
5
Use o TCP quando a confiabilidade for necessária, use o UDP quando isso não importa. Por exemplo, as coordenadas do jogador são enviadas no meu jogo via UDP. Eu uso interpolação e suavização para suavizar os pacotes ausentes. Funciona como um encanto. coisas que realmente precisam ser confiáveis, mas que podem ser um pouco mais lentas, são enviadas via TCP. Se você tem um estado em que um estado mais novo invalida o estado antigo, o UDP é uma boa opção, pois não importa quando algo entre eles foi descartado. posição do jogador).
precisa
Esta não é uma resposta direta à sua pergunta, mas eu recomendo fortemente apenas exigir um reconhecimento em um jogo em tempo real quando eles forem absolutamente necessários (por exemplo, na conexão inicial). É muito mais simples (e robusto) projetar o cliente e o servidor para que "trabalhem com o que têm" até obterem um novo pacote em um sistema sem estado, se possível. O Quake 3 fez isso incrivelmente bem com um sistema baseado em instantâneos . Também bibliotecas como Enet pode enviar apenas alguns pacotes de maneira confiável, para os casos quando você realmente precisa dele
JRH

Respostas:

32

Essa é uma forma do problema dos dois generais e você está certo - não há número de tentativas suficientes para garantir perfeitamente o recebimento.

Na prática nos jogos, geralmente há um horizonte de tempo além do qual as informações realmente não importam, mesmo que elas cheguem tecnicamente de maneira confiável. Como descobrir que você tinha um tiro na cabeça perfeito alinhado 2 segundos atrás - é tarde demais para o jogador usar essa informação agora.

Se sua perda de pacotes é tão alta que você não consegue obter as informações necessárias rotineiramente dentro de uma janela de reação apertada, então para um jogo em tempo real, é melhor chutar o jogador e tentar encontrar uma melhor correspondência para ele em outro lugar, em vez de continue tentando enviar o pacote para emular uma conexão confiável.

Por esse motivo, alguns sistemas de replicação de jogos ignoram completamente o reconhecimento e as novas tentativas e optam por enviar apenas spam a atualização mais recente sempre que possível. Se alguém cair ou chegar atrasado, muito ruim, pule, pegue o próximo e continue, contando com os sistemas de previsão e interpolação para suavizar a lacuna e minimizar os soluços visíveis para o jogador.

De repente, quero começar a chamar isso de "Replicação Simba" por desconsiderar problemas do passado e tentar viver no momento presente. ;)

Rafiki estabelecendo alguma reductio ad absurdum nessa filosofia de vida

Uma solução híbrida é seguir em frente enviando a nova atualização AND (já que as atualizações do estado do jogo geralmente podem ser bem pequenas / compactáveis ) também incluem a última atualização e talvez a anterior antes ... Portanto, caso o cliente as perca , você não precisa esperar um tempo completo de ida e volta para descobrir e consertá-lo. Na maioria das vezes o cliente já viu isso, então há dados redundantes dessa maneira, mas a latência para corrigir uma mensagem perdida é menor. As atualizações do cliente podem incluir o número de índice da atualização consecutiva mais recente que eles viram, para que você possa ser minimamente conservador com quantas atualizações antigas você incluirá no próximo pacote de atualização.

Você também pode implementar um sistema de duas camadas como outro tipo de híbrido, em que o estado de curta duração é replicado de maneira não confiável e o estado de longo prazo é sincronizado de maneira confiável, usando TCP ou sua própria implementação de confiabilidade com uma nova tentativa contagem. Isso fica mais complexo de gerenciar, porque você tem dois sistemas de mensagens para manter, e os dois instantâneos podem estar fora de sincronia, adicionando uma nova classe de casos extremos.

DMGregory
fonte
11
+1, bem escrito. Gostaria apenas de destacar que isso é mais relevante para jogos de ação / em tempo real. Os jogos TBS e RTS (e alguns eventos de jogos de ação) têm uma visão diferente do "horizonte temporal além do qual as informações realmente não importam".
Kromster diz apoio Monica
3
Sim, para um jogo baseado em turnos, eu imagino que alguém usaria o TCP em vez de tentar rolar a própria camada de confiabilidade sobre o UDP. ;) Eu ainda classificaria o micro em um RTS como o tipo de jogabilidade com um horizonte de tempo exato - essa abordagem híbrida pode se dar bem lá, onde você tem atualizações de baixa latência para o calor do momento e um rede de segurança para lidar retroativamente com eventos críticos perdidos, como gastos com recursos.
DMGregory
Isso é extremamente útil e meio que valida minha preocupação inicial. Muito obrigado.
Grimelios
2
Também pode ser útil mencionar a correção de erro de encaminhamento. Projete seu protocolo para que o receptor possa descobrir independentemente que um pacote foi descartado quando o próximo pacote for recebido, adicionando alguns dados extras para suavizar a interpolação necessária. Isso pode ser útil porque geralmente os pacotes UDP não estão cheios de qualquer maneira e você envia pacotes menores com mais frequência para manter a latência baixa. Adicionar alguns bytes extras não prejudicará a latência e a largura de banda não é um problema nesses casos.
MSalters
@MSalters Eu diria que vale a pena elaborar em sua própria resposta, se você estiver disposto. Eu votaria nisso. :)
DMGregory
9

A abordagem utilizada pelo TCP é que o remetente continuará reenviando o pacote até receber uma confirmação. O destinatário ignorará pacotes duplicados, mas ainda enviará agradecimentos por eles. O remetente ignorará as confirmações duplicadas.

Se um pacote for perdido, o remetente o reenvia, como você já sabe.
Se uma confirmação for perdida, o remetente reenvia o pacote original, o que faz com que o destinatário reenvie a confirmação.

Se um reconhecimento não for recebido dentro de um determinado período (talvez 60 segundos ou 20 tentativas), o jogador será considerado desconectado do jogo. Você deve implementar algum tipo de regra de tempo limite, caso contrário, um jogador que desconecte o cabo de rede amarrará os recursos no servidor para sempre.

user253751
fonte
Um recurso essencial do TCP é que o remetente não precisa se preocupar se algum pacote específico foi reconhecido, mas principalmente se preocupa com a "marca d'água alta" e com quanto tempo os pacotes ficam pendentes sem a marca d'água alta se mover.
Supercat
11
@ supercat Eu não diria que é essencial; mais como uma otimização.
user253751
Em relação à coisa entre parênteses (enviando ACK para pacotes que você já recebeu), acho que você deve enfatizá-la, em vez de colocar entre parênteses. Parece estar ausente do entendimento do OP (ou pelo menos de sua descrição).
Reintegrar Monica
@Angew feito agora.
user253751
6

Se você deseja reinventar o TCP, faz sentido olhar primeiro para o TCP , que lida com o problema exato que você descreve (parte da solução é usar valores definidos pelo usuário para novas tentativas e tempos limite).

Soluções que usam 2 canais, um canal TCP (para comunicação confiável) e um canal UDP (para comunicação de baixa latência) não são incomuns.

Algumas soluções detectam quando um cliente está perdendo algumas informações por muito tempo e iniciam uma ressincronização, que pode usar UDP ou TCP.

Outra abordagem comum é projetar a comunicação de forma que ela não dependa de reconhecimentos, mas isso está fora do escopo da questão.

Peter - Unban Robert Harvey
fonte
3

Em um RTS, você realmente não pode usar um protocolo como o TCP e também não pode tornar o UDP confiável. Se você tentar, o jogo irá congelar sempre que houver um problema na rede.

Em vez disso, você cria o protocolo para que os pacotes perdidos não importem muito.

A versão curta é que você não se importa onde os outros jogadores estiveram no último quadro, desde que saiba onde eles estão agora . A versão longa é mais complicada.

A pergunta então é: o que você faz quando um pacote desaparece? E a resposta é ... você adivinha. O jogador provavelmente está se movendo em linha reta, certo? Basta movê-los um passo adiante nessa linha. ... Exceto que nenhum jogador RTS se mova em linha reta. E então há detecção de colisão.

Isto é difícil. Muitos jogos entendem errado. Pode-se argumentar que não há resposta certa para isso, apenas vários erros que podem ser trocados.

A razão pela qual esses jogos funcionam muito bem não é apenas porque eles pensaram muito sobre esses problemas, mas também que a Internet se tornou bastante confiável. Quase todos os pacotes UDP chegam ao seu destino em tempo hábil. (A menos que haja um problema permanente como um firewall)

Stig Hemmer
fonte
Warcraft 3 usa TCP.
fsp