Caractéristiques de l'établissement d'une connexion entre les participants à un jeu en réseau peer-to-peer

Il s'agit d'une collection d'informations dont j'avais besoin pour implémenter l'étape d'établissement d'une connexion entre les participants à un jeu en réseau peer-to-peer utilisant le protocole UDP.

L'article est conçu pour les développeurs de jeux débutants. J'ai essayé d'écrire un article que j'aimerais moi-même lire à un moment où je commençais à peine à comprendre ce sujet: pour que toutes les nuances nécessaires soient rassemblées en un seul endroit, mais en même temps il n'y a rien de superflu, dans un langage simple, et avec des images visuelles. Peut-être que quelqu'un vous sera utile.

Les développeurs de jeux expérimentés ne trouveront probablement rien de nouveau ici. Mais je serai reconnaissant pour les commentaires et commentaires.



Jeu en réseau avec architecture peer-to-peer


  • Chaque joueur stocke la totalité de l'état du monde du jeu et le traite de manière synchrone avec les autres joueurs. Chaque joueur transmet les actions de l'utilisateur à tous les autres joueurs. Le joueur principal qui récupère les autres joueurs est appelé le serveur et les autres sont des clients. Le serveur n'est le principal qu'au stade de la collecte des joueurs. Et pendant le jeu, il n'y a pas d'ordinateur principal.
  • Cette approche présente les caractéristiques suivantes:
    • Le trafic ne dépend pas de la complexité du monde du jeu, mais uniquement du nombre de joueurs. Dans ce mode, les stratégies en temps réel fonctionnent généralement, où vous devez traiter des milliers d'unités.
    • Le volume de trafic est de l'ordre de N², où N est le nombre de joueurs. Par conséquent, cette approche n'est applicable que pour les jeux avec un petit nombre de joueurs.
    • Comme les données sont transmises directement entre les joueurs sans serveur intermédiaire, les retards de transmission (décalages) sont minimes. Mais si au moins l'un des joueurs a des problèmes de communication, cela affectera tous les joueurs.
    • Il est nécessaire d'établir des canaux de communication entre tous les acteurs. Mais si les joueurs sont sur des réseaux locaux différents, ce n'est pas toujours possible.
  • Vous pouvez utiliser TCP ou UDP pour transférer des données dans le jeu.
    • TCP (Transmission Control Protocol) fournit une livraison de flux d'octets fiable. Cela simplifie la mise en œuvre du jeu, mais il n'y a aucun contrôle sur les délais de transmission des données.
    • UDP (User Datagram Protocol) est un simple protocole de transfert de paquets sans garantie de leur livraison. Mais en raison de sa simplicité, UDP est utilisé dans les systèmes en temps réel lorsqu'il est inacceptable d'attendre des paquets retardés ou perdus. L'utilisation d'UDP peut réduire les retards dans le jeu, mais complique la mise en œuvre du jeu.
    Cet article décrit l'utilisation d'UDP.

