Que choisir entre une tortue fiable et un lièvre peu fiable ?

Sommaire


Le réseau dans les jeux vidéos

Dans le précédent article, nous avons fait un rapide tour d'horizon du réseau Internet et nous avons vu que celui-ci est de nature peu fiable pour beaucoup de raisons.

Nous avons aussi vu que les protocoles TCP et UDP étaient les deux choix que nous avions à disposition lorsque nous voulions faire communiquer deux applications sur internet.

Bien sûr, il ne s'agit pas des seuls protocoles existants, notamment au même niveau qu'TCP et UDP (on parlera de protocole de transport) nous retrouvons le protocole ICMP, un protocole servant au fonctionnement même du réseau (gérant notamment le ping).
Et bien sûr au-dessus nous trouvons une ribambelle de protocoles de plus haut niveau (on parlera de protocole d'application), certains implémentés au-dessus de TCP (FTP, HTTP, SSH, ...), d'autres au-dessus d'UDP (DNS, etc.).

Néanmoins dans le domaine du jeu vidéo il existe peu de protocoles d'applications tout prêts, dû notamment à la diversité des besoins, et il arrive bien souvent que des jeux implémentent leurs propres protocoles par-dessus les protocoles de transport existants.

Mais alors, que choisir entre TCP et UDP dans ce cas ?
Pour commencer, reprenons donc les caractéristiques de ces deux protocoles.

TCP :

  • Une connexion doit préalablement être établie entre une socket client et une socket serveur.[1]
  • Chaque paquet envoyé est reçu par le destinataire, ce dernier validant la réception de chaque paquet (provoquant une réémission du paquet perdu si la validation n'est pas reçue par l'émetteur).
  • Chaque paquet envoyé est reçu dans l'ordre (en cas de paquet perdu, les paquets suivants ne sont pas traités jusqu'à réception du paquet perdu).
  • Les paquets ne sont pas directement manipulables par l'utilisateur, à la place le protocole expose un flux entrant et un flux sortant pour chaque connexion, pouvant prendre des données de n'importe quelle taille.
  • Gère tout seul la congestion[2] en réduisant le débit en cas de besoin.

UDP :

  • N'a aucune notion de connexion, n'importe qui peut envoyer des paquets à n'importe qui.
  • Chaque paquet envoyé peut être reçu plusieurs fois, dans un ordre différent, ou même ne pas être reçu du tout.
  • Les paquets envoyés sont limités à la taille du MTU[3] de la route.
  • Aucune gestion de la congestion[2:1].

À ce stade, on pourrait se demander qui serait assez fou pour ne pas utiliser TCP, un protocole s'assurant que tous nos paquets sont bien reçus dans le bon ordre.
Après tout, si on envoie un paquet c'est bien pour qu'il soit reçu, l'utilisation d'UDP doit être marginale, non ?

Et pourtant il existe une catégorie d'applications qui bénéficie largement du fonctionnement d'UDP, ce sont les applications à communication temps-réel, comme les applications de communication vocale, de streaming audio/vidéo et justement les jeux vidéos.

