Características de establecer una conexión entre los participantes en un juego de red de igual a igual

Esta es una recopilación de información que necesitaba para implementar la etapa de establecer una conexión entre los participantes en un juego de red de igual a igual utilizando el protocolo UDP.

El artículo está diseñado para desarrolladores de juegos para principiantes. Traté de escribir un artículo que yo mismo quisiera leer en un momento en que apenas comenzaba a entender este tema: para que todos los matices necesarios se recopilen en un solo lugar, pero al mismo tiempo no hay nada superfluo, en un lenguaje simple y con imágenes visuales. Tal vez alguien sea útil.

Es poco probable que los desarrolladores de juegos experimentados encuentren algo nuevo para ellos aquí. Pero agradeceré los comentarios y comentarios.



Juego en red con arquitectura peer-to-peer


  • Cada jugador almacena todo el estado del mundo del juego y lo procesa sincrónicamente con otros jugadores. Cada jugador pasa las acciones del usuario a todos los demás jugadores. El jugador principal que recoge a los otros jugadores se llama servidor y el resto son clientes. El servidor es el principal solo en la etapa de recolección de jugadores. Y durante el juego no hay computadora principal.
  • Este enfoque tiene las siguientes características:
    • El tráfico no depende de la complejidad del mundo del juego, sino solo del número de jugadores. En este modo, las estrategias en tiempo real generalmente funcionan, donde necesita procesar miles de unidades.
    • El volumen de tráfico es del orden de N², donde N es el número de jugadores. Por lo tanto, este enfoque es aplicable solo para juegos con un pequeño número de jugadores.
    • Dado que los datos se transmiten directamente entre jugadores sin un servidor intermedio, los retrasos de transmisión (retrasos) son mínimos. Pero si al menos uno de los jugadores tiene problemas de comunicación, esto afectará a todos los jugadores.
    • Es necesario establecer canales de comunicación entre todos los jugadores. Pero si los jugadores están en diferentes redes locales, entonces esto no siempre es posible.
  • Puedes usar TCP o UDP para transferir datos en el juego.
    • TCP (Protocolo de control de transmisión) proporciona una entrega confiable de flujo de bytes. Esto simplifica la implementación del juego, pero no hay control sobre los retrasos en la transmisión de datos.
    • UDP (User Datagram Protocol) es un protocolo simple de transferencia de paquetes sin una garantía de su entrega. Pero debido a su simplicidad, UDP se usa en sistemas en tiempo real cuando es inaceptable esperar paquetes retrasados ​​o perdidos. El uso de UDP puede reducir los retrasos en el juego, pero complica la implementación del juego.
    Este artículo describe el uso de UDP.

Establecer una conexión en la red local


  • Para establecer una conexión entre jugadores, el cliente necesita conocer la dirección IP del servidor y el puerto que escucha el programa del juego.


    • Por ejemplo, la computadora del jugador A tiene una dirección IP de 192.168.1.2 en la red local. El jugador A inicia el programa del juego en su computadora en modo servidor, y el programa escucha en el puerto 50120. El jugador A de alguna manera comunica esta información al jugador B.
    • La computadora del jugador B tiene una dirección IP de 192.168.1.5 en la red local. El jugador B inicia el programa del juego en su computadora en modo cliente, y el programa ocupa el puerto 50150. El jugador B ingresa la dirección del servidor 192.168.1.2►0120 en el programa del juego, y el programa envía una solicitud al servidor en la dirección especificada.
    • El servidor está en modo de espera y, tras recibir una solicitud del jugador B, encontrará su dirección 192.168.1.5►0150. Por lo tanto, el cliente y el servidor establecieron una conexión y pueden iniciar el juego.
  • Si varios clientes están conectados al servidor, entonces el servidor debe enviar a cada uno de ellos las direcciones de otros clientes.


    En el ejemplo de la imagen, el servidor A envía al cliente B la dirección del cliente C (192.168.1.6►0160), y al cliente C envía la dirección del cliente B (192.168.1.5►0150). Por lo tanto, todos los jugadores podrán establecer conexiones "cada uno con cada uno".
  • Cada vez que se inicia el juego, el servidor puede solicitar cualquier puerto libre del sistema operativo. Pero es más conveniente usar el mismo puerto cada vez para que el cliente no tenga que ingresar un nuevo puerto cada vez.

    Todos los puertos se dividen en tres rangos:
    • [0, 1023] - bien conocido (sistémico).
    • [1024, 49151] - registrado (usuario). El puerto para el servidor debe seleccionarse en este rango.
    • [49152, 65535] - dinámico (privado). Aquí, el sistema operativo asigna puertos temporales para los programas.

    En Wikipedia, puede ver una lista de puertos reservados . A continuación, por ejemplo, se selecciona el puerto 49094 para el servidor de juegos.
  • Una computadora puede tener varias interfaces de red, tanto reales (Ethernet, WiFi) como virtuales (VPN). Cada interfaz de red tiene su propia dirección IP. El programa puede proporcionar al usuario del servidor la oportunidad de seleccionar la interfaz de red mediante la cual esperará las solicitudes del cliente. Pero es conveniente usar una dirección IP comodín especial 0.0.0.0 . Si el programa abre un socket con esta dirección IP, escuchará el puerto especificado para todas las interfaces de red de esta computadora. Por lo tanto, el jugador no tiene que pensar qué interfaz de red elegir.
  • También puede ayudar al cliente y determinar automáticamente la dirección IP del servidor. Si el cliente envía una solicitud a una dirección IP de transmisión especial (transmisión) , todas las computadoras de la red local la recibirán.

    Por ejemplo, si la dirección de red es 192.168.1.0, la máscara de subred es 255.255.255.0, entonces la dirección IP de transmisión será 192.168.1.255.


    Si todos los servidores de juegos están escuchando en el puerto 49094, entonces el paquete enviado a la dirección 192.168.1.255-00-009094 será recibido por todos los servidores de juegos en esta red. Cada servidor enviará una confirmación al remitente. Y así, el cliente recibirá una lista de todos los servidores del juego en su red, y podrá seleccionar el servidor que necesita.
  • Si todos los jugadores están en la misma red local, entonces establecer conexiones "cada una con cada" es bastante simple. Puede haber problemas con el acceso del cliente al puerto del servidor debido a Firewall. Pero depende del sistema operativo y la configuración de seguridad.