Etablissement d'une connexion sur le réseau local


  • Pour établir une connexion entre les joueurs, le client doit connaître l'adresse IP du serveur et le port que le programme de jeu écoute.


    • Par exemple, l'ordinateur du joueur A a une adresse IP de 192.168.1.2 sur le réseau local. Le joueur A lance le programme de jeu sur son ordinateur en mode serveur et le programme écoute sur le port 50120. Le joueur A communique en quelque sorte ces informations au joueur B.
    • L'ordinateur du joueur B a une adresse IP de 192.168.1.5 sur le réseau local. Le joueur B lance le programme de jeu sur son ordinateur en mode client et le programme occupe le port 50150. Le joueur B entre l'adresse du serveur 192.168.1.2►0120 dans le programme de jeu et le programme envoie une demande au serveur à l'adresse spécifiée.
    • Le serveur est en mode veille, et après avoir reçu une demande du joueur B, il trouvera son adresse 192.168.1.5►0150. Ainsi, le client et le serveur ont établi une connexion et peuvent démarrer le jeu.
  • Si plusieurs clients sont connectés au serveur, le serveur doit envoyer à chacun d'eux les adresses des autres clients.


    Dans l'exemple de l'image, le serveur A envoie au client B l'adresse du client C (192.168.1.6►0160) et au client C envoie l'adresse du client B (192.168.1.5►0150). Ainsi, tous les joueurs pourront établir des connexions "chacun avec chacun".
  • Chaque fois que le jeu démarre, le serveur peut demander n'importe quel port libre au système d'exploitation. Mais il est plus pratique d'utiliser le même port à chaque fois afin que le client n'ait pas à entrer un nouveau port à chaque fois.

    Tous les ports sont divisés en trois plages:
    • [0, 1023] - bien connu (systémique).
    • [1024, 49151] - enregistré (utilisateur). Le port du serveur doit être sélectionné dans cette plage.
    • [49152, 65535] - dynamique (privé). Ici, le système d'exploitation alloue des ports temporaires aux programmes.

    Sur Wikipédia, vous pouvez voir une liste des ports réservés . Ensuite, par exemple, le port 49094 pour le serveur de jeu est sélectionné.
  • Un ordinateur peut avoir plusieurs interfaces réseau, à la fois réelles (Ethernet, WiFi) et virtuelles (VPN). Chaque interface réseau a sa propre adresse IP. Le programme peut fournir à l'utilisateur du serveur la possibilité de sélectionner l'interface réseau par laquelle il attendra les demandes des clients. Mais il est pratique d'utiliser une adresse IP générique spéciale 0.0.0.0 . Si le programme ouvre un socket avec cette adresse IP, il écoutera le port spécifié pour toutes les interfaces réseau de cet ordinateur. Ainsi, le joueur n'a pas à penser quelle interface réseau choisir.
  • Vous pouvez également aider le client et déterminer automatiquement l'adresse IP du serveur. Si le client envoie une demande à une adresse IP de diffusion spéciale (diffusion) , tous les ordinateurs du réseau local la recevront.

    Par exemple, si l'adresse réseau est 192.168.1.0, le masque de sous-réseau est 255.255.255.0, alors l'adresse IP de diffusion sera 192.168.1.255.


    Si tous les serveurs de jeu écoutent sur le port 49094, le paquet envoyé à l'adresse 192.168.1.255-00-009094 sera reçu par tous les serveurs de jeu sur ce réseau. Chaque serveur enverra une confirmation à l'expéditeur. Ainsi, le client recevra une liste de tous les serveurs de jeu sur son réseau et pourra sélectionner le serveur dont il a besoin.
  • Si tous les joueurs sont sur le même réseau local, établir des connexions «chacun avec chacun» est assez simple. Il peut y avoir des problèmes avec l'accès client au port du serveur en raison du pare-feu. Mais cela dépend du système d'exploitation et des paramètres de sécurité.