Alors non, ce n'est pas parce qu'UDP est plus rapide que TCP (c'est une légende urbaine, les deux ont la même vitesse[4]), mais parce que TCP respecte des contraintes d'ordre et de réception qui ne sont pas toujours souhaitables.
Imaginons que vous appeliez votre grand-mère geek en VoIP[5], tout se passe bien jusqu'à ce qu'il y ait - comme souvent sur internet - un pic de perte de paquet, ou de latence, retardant certains paquets de plusieurs secondes (à parfois plusieurs dizaines de secondes).

Si vous utilisiez TCP, vous auriez un blanc le temps que les paquets soient tous retransmis et vous entendriez ensuite votre grand-mère en accéléré (renvoi de tous les paquets perdus) jusqu'à revenir à une situation temps réel. Ce n'est généralement pas ce que vous souhaitez.
Avec UDP, vous auriez un blanc pour les paquets perdus et le retard s'entendrait un peu, mais lorsque la situation reviendra à la normale, ce sera instantané. Autrement dit la panne sera mitigée beaucoup plus vite, voire pourra même passer inaperçue, les paquets perdus n'ont pas besoin d'être retransmis : ce qui nous intéresse est la faible latence pour le traitement.

Dans un jeu vidéo multijoueur c'est souvent la même chose : ce qui vous importe le plus c'est de connaître la position la plus récente de votre ennemi, pas de connaître toutes ses positions intermédiaires pour arriver jusque là[6].
Il est facile d'utiliser UDP dans ce scénario : il suffira donc d'ajouter un compteur incrémental à chaque paquet UDP pour nous permettre de gérer les retards, duplications ainsi qu'une somme de contrôle un peu plus robuste et c'est bon !

Le type de jeu

Pour véritablement se décider sur TCP et UDP, il faut d'abord parler du type de jeu avant tout. Nous avons vu qu'UDP était un candidat de choix pour les jeux nécessitant une réplication des plus rapides et c'est le cas typiquement pour les shooters. Mais bien des jeux n'ont pas de tels besoins.

Si vous faites un jeu de carte, un jeu tour par tour ou autre jeu où la latence importe peu, TCP fera le travail remarquablement bien. C'est le cas de jeux comme Hearthstone (jeu de carte), ou Worms (jeu tour par tour).

Pour les autres jeux vidéos, souvent plus nerveux (comme par exemple Quake/Unreal Tournament) on se retrouve sans surprise avec le protocole UDP (comme pour tous les jeux utilisant l'Unreal Engine, Counter-Strike, Fortnite, League of Legends, Team Fortress, Overwatch, etc.).

Étude d'une exception

Pour être le plus complet possible, je dois également vous parler des exceptions, par exemple Diablo 2 utilisait exclusivement TCP, et exemple plus imposant encore : World of Warcraft se base exclusivement sur TCP.

Mais WoW est un jeu nerveux non ? On est en droit de se poser la question, comment ça peut marcher si le protocole TCP est si inadapté au temps réel ?

Déjà, et je m'excuse auprès des joueurs de ce jeu, WoW n'est pas aussi exigeant en terme de réflexe/latence qu'un FPS classique : l'extrême majorité du temps les cibles sont soit verrouillées, soit des coordonnées spatiales, et aucun des deux n'est à quelques dizaines de millisecondes près.
Ensuite, les collisions du jeu se font avec des éléments statiques, donc prévisibles par le client et vérifiables par le serveur sans problème avec la latence.

Il y a aussi que dans ce jeu le client est responsable de sa position, dont le serveur vérifie ensuite la validité (on en reparlera dans un prochain chapitre).

Et pour finir, rappelons que TCP se comporte très bien lorsque les conditions réseaux sont acceptables mais déchante rapidement lorsque les conditions sont plus difficiles, bon nombre de joueurs pourront témoigner que WoW ne résiste pas très bien à un réseau instable.

Voici quelques liens pour approfondir la question :
https://1024monkeys.wordpress.com/2014/04/01/game-servers-udp-vs-tcp

https://gamedev.stackexchange.com/questions/431/is-the-tcp-protocol-good-enough-for-real-time-multiplayer-games

Mais au fait, pourquoi faut-il choisir ?

À partir d'ici, vous êtes en droit de vous poser cette question. Après tout, nous avons deux protocoles, un qui gère correctement les messages devant absolument être reçus (ex: message du chat) et un qui gère correctement les messages dont la perte est acceptable (ex: position d'un ennemi), pourquoi ne pas les combiner pour obtenir le meilleur des deux ?

Le raisonnement n'est pas idiot, mais c'est malheureusement une mauvaise idée en général. La raison à cela est que TCP et UDP sont tous les deux des surcouches à IP et qu'ils peuvent interférer l'un avec l'autre.

En effet lorsque vous envoyez beaucoup de données à un destinataire, TCP va tenter d'utiliser le plus de bande passante possible, et les routeurs vont avoir tendance à dropper les paquets UDP au profit des paquets TCP. Une connexion TCP active aura tendance à accroitre la perte de paquets UDP.
Imaginez un ennemi se téléporter à chaque message envoyé dans le chat par exemple, ce n'est pas très agréable.

Bien sûr, cela n'est véritablement gênant que sur une même route et avec du trafic suffisant, n'ayez donc aucun remord à faire appel par exemple à une API HTTPS, même en plein jeu.

Pour plus de détails sur la façon dont TCP interfère avec UDP :
https://web.archive.org/web/20160103125117/https://www.isoc.org/inet97/proceedings/F3/F3_1.HTM

SCTP, le sauveur oublié

SCTP (pour Stream Control Transission Protocol) est un protocol de transport peu connu et une alternative à TCP et UDP, conçu pour fournir le meilleur des deux mondes.

En effet, comme TCP il fournit un mécanisme de connexion (plus robuste même[7]) et des mécanismes gérant la congestion, la réémission des paquets perdus ainsi que l'ordonnancement de ceux-ci et un checksum sur 32bits (suffisant pour gérer la corruption). Mais comme UDP il expose directement la notion de paquet et vous permet de sélectionner la fiabilité désirée indépendamment de ceux-ci.

Enfin, il ajoute aussi la notion de canaux, une notion qu'on abordera dans le prochain chapitre et qui vous permet de mieux organiser et réguler les données qui transitent dans votre application.

Un protocole possédant les avantages de TCP et UDP sans leurs inconvénients, le candidat parfait pour le jeu vidéo non ?

Malheureusement ce protocole est extrêmement peu utilisé/supporté sur Internet, bien que très utilisé en téléphonie. Les raisons à cela sont sa relative nouveauté (il a été défini en l'an 2000) et l'inertie du matériel et des logiciels liés au réseau. Pour comparer dites-vous qu'IPv6 est plus ancien encore et n'est pas encore majoritaire par rapport à IPv4[8].

De plus, les deux systèmes d'exploitation majoritaires du public visé (joueurs), Windows et macOS, n'implémentent pas de support pour ce protocole et il faut alors installer des drivers tiers pour l'ajouter, inutile de vous dire que vous risquez de perdre une proportion importante d'utilisateurs en imposant ça.

Personnellement je n'ai jamais utilisé ce protocole et j'ignorais même son existence avant de faire des recherches pour écrire cet article, sachez donc qu'il est possible d'implémenter SCTP par-dessus UDP, et pour plus d'informations je vous redirige vers la page wikipédia.

Conclusion

Après ce chapitre plutôt long vous devriez avoir une idée des capacités et utilisations des deux protocoles de transport majoritaires sur Internet.
Nous avons vu que réaliser un jeu en TCP est tout à fait possible tant que le style s'y prête, mais que pour un jeu à temps réel il vaut mieux utiliser UDP.

Cependant, UDP est peu pratique à utiliser, pas de notion de connexion, pas de fiabilité, etc.

Nous allons voir dans le prochain chapitre les solutions qui s'offrent à nous pour utiliser UDP de façon fiable.


  1. Cela est fait à l'aide de l'émission de paquets de connexion, le client envoie un paquet au serveur, celui-ci lui répond et le client renvoie un dernier paquet validant la réception du paquet du serveur, laissant les deux entités savoir que la connexion fonctionne dans les deux sens (plus d'info: https://en.wikipedia.org/wiki/Handshaking#TCP_three-way_handshake). ↩︎

  2. La congestion du réseau est le moment où vous dépassez la bande-passante de la route entre votre ordinateur et celui que vous contactez, c'est-à-dire le moment où vous essayez d'envoyer plus de données que vous ne pouvez. ↩︎ ↩︎

  3. MTU pour Maximum Transmission Unit, la taille de paquet maximum que peuvent prendre les routiers qui font transiter les paquets, à noter qu'en IPv4 les routeurs sont autorisés à fragmenter les paquets trop gros, augmentant le risque de problèmes. ↩︎

  4. Dans le cas où le réseau se comporte normalement. ↩︎

  5. Voice Over IP, un terme de hipster pour dire "communication verbale transmise via le réseau internet plutôt que le réseau GSM". ↩︎

  6. Bien sûr, on part ici du principe que le serveur envoie continuellement les positions des ennemis, ce qui est très souvent vrai. ↩︎

  7. Dû à l'utilisation d'un 4-way handshake, résistant au SYN flood et d'un large cookie de connexion. ↩︎

  8. ~27% d'utilisation à l'heure où j'écris ces lignes, avec le pays des boulets liégeois en tête à 60% d'utilisation dans le pays ! ↩︎