Establecer una conexión desde una red local a un servidor en Internet


  • Como regla general, las computadoras no están conectadas a Internet directamente, sino a través de un enrutador. Y, como regla, el enrutador realiza la traducción de direcciones de red (NAT, traducción de direcciones de red) .

    Por ejemplo, el cliente C está en la red local y tiene acceso a Internet a través del enrutador B, mientras que el servidor A tiene una dirección IP pública en Internet. Permítales tener las siguientes direcciones:


    • La dirección de Internet del servidor A es 203.0.113.2. El programa del servidor escucha en el puerto 49094.
    • La dirección del enrutador B en Internet es 203.0.113.5. La dirección del enrutador B en la red local es 192.168.1.1.
    • La dirección del cliente C en la red local es 192.168.1.5. El programa cliente ocupa el puerto 50150 y envía un paquete al servidor en 203.0.113.2-00-009094.
    • El enrutador B es la puerta de enlace predeterminada en su red local. Es decir, todos los paquetes con direcciones que no pertenecen a la red local actual se envían a ella.
    • El enrutador tiene una tabla de traducción de direcciones: (dirección interna: puerto interno) - (dirección externa: puerto externo). Después de recibir un paquete del cliente al servidor 203.0.113.2-00-009094, el enrutador selecciona cualquier puerto externo libre, por ejemplo 52050, y crea una entrada en la tabla de traducción de direcciones: 192.168.1.5►0150 - 203.0.113.5►2050.
    • El enrutador reemplaza la dirección interna del remitente 192.168.1.5/100150 en el paquete con la dirección externa 203.0.113.5/102050, y transfiere el paquete cambiado al servidor.
    • El servidor recibe un paquete con la dirección del remitente 203.0.113.5► 2020, y envía una respuesta a esta dirección, es decir, al enrutador.
    • Después de recibir el paquete del servidor, el enrutador busca la dirección del destinatario en su tabla de traducción de direcciones, realiza el reemplazo inverso de la dirección externa del destinatario 203.0.113.5►2050 con la dirección interna 192.168.1.5►0150, y envía el paquete a esta dirección, es decir, al cliente.
    • Si el cliente envía paquetes subsiguientes al servidor, el enrutador mira la dirección del remitente para ver si ese registro ya existe en la tabla y, de ser así, utiliza el puerto externo previamente asignado, en este caso 52050.
    • Por lo tanto, el cliente y el servidor establecieron una conexión a través de NAT y pueden iniciar el juego. Pero el servidor no conoce la dirección interna del cliente en su red local y considera que el cliente es un enrutador.
    • La entrada en la tabla de traducción de direcciones es válida por un cierto período de tiempo, generalmente de 1 a 3 minutos. Por lo tanto, el cliente y el servidor necesitan intercambiar paquetes periódicamente para que no se elimine el registro que los conecta. Si el cliente envía un nuevo paquete al servidor después de eliminar el registro, se puede asignar un puerto externo diferente para el nuevo registro en el enrutador, y para el servidor será otro cliente con una dirección diferente.
  • Como regla general, el enrutador del usuario no está conectado directamente a Internet, sino que se encuentra en la red interna del proveedor de Internet. Es decir, el cliente está detrás de dos NAT.

    Por ejemplo, así:


    Es decir, se realiza una doble traducción de las direcciones de red.
  • Existen diferentes tipos de NAT:
    • Cono completo NAT


      • Después de crear una entrada en la tabla de traducción de direcciones (dirección interna: puerto interno) - (dirección externa: puerto externo), todos los paquetes del remitente (dirección interna: puerto interno) se transmiten a través de (dirección externa: puerto externo) a cualquier dirección de destinatario.
      • Cualquier servidor externo puede enviar paquetes a (dirección interna: puerto interno), enviando paquetes a (dirección externa: puerto externo).
      Por ejemplo, el cliente C envía paquetes a los servidores A y D utilizando la misma dirección externa 203.0.113.5► 202050. Si el servidor E envía un paquete a esta dirección 203.0.113.5► 202050, el enrutador lo pasará al cliente C.
    • Cono restringido por dirección NAT.


      • Después de crear una entrada en la tabla de traducción de direcciones (dirección interna: puerto interno) - (dirección externa: puerto externo), todos los paquetes del remitente (dirección interna: puerto interno) se transmiten a través de (dirección externa: puerto externo) a cualquier dirección de destinatario.
      • Un servidor externo (dirección del servidor: puerto del servidor) puede enviar paquetes a (dirección interna: puerto interno), enviando paquetes a (dirección externa: puerto externo) solo si (dirección interna: puerto interno) envió previamente paquetes a (dirección del servidor: cualquiera puerto).
      Por ejemplo, el cliente C envía paquetes a los servidores A y D utilizando la misma dirección externa 203.0.113.5► 202050. El servidor D puede enviar un paquete al cliente C a través de la dirección de enrutador 203.0.113.5► 202050 desde cualquiera de sus puertos. Pero el enrutador no pasará paquetes del servidor E.
    • Cono restringido a puertos NAT.


      • Después de crear una entrada en la tabla de traducción de direcciones (dirección interna: puerto interno) - (dirección externa: puerto externo), todos los paquetes del remitente (dirección interna: puerto interno) se transmiten a través de (dirección externa: puerto externo) a cualquier dirección de destinatario.
      • Un servidor externo (dirección del servidor: puerto del servidor) puede enviar paquetes a (dirección interna: puerto interno), enviando paquetes a (dirección externa: puerto externo) solo si (dirección interna: puerto interno) envió paquetes previamente a (dirección del servidor: puerto servidor).
      Por ejemplo, el cliente C envía paquetes a los servidores A y D utilizando la misma dirección externa 203.0.113.5► 202050. El enrutador no pasará paquetes desde el servidor E u otro puerto del servidor D.
    • NAT simétrica (NAT simétrica).


      • Si el mismo remitente interno (dirección interna: puerto interno) envía paquetes a diferentes destinatarios (dirección del servidor: puerto del servidor), se asignará un puerto externo separado para cada dirección de destinatario y se usará una entrada separada en la tabla de traducción de direcciones.
      • Solo el servidor externo (dirección del servidor: puerto del servidor) que recibió el paquete del remitente interno (dirección interna: puerto interno) puede devolver el paquete.
      Por ejemplo, el cliente C envía paquetes al servidor A usando una dirección externa 203.0.113.5► 202050, y al servidor D usando otra dirección externa 203.0.113.5. 20201. El enrutador no pasará paquetes desde el servidor E u otro puerto del servidor D.
  • Si varios clientes están conectados al servidor del juego, entonces para un juego entre pares, debe establecer una conexión directamente entre cada par de clientes sin pasar por el servidor.

    Por ejemplo, dos clientes C y E conectados al servidor A:


    • El cliente C tiene una dirección interna de 192.168.1.5/100150 y una dirección externa de 203.0.113.5/102050.
    • El cliente E tiene una dirección interna de 192.168.2.5:50250 y una dirección externa de 203.0.113.6-062060.
    • El servidor debe informar a cada cliente la dirección externa del otro cliente.
    • Por lo general, los enrutadores utilizan un cono NAT de tipo puerto restringido. El enrutador pasa los paquetes al cliente solo desde el remitente (dirección IP y puerto) al que el cliente envió los paquetes anteriormente. Y si el cliente C envía un paquete al cliente E, el enrutador D no perderá este paquete.
    • En este caso, se utiliza el método de perforación de agujeros UDP . Ambos clientes deben enviarse paquetes UDP entre sí. Tan pronto como el cliente C envíe un paquete al cliente E, el enrutador B estará listo para recibir paquetes del cliente E. De manera similar, tan pronto como el cliente E envíe un paquete al cliente C, el enrutador D estará listo para recibir paquetes del cliente C. Los primeros paquetes del cliente que inició transmitir primero se perderá. Pero tan pronto como el segundo cliente también comience a enviar paquetes, ambos clientes podrán intercambiar paquetes.
    • Pero si el enrutador usa NAT simétrica, se asignará un nuevo puerto externo para cada destinatario. Y, como regla, no hay forma de averiguar qué puerto ha asignado el enrutador. Por lo tanto, si al menos uno de los enrutadores utiliza NAT simétrica, será imposible establecer una conexión entre los clientes.
  • Si dos clientes están en la misma red local, entonces solo conocerán las direcciones externas de cada uno.


    Por ejemplo, el cliente C envía un paquete al cliente D en su dirección externa 203.0.113.5Point2051. El enrutador B debe procesar este paquete como si se hubiera recibido de una red externa, reemplazar la dirección del destinatario 203.0.113.5► 20205 con la dirección interna del cliente D 192.168.1.6►0060 y enviar el paquete al cliente D nuevamente a la red local. Esta función de enrutador se llama NAT loopback ( NAT hairpinning ). Si NAT loopback está deshabilitado en el enrutador, entonces será imposible establecer una conexión entre clientes.
  • Los clientes pueden estar ubicados en diferentes redes locales, pero tienen acceso a Internet a través de un proveedor de Internet común.


    Si NAT loopback está desactivado en el equipo del proveedor de servicios de Internet, será imposible establecer una conexión entre los clientes.
  • ¿Qué debo hacer si se utiliza NAT simétrica en el enrutador o si se desactiva NAT loopback?

    Según tengo entendido, la única forma confiable de resolver estos problemas es transferir paquetes no directamente entre clientes, sino a través de un servidor. Es necesario implementar esta función en el programa del juego dentro del marco de la arquitectura punto a punto o implementar la arquitectura cliente-servidor.

    O puede usar programas de terceros para crear una VPN, por ejemplo LogMeIn Hamachi .