Établir une connexion d'un réseau local à un serveur sur Internet


  • En règle générale, les ordinateurs ne sont pas connectés à Internet directement, mais via un routeur. Et, en règle générale, le routeur effectue la traduction d'adresses réseau (NAT, Network Address Translation) .

    Par exemple, le client C est sur le réseau local et a accès à Internet via le routeur B, tandis que le serveur A a une adresse IP publique sur Internet. Donnez-leur les adresses suivantes:


    • L'adresse Internet du serveur A est 203.0.113.2. Le programme serveur écoute sur le port 49094.
    • L'adresse du routeur B sur Internet est 203.0.113.5. L'adresse du routeur B sur le réseau local est 192.168.1.1.
    • L'adresse du client C sur le réseau local est 192.168.1.5. Le programme client occupe le port 50150 et envoie un paquet au serveur au 203.0.113.2-00-009094.
    • Le routeur B est la passerelle par défaut sur son réseau local. Autrement dit, tous les paquets dont les adresses ne proviennent pas du réseau local actuel lui sont envoyés.
    • Le routeur possède une table de traduction d'adresses: (adresse interne: port interne) - (adresse externe: port externe). Après avoir reçu un paquet du client vers le serveur 203.0.113.2-00-009094, le routeur sélectionne n'importe quel port externe libre, par exemple 52050, et crée une entrée dans la table de traduction des adresses: 192.168.1.5►0150 - 203.0.113.5►2050.
    • Le routeur remplace l'adresse interne de l'expéditeur 192.168.1.5/100150 dans le paquet par l'adresse externe 203.0.113.5/102050 et transfère le paquet modifié au serveur.
    • Le serveur reçoit un paquet avec l'adresse de l'expéditeur 203.0.113.5► 2020 et envoie une réponse à cette adresse, c'est-à-dire au routeur.
    • Après avoir reçu le paquet du serveur, le routeur recherche l'adresse du destinataire dans sa table de traduction d'adresse, effectue le remplacement inverse de l'adresse externe du destinataire 203.0.113.5►2050 par l'adresse interne 192.168.1.5►0150 et envoie le paquet à cette adresse, c'est-à-dire le client.
    • Si le client envoie des paquets ultérieurs au serveur, le routeur examine l'adresse de l'expéditeur pour voir si un tel enregistrement existe déjà dans la table et, dans l'affirmative, il utilise le port externe précédemment alloué, dans ce cas 52050.
    • Ainsi, le client et le serveur ont établi une connexion via NAT et peuvent démarrer le jeu. Mais le serveur ne connaît pas l'adresse interne du client dans son réseau local et considère que le client est un routeur.
    • L'entrée dans la table de traduction d'adresses est valide pendant une certaine période de temps, généralement 1 à 3 minutes. Par conséquent, le client et le serveur doivent périodiquement échanger des paquets afin que l'enregistrement qui les connecte ne soit pas supprimé. Si le client envoie un nouveau paquet au serveur après avoir supprimé l'enregistrement, alors un port externe différent peut être alloué pour le nouvel enregistrement sur le routeur, et pour le serveur, ce sera un autre client avec une adresse différente.
  • En règle générale, le routeur de l'utilisateur n'est pas connecté directement à Internet, mais se trouve sur le réseau interne du fournisseur Internet. Autrement dit, le client est derrière deux NAT.

    Par exemple, comme ceci:


    C'est-à-dire qu'une double traduction des adresses réseau est effectuée.
  • Il existe différents types de NAT:
    • NAT à cône plein


      • Après la création d'une entrée dans la table de traduction d'adresses (adresse interne: port interne) - (adresse externe: port externe), tous les paquets de l'expéditeur (adresse interne: port interne) sont transmis via (adresse externe: port externe) à n'importe quelle adresse de destinataire.
      • Tout serveur externe peut envoyer des paquets à (adresse interne: port interne), envoyer des paquets à (adresse externe: port externe).
      Par exemple, le client C envoie des paquets aux serveurs A et D en utilisant la même adresse externe 203.0.113.5► 202050. Si le serveur E envoie un paquet à cette adresse 203.0.113.5► 202050, le routeur le transmettra au client C.
    • NAT à cône à adresse restreinte.


      • Après la création d'une entrée dans la table de traduction d'adresses (adresse interne: port interne) - (adresse externe: port externe), tous les paquets de l'expéditeur (adresse interne: port interne) sont transmis via (adresse externe: port externe) à n'importe quelle adresse de destinataire.
      • Un serveur externe (adresse du serveur: port du serveur) peut envoyer des paquets à (adresse interne: port interne), en envoyant des paquets à (adresse externe: port externe) uniquement si (adresse interne: port interne) précédemment envoyé des paquets à (adresse du serveur: tout port).
      Par exemple, le client C envoie des paquets aux serveurs A et D en utilisant la même adresse externe 203.0.113.5► 202050. Le serveur D peut envoyer un paquet au client C via l'adresse de routeur 203.0.113.5► 202050 depuis n'importe lequel de ses ports. Mais le routeur ne transmettra pas les paquets du serveur E.
    • NAT à cône restreint au port.


      • Après la création d'une entrée dans la table de traduction d'adresses (adresse interne: port interne) - (adresse externe: port externe), tous les paquets de l'expéditeur (adresse interne: port interne) sont transmis via (adresse externe: port externe) à n'importe quelle adresse de destinataire.
      • Un serveur externe (adresse du serveur: port du serveur) peut envoyer des paquets à (adresse interne: port interne), en envoyant des paquets à (adresse externe: port externe) uniquement si (adresse interne: port interne) précédemment envoyé des paquets à (adresse du serveur: port serveur).
      Par exemple, le client C envoie des paquets aux serveurs A et D en utilisant la même adresse externe 203.0.113.5► 202050. Le routeur ne transmettra pas les paquets du serveur E ou d'un autre port de serveur D.
    • NAT symétrique (NAT symétrique).


      • Si le même expéditeur interne (adresse interne: port interne) envoie des paquets à différents destinataires (adresse de serveur: port de serveur), un port externe distinct sera alloué pour chaque adresse de destinataire et une entrée distincte sera utilisée dans la table de traduction des adresses.
      • Seul le serveur externe (adresse du serveur: port du serveur) qui a reçu le paquet de l'expéditeur interne (adresse interne: port interne) peut renvoyer le paquet.
      Par exemple, le client C envoie des paquets au serveur A en utilisant une adresse externe 203.0.113.5► 202050, et au serveur D en utilisant une autre adresse externe 203.0.113.5.20201. Le routeur ne transmettra pas les paquets du serveur E ou d'un autre port de serveur D.
  • Si plusieurs clients sont connectés au serveur de jeu, pour un jeu d'égal à égal, vous devez établir une connexion directement entre chaque paire de clients contournant le serveur.

    Par exemple, deux clients C et E connectés au serveur A:


    • Le client C a une adresse interne de 192.168.1.5/100150 et une adresse externe de 203.0.113.5/102050.
    • Le client E a une adresse interne de 192.168.2.5:50250 et une adresse externe de 203.0.113.6-062060.
    • Le serveur doit indiquer à chaque client l'adresse externe de l'autre client.
    • En règle générale, les routeurs utilisent un NAT de type NAT à cône restreint au port. Le routeur transmet les paquets au client uniquement à partir de l'expéditeur (adresse IP et port) auquel le client a envoyé les paquets plus tôt. Et si le client C envoie un paquet au client E, le routeur D ne manquera pas ce paquet.
    • Dans ce cas, la méthode de perforation UDP est utilisée. Les deux clients doivent s’envoyer des paquets UDP. Dès que le client C envoie un paquet au client E, alors le routeur B sera prêt à recevoir des paquets du client E. De même, dès que le client E envoie un paquet au client C, alors le routeur D sera prêt à recevoir des paquets du client C. Les premiers paquets du client qui a démarré transmettre en premier sera perdu. Mais dès que le deuxième client commence également à envoyer des paquets vers, les deux clients pourront échanger des paquets.
    • Mais si le routeur utilise NAT symétrique, un nouveau port externe sera alloué à chaque destinataire. Et, en règle générale, il n'y a aucun moyen de savoir quel port le routeur a alloué. Par conséquent, si au moins l'un des routeurs utilise NAT symétrique, il sera impossible d'établir une connexion entre les clients.
  • Si deux clients sont sur le même réseau local, ils ne connaîtront que les adresses externes de l'autre.


    Par exemple, le client C envoie un paquet au client D à son adresse externe 203.0.113.5Point2051. Le routeur B doit traiter ce paquet comme s'il était reçu d'un réseau externe, remplacer l'adresse du destinataire 203.0.113.5► 20201 par l'adresse interne du client D 192.168.1.6►0060 et renvoyer le paquet au client D vers le réseau local. Cette fonction de routeur est appelée boucle de NAT ( épingle à cheveux NAT ). Si le bouclage NAT est désactivé sur le routeur, il sera impossible d'établir une connexion entre les clients.
  • Les clients peuvent être situés dans différents réseaux locaux, mais ont accès à Internet via un fournisseur Internet commun.


    Si le bouclage NAT est désactivé sur l'équipement du fournisseur de services Internet, il sera impossible d'établir une connexion entre les clients.
  • Que dois-je faire si le NAT symétrique est utilisé sur le routeur ou si le bouclage NAT est désactivé?

    Si je comprends bien, le seul moyen fiable de résoudre ces problèmes est de transférer les paquets non pas directement entre les clients, mais via un serveur. Il est nécessaire soit d'implémenter cette fonction dans le programme de jeu dans le cadre de l'architecture peer-to-peer, soit d'implémenter l'architecture client-serveur.

    Ou vous pouvez utiliser des programmes tiers pour créer un VPN, par exemple LogMeIn Hamachi .

