Ajuda para troca de mensagens cliente-servidor de rede e sincronização de relógio

10

estou fazendo um jogo de física rápido que é um hóquei na mesa. Com dois marretas e um disco. O jogo roda em iphone / ipad e eu estou fazendo a parte multiplayer através do GameCenter.

É assim que o sistema de rede funciona. O cliente que iniciar a partida será estabelecido como servidor e quem aceitar a solicitação de partida será o cliente.

O 'servidor' tem a física em execução e a resposta é imediata e o cliente também tem a física em execução, para que pareça suave entre a troca de mensagens. O que faço como servidor é que eu envio ao cliente minha velocidade de disco e minha posição e o cliente ajusta sua velocidade / posição de disco relacionada ao servidor para mantê-lo sincronizado. Caso contrário, a física dessincroniza e estraga tudo.

Quando a latência da rede é boa, abaixo de 100ms, os resultados são muito bons, eu tenho um jogo fácil de jogar no lado do cliente e o comportamento estranho é mínimo. O problema ocorre quando o atraso é de cerca de 150 a 200ms. Nesse caso, acontece que o meu disco do cliente já atingiu uma direção invertida, mas recebe uma mensagem de atraso do servidor e recua um pouco, causando uma sensação estranha no comportamento da bola.

Eu li algumas coisas sobre isso:

Exemplo de Rede Pong

Clique em exemplo de sincronização

Wikipedia sobre sincronização de relógio

Então, como posso resolver isso? Tanto quanto li a melhor opção que tenho, é fazer uma sincronização de relógio no servidor / cliente com um carimbo de data e hora, para que, quando recebo mensagens de atraso relacionadas ao meu relógio, eu simplesmente as ignore e permita que a simulação dos clientes faça o seguinte. trabalho. Vocês concordam com isso? E como estou enviando dados não confiáveis ​​(UDP), posso receber mensagens atrasadas ou fora de ordem.

Se essa é a melhor abordagem, como faço para implementar a sincronização do relógio. Eu li as etapas de como fazê-lo, mas não o entendi direito.

Isso diz que:

  1. O cliente carimba a hora local atual em um pacote de "solicitação de hora" e envia para o servidor.
  2. Após o recebimento pelo servidor, o servidor marca o horário do servidor e retorna
  3. Após o recebimento pelo cliente, o cliente subtrai o horário atual do horário enviado e divide por dois para calcular a latência. Ele subtrai a hora atual da hora do servidor para determinar o delta do horário cliente-servidor e adiciona a meia latência para obter o delta do relógio correto. (Até agora, este algothim é muito semelhante ao SNTP)
  4. O cliente repete as etapas 1 a 3 cinco ou mais vezes, pausando alguns segundos a cada vez. Outro tráfego pode ser permitido nesse ínterim, mas deve ser minimizado para obter melhores resultados. Os resultados dos recebimentos de pacotes são acumulados e classificados na ordem de menor latência para maior latência. A latência mediana é determinada escolhendo a amostra do ponto médio dessa lista ordenada.
  5. Todas as amostras acima de aproximadamente 1 desvio padrão da mediana são descartadas e as demais amostras são calculadas usando uma média aritmética.

Seguindo este exemplo, eu teria isso:

Vamos fingir que o jogo foi carregado e o tempo do meu cliente é 0 agora, então eu envio ao servidor que o meu tempo é 0.

As mensagens levam 150ms para chegar ao servidor, mas o relógio do servidor já foi iniciado e fica 1 segundo à frente do cliente. Quando o servidor receber a mensagem, o horário será: 1.15 e envia esse horário para o cliente, estamos bem? Vamos fingir que nosso atraso é constante a 150ms.

Agora, o cliente recebe o horário 1,15 e subtrai o horário atual do horário enviado e divide por dois para calcular a latência. Qual é: 0,3 - 0 = 0,3 / 2 -> 150ms.

Ele subtrai a hora atual da hora do servidor para determinar o delta do horário cliente-servidor e adiciona a meia latência para obter o delta do relógio correto:
Hora do cliente: 0,3 Hora do servidor 1,15
0,3 - 1,15 = 0,85 + latência (0,15) = 1

Como isso é sincronizado? O que estou perdendo?