Establecimiento de una conexión entre LAN en Internet


  • Como regla general, ninguno de los jugadores tiene una dirección IP pública, y los jugadores están en diferentes redes locales detrás de NAT.


    Por ejemplo, en este esquema, el cliente C enviará paquetes al servidor A en su dirección externa 203.0.113.2-022020, en la cual el puerto es seleccionado por el enrutador B. Por lo tanto, elegir el puerto interno del servidor A no importa, y puede seleccionar cualquier puerto. En este caso, en lugar del puerto 49094, el servidor A puede solicitar cualquier puerto libre del sistema operativo, por ejemplo 50120.
  • Para que el servidor A y el cliente C establezcan una conexión, primero, cada uno de ellos necesita encontrar de alguna manera su dirección externa.

    Puede utilizar el protocolo STUN (Utilidades de recorrido de sesión para NAT) para esto .


    El protocolo STUN permite al cliente ubicado detrás de NAT determinar su puerto y dirección IP externa.

    Los mensajes STUN se envían en paquetes UDP.

    El cliente puede acceder a cualquiera de los servidores STUN públicos. Se puede encontrar una lista de servidores STUN públicos en Wikipedia . O busque la "lista de servidores STUN públicos" .

    Este método no es aplicable si al menos uno de los jugadores en el enrutador usa "NAT simétrica".
  • Después de que cada uno de los jugadores haya aprendido su dirección externa, de alguna manera debe dar su dirección a todos los demás jugadores.

    Para que los jugadores puedan intercambiar sus direcciones, puede usar su servidor público. En el diagrama, se llama servidor de direcciones.


    Esto no requiere necesariamente un servidor dedicado. Puede utilizar cualquier alojamiento con un servidor web y con el soporte de cualquier lenguaje de script, como PHP. Usando el protocolo HTTP, cada jugador debe enviar su dirección externa al servidor de direcciones. Y luego solicite las direcciones de todos los demás jugadores.
  • Por ejemplo, cada servidor de juegos puede registrar su identificador único (game-id) en el servidor de direcciones. Puede usar cualquier cadena como id del juego, por ejemplo, el apodo (alias) del usuario del servidor. El usuario del servidor de alguna manera le dice a los jugadores a quienes quiere invitar a su juego. Y estos jugadores podrán unirse al juego solicitando por id de juego del servidor de direcciones las direcciones externas de todos los participantes en este juego.
  • Después de que cada jugador haya recibido las direcciones externas de todos los demás jugadores, pueden establecer conexiones de uno a cada usando el método de perforación de agujeros UDP .

Source: https://habr.com/ru/post/485576/


All Articles