Établir une connexion entre des LAN sur Internet


  • En règle générale, aucun des joueurs n'a d'adresse IP publique et les joueurs se trouvent sur différents réseaux locaux derrière NAT.


    Par exemple, dans ce schéma, le client C enverra des paquets au serveur A à son adresse externe 203.0.113.2-022020, dans laquelle le port est sélectionné par le routeur B.Par conséquent, le choix du port interne du serveur A n'a pas d'importance et vous pouvez sélectionner n'importe quel port. Dans ce cas, au lieu du port 49094, le serveur A peut demander n'importe quel port libre au système d'exploitation, par exemple 50120.
  • Pour que le serveur A et le client C établissent une connexion, tout d'abord, chacun d'eux doit en quelque sorte trouver son adresse externe.

    Pour cela, vous pouvez utiliser le protocole STUN (Session Traversal Utilities for NAT) .


    Le protocole STUN permet au client situé derrière NAT de déterminer son adresse IP externe et son port.

    Les messages STUN sont envoyés dans des paquets UDP.

    Le client peut accéder à n'importe lequel des serveurs STUN publics. Une liste de serveurs STUN publics peut être trouvée sur Wikipedia . Ou recherchez «liste des serveurs STUN publics» .

    Cette méthode n'est pas applicable si au moins un des lecteurs du routeur utilise «NAT symétrique».
  • Après que chacun des joueurs a appris son adresse externe, il doit en quelque sorte donner son adresse à tous les autres joueurs.

    Pour que les joueurs puissent échanger leurs adresses, vous pouvez utiliser votre serveur public. Dans le diagramme, il est appelé serveur d'adresses.


    Cela ne nécessite pas nécessairement un serveur dédié. Vous pouvez utiliser n'importe quel hébergement avec un serveur Web et avec le support de tout langage de script, tel que PHP. En utilisant le protocole HTTP, chaque joueur doit envoyer son adresse externe au serveur d'adresses. Et puis demandez les adresses de tous les autres joueurs.
  • Par exemple, chaque serveur de jeux peut enregistrer son identifiant unique (game-id) sur le serveur d'adresses. Vous pouvez utiliser n'importe quelle chaîne comme identifiant de jeu, par exemple, le surnom (alias) de l'utilisateur du serveur. L'utilisateur du serveur indique en quelque sorte l'ID de jeu aux joueurs qu'il souhaite inviter à sa partie. Et ces joueurs pourront rejoindre le jeu en demandant par game-id au serveur d'adresse les adresses externes de tous les participants à ce jeu.
  • Une fois que chaque joueur a reçu les adresses externes de tous les autres joueurs, ils peuvent établir des connexions entre eux en utilisant la méthode de perforation UDP .

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


All Articles