Le réseau dans les jeux vidéo

UDP VS TCP

Vous vous lancez dans un jeu vidéo et vous voulez lui donner des fonctionnalités multijoueurs. Il est évident d'arriver à la question du protocole. Faut-il choisir TCP ou UDP. Dans cet article, Glenn Fiedler apporte une réponse.

43 commentaires Donner une note à l'article (5)

Article lu   fois.

Les deux auteur et traducteur

Site personnel

Traducteur : Profil Pro

Liens sociaux

Viadeo Twitter Facebook Share on Google+   

I. Introduction

Bonjour, et bienvenue dans la traduction française de la série d'articles « le réseau dans les jeux vidéo » écrits par Glenn Fiedler.

Dans cet article, nous allons commencer par l'aspect le plus basique de la programmation réseau : envoyer et recevoir des données à travers le réseau. Ce n'est que le commencement - la partie la plus simple de ce qu'un programmeur réseau fait -, mais ce n'est jamais simple de décider quel est le meilleur plan d'action. Attention, parce que la moindre erreur dans cette partie aura de terribles conséquences sur votre jeu multijoueur !

Vous avez probablement entendu parler de sockets, et vous savez sûrement qu'il en existe deux types principaux : TCP et UDP. Quand on programme un jeu réseau, nous devons tout d'abord choisir quel type de socket sera utilisé. Va-t-on utiliser des sockets TCP, UDP ou un mix des deux ?

Le choix dépendra du type de jeu que vous voulez faire. Donc à partir de maintenant, et pour le reste de cette série d'articles, je vais supposer que vous voulez créer un jeu d'action en réseau. Un jeu à l'instar de Halo, Battlefield, Quake, Unreal, CounterStrike, Team Fortress, etc.

Maintenant que nous voulons un jeu d'action en réseau, nous allons nous intéresser de très près à chaque type de socket, et sur la façon dont l'internet actuel fonctionne. Avec toutes ces informations, le bon choix à faire deviendra très clair !

II. TCP/IP

TCP signifie « transmission control protocol » et IP signifie « internet protocol ». Ensemble, ils forment l'épine dorsale de presque tout ce que vous faites en ligne, de la navigation web à IRC en passant par les courriels, tout est fait sur une base TCP/IP.

Si vous avez déjà utilisé un socket TCP, alors vous savez qu'il s'agit d'un protocole de connexion fiable. Ça signifie simplement que vous pouvez effectuer une connexion entre deux machines, puis envoyer des données entre les deux ordinateurs un peu comme si vous écriviez dans un fichier d'un côté et que vous lisiez dans un fichier de l'autre.

Cette connexion est fiable et ordonnée, ce qui signifie que toutes les données que vous envoyez sont garanties d'arriver sur la machine distante dans l'ordre dans lequel vous les avez envoyées. De plus, c'est un flux de données : TCP prend soin de découper vos données en paquets et de les envoyer à travers le réseau pour vous.

Souvenez-vous juste que c'est comme écrire dans un fichier. C'est simple !

III. IP

La simplicité d'utilisation est à l'opposé de ce qui se passe au niveau inférieur du protocole sous TCP, sur la couche « IP ».

À ce niveau, il n'y a aucune notion de connexion, il n'y a que des paquets passés d'un ordinateur à un autre. Vous pouvez visualiser ce processus comme une note écrite à la main, passée d'une personne à une autre, à travers une salle pleine de monde, pour finalement atteindre la personne voulue, mais seulement après avoir traversé de nombreuses mains.

Il n'y a aucune garantie que la note atteigne la personne souhaitée. L'expéditeur ne fait que passer la note et espérer que ça marche, ne sachant jamais si la note a été reçue ou non, à moins que le destinataire décide de répondre !

Bien sûr, c'est en réalité un peu plus compliqué que ça, car aucun ordinateur ne connait la liste des machines qui doivent faire passer le paquet pour atteindre son destinataire rapidement. Parfois « IP » transmet plusieurs copies du même paquet, à travers plusieurs chemins différents, qui arriveront sûrement à des moments différents.

C'est parce que l'internet est conçu pour s'auto-organiser et s'autoréparer, capable de contourner les problèmes de connectivité. C'est en fait plutôt cool si vous pensez à ce qui se passe vraiment à plus bas niveau. Vous pouvez lire à ce sujet dans le classique livre TCP/IP illustré.

IV. UDP

 

Et si, au lieu de traiter les communications entre ordinateurs comme une écriture de fichiers nous voulions directement envoyer et recevoir des paquets ?