É a minha primeira vez em multiplayer e experiência em rede, então estou um pouco confuso.

Obrigado.

gmemario
fonte
Boa pergunta. Você poderia corrigir: 0,3 - 1,15 para ser 1,15 - 0,3?
Ciaran

Respostas:

12

O algoritmo publicado estava correto, mas no seu exemplo você está esquecendo o tempo que leva para o pacote do servidor chegar ao cliente, portanto:

Server time: 1
Client time: 0
Client sends 0 to server

... 150ms to get to server  (ping is 300! not 150ms in this case. Ping is round-trip)

Server time: 1.15
Client time: 0.15
Server receives packet and sends client 1.15

... 150ms to get back to client

Server time: 1.30
Client time: 0.30
Client receives 1.15 from server

Agora, como você pode ver, se o cliente alterasse o relógio para 1,15, seria 0,15 atrás do servidor, é por isso que você precisa ajustar o Ping (também conhecido como Round Trip Time [RTT]). Aqui está o cálculo do tempo delta completo realizado em várias etapas:

Server Time - Current Time + Ping / 2
= Server Time - Current Time + (Current Time - First Packet Time) / 2
= 1.15 (Perceived, not actual!) - 0.30 + (0.30 - 0.00) / 2
= 1.00

Isso nos dá o tempo delta correto de 1,00 segundos

John McDonald
fonte
Estou recebendo, então meu relógio do cliente é 1,00 e o servidor é 1,30. Depois que eu receber uma mensagem do servidor, preciso adicionar o ping para verificar se há uma mensagem atrasada ou algo assim? Outra coisa, e se a latência mudar, eu tenho que continuar fazendo esses cálculos o tempo todo?
Guemario #
O tempo delta de 1,00s deve ser adicionado ao relógio atual para tornar o tempo do cliente igual ao tempo do servidor. A latência está sempre mudando, cada pacote terá um RTT ligeiramente diferente. Em um curto período de tempo, como em um jogo, eu simplesmente sincronizaria esse horário apenas uma vez, o cliente terá um bom palpite sobre qual é o tempo do servidor e os dois relógios deverão avançar aproximadamente no mesmo ritmo. A única exceção seria se você recebesse um pacote que parece ser do futuro. Nesse caso, existem 2 soluções: 1) Faça uma nova sincronização de tempo. 2) Avance seu relógio para combinar com o futuro
John McDonald
Ok, vamos analisar esta situação apenas para garantir que eu entendi: Hora do servidor: 1s Hora do cliente: 0s 150ms para chegar ao servidor Hora do servidor: 1.15s Hora do cliente: 0.15s O servidor envia o cliente 1.15s 200ms para chegar ao cliente Então, meu ping agora é de 350ms. Isso está correto? Tempo do servidor: 1,35 Tempo do cliente: 0,35 Execução dos cálculos: 1,15 - 0,35 + (0,35 - 0,00) / 2 Agora meu deltaTime = 0,975 Se eu adicionar o tempo delta 0,975 ao meu 0,35, obtém: 1,325 O que é desincronizado. Isso também está correto?
Guemario #
@ Gilson, isso está correto. Se a latência para os dois pacotes for diferente (quase garantida), o cálculo do tempo delta do cliente não será perfeito. Não há como o cliente ser perfeito. No algoritmo que você descreveu na pergunta, o tempo delta foi calculado várias vezes e havia um método para selecionar e calcular a média desses resultados. Muitos métodos de sincronização de tempo farão algo assim, mas geralmente são para servidores críticos e de longa duração, como para bolsas de valores. Pela precisão do tempo que você precisa para um jogo curto, acho que seria um exagero.
John McDonald
@ Gilson, contanto que o cliente tenha uma estimativa razoável do tempo do servidor e ambos os relógios avancem aproximadamente na mesma taxa, você não deve notar nenhum problema. Quando o cliente ou o servidor envia uma ação para o outro lado, eles enviam a hora local. No lado de recebimento, a mensagem deve sempre estar no passado e precisará ser interpretada como um evento passado. Isso significa que você precisa de todas as informações no pacote, como localização, velocidade (magnitude + direção) e tempo. Depois, você pode colocar o disco ali e depois e movê-lo do passado para o presente. Estou no chat btw
John McDonald