Tengo una pregunta sobre UDP. Por contexto, estoy trabajando en un juego de acción en tiempo real.
He leído bastante sobre las diferencias entre UDP y TCP y creo que las entiendo bastante bien, pero hay una pieza que nunca se ha sentido correcta, y es la fiabilidad y los reconocimientos específicos . Entiendo que UDP no ofrece confiabilidad por defecto (es decir, los paquetes se pueden descartar o llegar fuera de servicio). Cuando se requiere cierta confiabilidad, la solución que he visto (lo que tiene sentido conceptualmente) es usar confirmaciones (es decir, el servidor envía un paquete al cliente, y cuando el cliente recibe ese mensaje, envía una confirmación al servidor) .
¿Qué sucede cuando se descarta el reconocimiento?
En el ejemplo anterior (un servidor que envía un paquete a un cliente), el servidor maneja la pérdida potencial de paquetes reenviando paquetes en cada trama hasta que se reciban los acuses de recibo de esos paquetes. Aún podría encontrarse con problemas de ancho de banda o mensajes fuera de servicio, pero puramente desde una perspectiva de pérdida de paquetes, el servidor está cubierto.
Sin embargo, si el cliente envía un acuse de recibo que nunca llega, el servidor no tendría más remedio que dejar de enviar ese mensaje, lo que podría interrumpir el juego si se requiere la información contenida en ese paquete. Podría adoptar un enfoque similar al servidor (es decir, ¿seguir enviando acuses de recibo hasta que reciba un reconocimiento por el reconocimiento?), Pero ese enfoque lo obligaría a ir y venir para siempre (ya que necesitaría un reconocimiento por el reconocimiento por el reconocimiento) y así).
Siento que mi lógica básica es correcta aquí, lo que me deja con dos opciones.
- Envíe un solo paquete de reconocimiento y espere lo mejor.
- Envíe un puñado de paquetes de reconocimiento (quizás 3-4) y espere lo mejor, suponiendo que no todos se descartarán.
¿Hay una respuesta a este problema? ¿Estoy fundamentalmente malentendido algo? ¿Hay alguna garantía de usar UDP que desconozco? Me siento reacio a avanzar con demasiado código de red hasta que me sienta cómodo de que mi lógica es sólida.
fuente
Respuestas:
Esta es una forma del problema de los dos generales , y tiene razón: no hay suficientes reintentos para garantizar perfectamente la recepción.
En la práctica, en los juegos, generalmente hay un horizonte temporal más allá del cual la información realmente no importa, aunque técnicamente llegue de manera confiable. Como descubrir que tenía un tiro en la cabeza perfecto alineado hace 2 segundos, es demasiado tarde para que el jugador use esa información ahora.
Si su pérdida de paquetes es tan alta que no puede obtener de forma rutinaria la información necesaria dentro de una ventana de reacción ajustada, entonces para un juego en tiempo real, sería mejor patear al jugador e intentar encontrar una mejor coincidencia para ellos en otro lugar, en lugar de continúe intentando enviar el paquete para emular una conexión confiable.
Debido a esto, algunos sistemas de replicación de juegos omiten el reconocimiento y los reintentos por completo y optan por enviar spam a la actualización más reciente con la mayor frecuencia posible. Si uno se cae o llega tarde, lástima, sáltelo, tome el siguiente y continúe, confiando en los sistemas de predicción e interpolación para suavizar la brecha y minimizar el hipo visible para el jugador.
De repente quiero comenzar a llamar a esto "Simba Replication" por cómo ignora los problemas en el pasado y trata de vivir en el momento presente. ;)
Una solución híbrida es correr hacia adelante enviando la nueva actualización Y (dado que las actualizaciones de estado del juego a menudo pueden ser bastante pequeñas / compresibles ) también empacar en la última actualización, y tal vez la anterior ... Entonces, en caso de que el cliente las perdiera , no tiene que esperar un tiempo completo de ida y vuelta para averiguarlo y solucionarlo. La mayoría de las veces el cliente ya vio esto, por lo que hay datos redundantes de esta manera, pero la latencia para corregir un mensaje perdido es menor. Las actualizaciones del cliente pueden incluir el número de índice de la actualización consecutiva más reciente que han visto, por lo que puede ser mínimamente conservador con la cantidad de actualizaciones antiguas que incluye en el próximo paquete de actualizaciones.
También podría implementar un sistema de dos niveles como otro tipo de híbrido, donde el estado de corta duración se replica de manera poco confiable y el estado a largo plazo se sincroniza de manera confiable, utilizando TCP o su propia implementación de confiabilidad con un alto reintento contar. Sin embargo, esto se vuelve más complejo de administrar, porque tiene dos sistemas de mensajería que mantener y las dos instantáneas pueden estar fuera de sincronización entre sí, agregando una clase completamente nueva de caso límite.
fuente
El enfoque que utiliza TCP es que el remitente seguirá reenviando el paquete hasta que reciba un acuse de recibo. El receptor ignorará los paquetes duplicados, pero aún enviará acuses de recibo por ellos. El remitente ignorará los acuses de recibo duplicados.
Si se pierde un paquete, el remitente lo reenvía, como ya sabe.
Si se pierde un acuse de recibo, el remitente reenvía el paquete original, lo que hace que el receptor reenvíe el acuse de recibo.
Si no se recibe un acuse de recibo dentro de un cierto tiempo (quizás 60 segundos o 20 reintentos), se considera que el jugador está desconectado del juego. Usted debe aplicar algún tipo de regla de tiempo de espera, o de otro modo un jugador que desenchufa el cable de red, no podrá utilizar los recursos en el servidor para siempre.
fuente
Si desea reinventar TCP, tiene sentido mirar primero TCP , que se ocupa del problema exacto que describe (parte de la solución es usar valores definidos por el usuario para intentos de reintento y tiempos de espera).
Las soluciones que usan 2 canales, un canal TCP (para comunicación confiable) y un canal UDP (para comunicación de baja latencia) no son infrecuentes.
Algunas soluciones detectan cuando a un cliente le falta información durante demasiado tiempo e inician una resincronización, que puede usar UDP o TCP.
Otro enfoque común es diseñar la comunicación de manera que no se base en absoluto en los reconocimientos, pero eso está fuera del alcance de la pregunta.
fuente
En un RTS, realmente no puede usar un protocolo como TCP, y tampoco puede hacer que UDP sea confiable. Si lo intentas, el juego se congelará cada vez que haya una conexión de red.
En su lugar, diseña el protocolo para que los paquetes perdidos no importen demasiado.
La versión corta es que no te importa dónde estuvieron los otros jugadores el último cuadro, siempre y cuando sepas dónde están ahora . La versión larga es más complicada.
La pregunta entonces es, ¿qué haces cuando un paquete se pierde? Y la respuesta es ... supongo. El jugador probablemente se está moviendo en línea recta, ¿verdad? Simplemente muévalos un paso más allá en esa línea. ... Excepto que ningún jugador de RTS se mueve en línea recta. Y luego está la detección de colisiones.
Esto es duro. Muchos juegos se equivocan. Se puede argumentar que no hay una respuesta correcta a esto, solo varios errores que se pueden cambiar.
La razón por la que estos juegos funcionan bastante bien no es solo porque han pensado mucho sobre estos problemas, sino también porque Internet se ha vuelto bastante confiable. Casi todos los paquetes UDP llegan a su destino de manera oportuna. (A menos que haya un problema permanente como un firewall)
fuente