Nous pouvons en utilisant UDP. UDP signifie « user datagram protocol » et est un autre protocole basé sur IP, tout comme TCP, mais cette fois au lieu d'ajouter des fonctionnalités et de la complexité, il s'agit d'une très fine surcouche d'IP.

Avec UDP nous pouvons envoyer des paquets à un destinataire en connaissant son adresse IP (par exemple : 112.140.20.10) et un port (disons 52432), et ils seront passés d'un ordinateur à un autre jusqu'à ce qu'ils arrivent à destination, ou qu'ils soient perdus en cours de route.

Du côté du récepteur, nous attendons juste en écoutant sur un port spécifique (par exemple 52432) et quand un paquet arrive de n'importe quel ordinateur (souvenez-vous qu'il n'y a pas de connexion !), nous sommes informés de l'adresse et du port de l'expéditeur, la taille du paquet, et nous pouvons lire ses données.

UDP est un protocole non fiable. En pratique, la plupart des paquets envoyés arriveront à destination, mais vous aurez en général environ 1-5 % de paquets perdus, et par moment des périodes où aucun paquet n'arrivera (souvenez-vous qu'il y a beaucoup d'ordinateurs entre vous et votre destinataire, et autant de sources d'erreurs…)

Il n'y a aucune garantie de l'ordre des paquets. Vous pourriez envoyer cinq paquets ordonnés 1,2,3,4,5 et ils pourraient arriver dans le désordre, comme 3,1,2,5,4. En pratique, ils arriveront dans l'ordre quasiment tout le temps, mais là encore, vous ne pouvez pas compter dessus !

Enfin, bien qu'UDP ne soit qu'une mince surcouche à IP, il procure une garantie. Si vous envoyez un paquet, il arrivera entièrement à destination, ou pas du tout. Donc si vous envoyez un paquet de 256 bits à un autre ordinateur, cet ordinateur ne peut pas recevoir que les 100 premiers bits du paquet, il doit recevoir les 256 bits de données. C'est à peu près la seule garantie que vous aurez avec UDP, le reste dépend de vous !

V. TCP vs UDP

 

Nous devons maintenant prendre une décision, va-t-on utiliser des sockets TCP ou UDP ?

Regardons les propriétés de chacun :

  • TCP

    • basé sur une connexion (mode connecté),
    • fiabilité et ordre garantis,
    • découpe automatiquement vos données en paquets pour vous,
    • s'assure de ne pas envoyer de données trop rapidement sur la connexion internet (contrôle de débit),
    • facile à utiliser, vous écrivez et lisez les données comme dans un fichier ;
  • UDP

    • pas de notion de connexion (mode déconnecté),
    • aucune garantie de fiabilité et d'ordre, ils peuvent arriver dans le désordre, arriver en plusieurs exemplaires, ou ne pas arriver du tout,
    • vous devez manuellement découper vos données en paquets et les envoyer,
    • vous devez vous assurer de ne pas envoyer de données plus rapidement que votre connexion ne le permet,
    • si un paquet est perdu, vous devez concevoir un moyen de le détecter et le renvoyer si nécessaire.

La décision semble plutôt claire. TCP fait tout ce que nous voulons et est très simple à utiliser, alors qu'UDP est d'une grande pénibilité et nous devons tout coder nous-mêmes à partir de rien. Donc évidemment que nous utilisons TCP, n'est-ce pas ?

Erreur.

Utiliser TCP est la plus grosse erreur que vous pourriez faire pour développer un jeu d'action comme un FPS ! Pour comprendre pourquoi, vous devez voir ce que TCP fait par-dessus IP pour que tout semble aussi simple !

VI. Comment TCP fonctionne réellement

 

TCP et UDP sont tous deux basés sur IP, mais ils sont radicalement différents. UDP a un comportement très proche d'IP, tandis que TCP fait abstraction pour vous de tout pour donner l'impression de lire et écrire dans un fichier, cachant toute la complexité des paquets et de la non-fiabilité.

Alors comment fait-il ça ?

Premièrement, TCP est un protocole de flux, donc vous écrivez juste des bits dans un flux, et TCP s'assure qu'ils arrivent de l'autre côté. Comme IP fonctionne par paquets, et que TCP est une surcouche d'IP, TCP doit avant tout découper votre flux en paquets de données. Ainsi, du code interne à TCP établit une file de données à envoyer, et quand il y a assez de données en attente, les envoie à l'autre machine.

