Comment communiquer de façon fiable en utilisant un protocole non-fiable ?

Sommaire


Le réseau dans les jeux vidéos

Dans le chapitre précédent nous avons étudiés les deux protocoles de transport majeurs les plus répandus sur Internet, à savoir TCP et UDP, ainsi que leurs différentes caractéristiques.

Nous avons vu que TCP pouvait convenir à des jeux en tour par tour ou dont la latence importait peu, mais qu'il valait mieux privilégier UDP pour le temps-réel, c'est-à-dire les jeux dont l'action est suffisamment rapide pour que la latence puisse désavantager certains joueurs. C'est notamment le cas de tous les shooters par exemple.

Néanmoins, UDP n'est pas utilisable out of the box[1] car il n'a aucune notion de connexion ni de fiabilité, il nous faudra donc utiliser quelque chose par-dessus.

Ce qui va nous intéresser ici c'est un protocole applicatif construit par-dessus UDP et nous apportant des garanties supplémentaires, et il en existe toute une ribambelle utilisables à travers diverses bibliothèques.

Comprenez-bien que la liste à venir n'est pas exhaustive et qu'elle est composée de solutions utilisables en C ou en C++ (étant moi-même un développeur C++), il peut être possible de les utiliser dans d'autres langages (via des bindings existants ou le FFI[2]) mais je ne garanti rien.

N'hésitez pas à commenter si vous connaissez une bibliothèque ou un protocole qui aurait sa place sur cette liste, qui par ailleur n'est pas triée selon un ordre particulier.

RakNet

Si vous vous êtes déjà renseigné sur le sujet, vous devez certainement connaître ce nom. RakNet est l'une des bibliothèques les plus ancienne et les plus connues pour le réseau dans le jeu vidéo.

C'est une bibliothèque assez massive qui propose énormément de choses, du chiffrement au RPC[3] en passant par le Hole punching[4] et bien d'autres fonctionnalités.

C'est une solution de qualité professionnelle, utilisée notamment en interne par le moteur de jeu Unity (et bien d'autres) et devenue gratuite (pour le PC et les mobiles) lors du rachat par Oculus, à noter du coup que le support console est payant.

Le développement semble s'être arrêté il y a bientôt quatre ans, en 2015 (date du dernier commit). Néanmoins je suppose fortement que le développement par Oculus a continué de façon propriétaire et qu'avec une licence adéquate vous aurez accès aux dernières mises à jour.

RakNet est écrit en C++ et dispose d'un support C#.

Site officiel :
http://www.jenkinssoftware.com

ENet

Inventée à l'origine pour la série de FPS dont Cube et Cube 2 : Sauerbraten sont les représentants, cette bibliothèque en développement depuis 2002 est simple d'utilisation mais efficace.

ENet fournit une API simple et des fonctionnalités classiques par-dessus UDP comme l'établissement de connexions, la fiabilité optionnelle, le découpage de paquets trop gros en fragments, la compression, les canaux et la limitation de bande passante.
En revanche, elle ne fournit aucun support pour le chiffrement[5] et donc aucune protection contre les attaques de type MITM[6], elle ne fournit non plus aucun support pour l'IPv6 (même si des patches existent, dont un par votre serviteur).

C'est néanmoins une solution assez connue et robuste, notamment à la base du protocole réseau de League of Legends, cependant le développement semble s'être arrêté ces dernières années.

ENet est écrit en C, facile à utiliser et à interfacer dans d'autres langages, elle est notamment disponible de base avec Löve 2D (framework de développement de jeu vidéo en Lua).

Et pour le peu que ça apporte à l'article, sachez qu'ENet est également intégré et utilisable en C++ avec mon moteur de jeu, Nazara.

Site officiel :
http://enet.bespin.org

Yojimbo

En 2016 le génial Glenn Fiedler[7], créateur du protocole netcode.io[8] dédié à l'établissement de connexions chiffrées par-dessus UDP et créateur de la bibliothèque reliable.io dédiée à la fiabilité par-dessus des protocoles non-fiables (comme UDP), décide de créer une bibliothèque tirant parti de ses deux autres projets pour fournir un framework réseau prêt au jeu vidéo.

Yojimbo est donc capable, de la même façon que RakNet et ENet, de gérer des connexions, d'assurer (ou non) la fiabilité des paquets réseaux, d'envoyer des paquets de taille arbitraire et propose un système de canaux.

Cette bibliothèque propose aussi, contrairement à ENet, d'établir des connexions chiffrées en se servant d'un serveur web par-dessus HTTPS pour échanger des clés privées. L'idée derrière étant que le serveur web (souvent une WebAPI) et le(s) serveur(s) de jeu possèdent une clé secrète commune, et que le protocole HTTPS protègera la récupération des clés contre toute tentative d'attaque.

Contrairement à ENet et RakNet cependant, elle ne propose pas de moyen (à ma connaissance) de réguler la bande passante. C'est néanmoins assez rarement un problème dans le domaine du jeu vidéo si vous concevez correctement votre protocole, excepté si vous souhaitez faire transiter des données plus lourdes (comme des assets).

Cette bibliothèque est toujours en développement actif et est utilisée notamment par le très célèbre Star Citizen[9] ainsi que (précédemment) le mod Skyrim Together.

Contrairement à netcode.io et reliable.io qui sont écrits en C et utilisables dans bien d'autres langages, actuellement le support de yojimbo se limite au C++, langage dans lequel il est écrit. Libre à vous cependant d'y coller votre abstraction pour utiliser un autre langage.