Ceci peut être un problème dans un jeu multijoueur, si vous envoyez de petits paquets. Que se passe-t-il si TCP décide qu'il n'y a pas assez de données à envoyer tant que vous n'en avez pas mis suffisamment en file pour qu'il puisse faire un paquet de taille raisonnable (disons plus que 100 bits). C'est un problème parce que vous voulez que les entrées du joueur parviennent au serveur le plus rapidement possible, si c'est retardé ou « aggloméré jusqu'à » pour que TCP puisse faire de petits paquets, l'expérience utilisateur sera très mauvaise. Les mises à jour réseau du jeu arriveront tardivement, non régulièrement, au lieu de la mise à jour régulière et rapide que nous voulons.

TCP a une option que l'on peut utiliser pour régler ce comportement appelée TCP_NODELAY. Cette option indique à TCP de ne pas attendre qu'une certaine quantité de données soit en attente, mais d'envoyer immédiatement les données mises en file. Ceci est typiquement appelé la désactivation de l'algorithme de Nagle.

Malheureusement, même en utilisant cette option, TCP a toujours de sérieux problèmes pour un jeu multijoueur.

Tout provient de la façon dont TCP gère les paquets perdus et l'ordre, pour vous donner l'illusion de la fiabilité et d'un flux de données ordonné.

VII. Comment TCP implémente la fiabilité

 

Fondamentalement, TCP découpe un flux de données en paquets, envoie ces paquets à travers IP non fiable, puis récupère les paquets de l'autre côté et reconstruit le flux.

Mais que se passe-t-il si un paquet est perdu ? Si des paquets arrivent dans le désordre ou dupliqués ?

Sans trop plonger dans les détails du fonctionnement de TCP, parce que c'est très compliqué (s'il vous plait, référez-vous à TCP/IP illustré), dans les faits TCP envoie un paquet, attend un moment jusqu'à détecter que ce paquet a été perdu parce qu'il ne reçoit aucun accusé de réception, puis renvoie le paquet perdu à la machine distante. Les duplicatas sont jetés à la réception, et les paquets sont réordonnés pour que tout soit fiable et dans l'ordre.

Le problème est que si nous comptons synchroniser notre jeu via TCP, dès qu'un paquet est perdu, on arrête et attend que la donnée soit renvoyée. Oui, même si d'autres données arrivent, elles sont mises en file et inaccessibles tant que le paquet perdu n'a pas été renvoyé. Combien de temps il faut pour renvoyer un paquet ? Eh bien, au moins un temps de parcours (RTT - Round Trip Time, le temps entre l'envoi d'une machine et la réception du destinataire) pour déclarer un paquet perdu, mais généralement ça prend deux RTT, et un autre temps de parcours pour le renvoyer effectivement. Donc si vous avez 125 ms de ping, vous attendrez environ 1/5 s pour qu'un paquet soit renvoyé dans le meilleur des cas, et dans le pire des scénarios vous pourriez attendre jusqu'à 1/2 s ou plus (imaginez ce qu'il se passe si la tentative de renvoi finit aussi par un paquet perdu !). Que se passe-t-il si TCP décide qu'une perte de paquet indique une congestion du réseau et fait machine arrière ? Oui, il fait ça. Des moments de plaisir !

VIII. Pourquoi ne jamais utiliser TCP pour les données critiques

 

Le problème de TCP pour les jeux en temps réel comme les FPS est que, contrairement à un navigateur web, ou un courriel ou la plupart des applications, ces jeux nécessitent l'envoi de paquets en temps réel. Pour la plupart des parties de votre jeu, par exemple l'action d'un joueur ou la position des personnages, ce qui s'est passé la seconde d'avant n'est pas très probant, vous ne vous souciez que des données les plus récentes. TCP n'a tout simplement pas été créé à cet effet.

Imaginez un simple exemple de jeu multijoueur, un jeu d'action comme un shooter. Vous voulez le mettre en réseau de manière très simple. À chaque frame, vous envoyez les entrées du client au serveur (par exemple : les touches appuyées, les données de la souris, de la manette), et à chaque frame, le serveur traite les entrées de chaque joueur, met à jour la simulation, puis envoie la position actuelle des éléments de jeu au client pour qu'il les affiche.

Donc dans notre simple jeu multijoueur, dès qu'un paquet est perdu, tout serait arrêté dans l'attente de son renvoi. Chez le client, les mises à jour ne sont plus reçues, donc les éléments du jeu paraitront fixes, et sur le serveur les entrées du joueur ne sont plus reçues, donc il ne pourra plus bouger ou tirer. Quand les paquets arrivent enfin, ils sont périmés et les informations qu'ils contiennent ne vous intéressent même pas ! En plus, il y a des paquets en file qui attendent que le renvoi soit fini et arrivent en même temps, donc vous devez traiter tous ces paquets dans la même frame. Tout est aggloméré !

Hélas, il n'y a rien que vous puissiez faire pour arranger ce comportement de TCP, même si vous le vouliez, il s'agit juste de la nature fondamentale de TCP ! C'est juste ce qu'il faut pour qu'un protocole IP non fiable, basé sur de paquets, ressemble à un flux fiable et ordonné.

Le fait est que nous ne voulons pas d'un flux fiable ordonné.

Nous voulons que nos données arrivent aussi vite que possible d'un client au serveur sans avoir à attendre que les paquets perdus soient renvoyés.

C'est pour quoi vous ne devriez jamais utiliser TCP pour les données réseaux critiques dans le temps.

IX. Pourquoi ne pas utiliser à la fois UDP et TCP ?

Pour les données temps réel comme les entrées et l'état du joueur, seules les données les plus récentes sont pertinentes, mais pour les autres types de données, par exemple une séquence de commandes envoyées d'une machine à une autre, la fiabilité et l'ordre peuvent être très importants.

La tentation est alors d'utiliser UDP pour les entrées et l'état du joueur, et TCP pour les données fiables et ordonnées. Si vous êtes pointus, vous avez probablement même déjà travaillé à avoir plusieurs « flux » de commandes fiables et ordonnées, peut-être une pour le chargement du niveau, et un autre pour l'IA. Peut-être, pensez-vous, « Bon, je ne voudrais pas que les commandes de l'IA soient bloquées si un paquet contenant les commandes de chargement du niveau est perdu - ils n'ont rien à voir ! ». Vous avez raison, donc vous pourriez être tenté de créer un socket TCP pour chaque flux de commandes.

En surface, ça semble une bonne idée. Le problème est que comme TCP et UDP sont tous deux basés sur IP, chaque paquet envoyé par l'un ou l'autre des protocoles affectera l'autre. La manière exacte dont chacun impacte l'autre est plutôt compliquée et se rapporte à comment TCP réalise la fiabilité et le contrôle de flux, mais vous devriez vous souvenir essentiellement que TCP tend à induire une perte de paquets dans les paquets UDP. Pour plus d'information, lisez ce papier sur le sujet.

Aussi, il est plutôt compliqué de mélanger UDP et TCP. Si vous mélangez UDP et TCP, vous perdez un peu de contrôle. Peut-être que vous pouvez implémenter la fiabilité de manière plus efficace que ce que fait TCP, plus adapté à vos besoins ? Même si vous avez besoin de données fiables et ordonnées, il est possible, à condition que les données soient faibles par rapport à la bande passante nécessaire, d'envoyer ces données plus vite et de manière plus sûre que si vous les aviez envoyées via TCP. En plus, si vous devez faire du NAT pour mettre en relation les joueurs entre eux (NAT Traversal), devoir faire cette opération pour UDP et pour TCP (même pas sûr que ce soit possible…) est vraiment pénible.

X. Conclusion

 

Ma recommandation n'est donc pas seulement d'utiliser UDP, mais que pour votre protocole de jeu vous n'utilisiez qu'UDP. Ne mélangez pas TCP et UDP, apprenez plutôt comment implémenter les parties spécifiques de TCP qui vous intéressent dans votre propre protocole basé sur UDP.

Bien sûr, ce n'est pas problématique d'utiliser HTTP pour communiquer avec un service REST tant que votre jeu tourne - ce n'est pas ce que j'ai voulu dire. Une paire de connexions TCP en parallèle de votre jeu ne va pas tout faire s'effondrer. Le principe est de ne pas découper votre protocole de jeu entre TCP et UDP. Gardez votre protocole basé sur UDP pour avoir un contrôle complet des données que vous envoyez et recevez, et comment la fiabilité, l'ordonnancement et la congestion sont implémentés et gérés.

Les articles suivants de la série vous montreront comment y parvenir, en créant votre propre mode connecté virtuel basé sur UDP, puis votre propre système de fiabilité, gestion de flux et de congestion.

XI. Remerciements

Cet article est une traduction autorisée de l'article de Glenn Fiedler.

Merci aussi à ClaudeLELOUP pour sa relecture orthographique.

Vous avez aimé ce tutoriel ? Alors partagez-le en cliquant sur les boutons suivants : Viadeo Twitter Facebook Share on Google+   

  

Les sources présentées sur cette page sont libres de droits et vous pouvez les utiliser à votre convenance. Par contre, la page de présentation constitue une œuvre intellectuelle protégée par les droits d'auteur. Copyright © 2015 Glenn Fiedler. Aucune reproduction, même partielle, ne peut être faite de ce site et de l'ensemble de son contenu : textes, documents, images, etc. sans l'autorisation expresse de l'auteur. Sinon vous encourez selon la loi jusqu'à trois ans de prison et jusqu'à 300 000 € de dommages et intérêts.