GitHub officiel :
https://github.com/networkprotocol/yojimbo

SCTP

On en a parlé dans le chapitre précédent, SCTP est un protocole de transport (au même titre que TCP et UDP) qui a pour but de proposer un système de connexion ainsi qu'une fiabilité et un réordonnancement optionnels, un système de canaux et de fragmentation des paquets trop gros (configurable).

Il apporte aussi une fonctionnalité inédite : le support du multi-homing (la capacité d'être connecté à plusieurs destinataires pour assurer la fiabilité de la connexion), à ma connaissance ce n'est pas utilisé dans le jeu vidéo mais c'est toujours intéressant d'en parler.

Contrairement à RakNet et Yojimbo il ne propose pas de chiffrement de base mais il est possible de le rajouter par-dessus, mais en revanche, comme RakNet et ENet, il propose un mécanisme de gestion de congestion.

Le principal défaut de ce protocole est d'être faiblement supporté à travers Internet, vous n'avez pas la garantie de pouvoir établir de connexion avec un destinaire (il suffit qu'un élément de la route ne laisse pas passer le SCTP pour être bloqué), et les systèmes d'exploitation ont un support assez faible du protocole, Linux étant l'un des seuls systèmes à proposer un support SCTP sans nécessiter de modification.

Cependant il est possible de régler ces deux problèmes en implémentant SCTP par-dessus UDP, ce qui ne pose aucun problème car ce dernier possède moins de contraintes que SCTP. Et c'est très exactement là qu'intervient usrsctp.

Cette bibliothèque est une implémentation utilisateur du protocole SCTP soit directement par-dessus IP (en utilisant des raw sockets[10]), ce qui néanmoins requiert des permissions spéciales (root sous Linux et administrateur sous Windows) et est toujours exposé à la perte des paquets SCTP incompris par certains éléments de la route. Soit par-dessus UDP, rendant le protocole utilisable et ne nécessitant pas de droit administrateur.

Je ne peux pas vous en dire plus sur ce protocole, je ne l'ai pas encore utilisé mais au vu de ce que j'ai pu lire dessus il semble prometteur, et puis si un jour nous basculons tous dessus, vous pourrez dire "J'y étais".

Implémentation de SCTP par-dessus UDP/IP :
https://github.com/sctplab/usrsctp

Concevoir son propre protocole

La dernière option que je peux vous proposer ici serait de concevoir votre propre protocole, c'est-à-dire votre propre couche de fiabilité par-dessus UDP.

Le gros problème de cette option est sa complexité, faire un protocole se débarrassant des défauts d'UDP est une affaire assez complexe.
Pour que vous compreniez, j'avais commencé à détailler tout ce qu'il fallait prendre en compte pour concevoir son propre protocole par-dessus UDP dans cette sous-section, il y a quelques mois. Et puis quand ça a représenté 75% des mots de cet article et j'ai décidé que le sujet méritait son propre article.
Et puis quand c'est devenu cinq fois plus long que le plus long de mes articles (et que la sortie s'éternisait), j'ai décidé d'en faire une série d'articles, dont le premier (après l'introduction) est le plus long article que j'ai sorti jusqu'ici et pourtant ne traite que des bases.

Tout ça pour dire que concevoir son propre protocole par-dessus UDP est complexe et qu'il y a beaucoup de choses à prendre en compte. Je ne peux que vous encourager à sérieusement considérer les possibilités listées ci-dessus avant toute chose, surtout si vous avez peu de connaissances dans le réseau.

Néanmoins voici donc mon guide sur la conception de son propre protocole réseau par-dessus UDP.

Conclusion

Si vous connaissez une autre bonne bibliothèque/protocole qui mériterait sa place ici, n'hésitez pas à me la soumettre en commentaire.

Maintenant que nous sommes capables de communiquer efficacement entre deux machines par le biais d'internet, nous sommes prêts à aborder la suite qui va traiter des différents moyens à mettre en oeuvre pour communiquer dans un jeu vidéo.


  1. Du premier coup, d'emblée, directement. ↩︎

  2. Foreign Function Interface, en quelques mots c'est de la magie noire permettant d'utiliser une fonction d'un autre langage presque comme si elle était écrite dans le langage actuel. ↩︎

  3. Remote procedure calls, un mécanisme haut-niveau proposé par certains moteurs permettant d'appeler une fonction sur un objet se situant sur un autre ordinateur à travers le réseau, en gérant pour vous tout ce que ça implique. ↩︎

  4. Une technique permettant de bypasser la plupart des NAT sans devoir manuellement ouvrir de port, j'en parle plus en détail dans la série sur la conception de son propre protocole. ↩︎

  5. Bien qu'il soit possible d'en ajouter le support via le code utilisateur et une petite modification du code source (oui, je parle d'expérience :D ). ↩︎

  6. Man-in-the-middle, une attaque consistant à intercepter et éventuellement altérer les paquets transitants entre deux hôtes. ↩︎

  7. Dont je vous invite fortement à consulter le devblog dédié au réseau. ↩︎

  8. Auquel je suis contributeur \o/ ↩︎

  9. Qui, j'en suis conscient, ne brille pas par son utilisation du réseau, c'est néanmoins plus un problème de programmation serveur que de protocole réseau. ↩︎

  10. Les raw sockets permettent d'envoyer et de recevoir un paquet IP directement en contrôlant l'ensemble de ses en-têtes et données, permettant notamment d'implémenter n'importe quel protocole de plus haut-niveau qu'IP dans le code utilisateur. ↩︎