Slither.io : Chapitre 3.3

 

Connexion au(x) serveur(s)

 

Les WebSockets

Avant de commencer ce chapitre, un petit rappel sur les websockets. Le mode d'échange classique entre le navigateur et le serveur web est un mode déconnecté, c'est le protocole HTTP qui veut çà. Le navigateur crée une connexion TCP avec le serveur, envoie une requête (un peu comme un mail) au serveur, et attend la réponse. Ensuite la connexion est coupée. Ce mode de fonctionnement ainsi que le protocole HTTP (en-têtes, ...) ne sont pas adaptés à une application interactive type chat ou jeu en ligne car les connexions doivent être persistantes, rapides et les formats d'échanges doivent pouvoir être configurés à volonté, par exemple pour échanger des données binaires ou compactées.

Avec le standard HTML5 est apparu le concept de WebSocket. Le terme WebSocket est la concaténation ... vous ne l'auriez jamais deviné ... des termes Web et Socket (connecteur réseau). Une WebSocket permet d’ouvrir une connexion bi-directionnelle permanente entre un client et un serveur. Les WebSockets autorisent ainsi le développement de véritables applications temps-réel performantes (chat, jeux en ligne…).


Côté navigateur çà se traduit en une API Javascript qui permet de lancer une connexion vers un serveur, d'échanger des données et de créer des évènements relatifs à la réception de données ou à la perte de connexion.

Au chargement

1ere Etape:

Au chargement du  jeu ... enfin de la page du jeu slither.io, le code JS télécharge le fichier i33628.txt. C'est un fichier qui comporte des caractères ASCII sans aucune signification au premier abord. Ce fichier est en fait un fichier de configuration qui comporte les adresses IP des serveurs du jeu, non pas les serveurs hébergeant la page web du jeu mais les serveurs permettant d'héberger les différentes sessions de jeu. En effet pour répartir la charge mais aussi mieux désservir chaque zone géographique, les éditeurs de slither.io ont loué un certain nombre de serveurs de par le monde.

Revenons au fichier "i33628.txt", ce fichier est décodé par la fonction loadSos() qui en extrait des informations structurées et renseigne les variables (de type objets) sos[ ] et clus[ ]. Clus signifie cluster, car les serveurs sont regroupés par clusters ou groupes de 10 à 20 serveurs qui fonctionnent ensembles. La variable clus regroupe de 10 à 12 clusters soit en tout de 100 à 200 serveurs.

Chargement après chargement, les @IP des serveurs contenues dans le fichier i33628.txt changent un peu, mais on retrouve toujours à peu près les mêmes. Pour en arriver à ces conclusions, j'ai construit une liste des @IP :
for (i=0;i<clus.length;i++) {
  if (typeof(clus[i]) !== 'undefined') {
    for (j=0;j<clus[i].sis.length;j++) {
      console.log(clus[i].sis[j].ip)
      }
    }
  }
J'ai concaténé ces @IP au fil des chargements du jeu, et au final en analysant la liste obtenue, on a environ 170 @IP différentes. C'est un ordre de grandeur, je n'ai pas trouvé utile de pousser l'analyse pour arriver à un chiffre exact, d'ailleurs le nombre de serveurs peut très bien évoluer avec le temps en fonction de la charge.

Slither.io réserve donc on va dire à peu près 200 @IP de part le monde. Une géolocalisation des ces @IP montre qu'il y en a un peu partout :
- Allemagne, suisse, France
- USA, Bresil
- Australie
- ...
Ceci permet de mieux desservir chaque zone géographique et assurer à chaque utilisateur de pouvoir joindre un serveur pas trop loin afin d'avoir une bonne réactivité.

2eme Etape:

Une fois les @IP renseignées dans les tableaux sos et clus, l'application va tester le temps de réponse moyen de chaque cluster (ensemble de serveurs). L'application prend un serveur au hasard par cluster , ouvre une WebSocket et teste la connectivité et le temps de réponse :

// création d'une WebSocket par cluster  
e = new WebSocket('ws://' + b + ':80/ptc')
...

// à l'ouverture de la socket envoi d'un message puis fermeture immédiate
e.onopen = function (b) { ... b || this.close() ...}

// sur évènement de réception de message calcul du "ping time" 
e.onmessage = function (b) {
...
console.log(' ping time for cluster ' + b + ': ' + e);
c.ptms.push(e);
...
}



La connexion à la WebSocket est ensuite refermée. Ces tests sont visibles dans l'onglet réseau de l'outil de développement Firefox, en filtrant les échanges de type WS (websocket) :












3eme Etape:

Quand le joueur clique sur le bouton jouer,le code JS lance la fonction "connect()". Cette fonction récupère la liste des @IP du tableau des clusters "clus" et il y a un certain classement assez peu clair mais qui revient à classer les clusters par délai moyen de ping. Le classement des clusters ne changera pas, tant qu'on ne recharge pas la page le joueur restera dans le même cluster, donc sur une liste de 10 à 20 serveurs.
Suite à quelques tests je peux affirmer que chaque serveur héberge une partie donnée (une liste de joueurs, comme une chambre de chat). Ceci explique que lorsqu'on meure et qu'on recommence une partie, on ne retombe pas sur la même partie, cependant au fil du temps, après avoir recommencé plusieurs fois, on peut retomber sur la même partie.
Vous aurez compris qu'on peut assez facilement "hacker" le jeu sur cet aspect là et par exemple rechercher le meilleur serveur (ping le plus faible) et se scotcher sur ce serveur afin de redémarrer à chaque fois sur la même partie. Ceci nécessitera de modifier le tableau "clus" après chargement et avant de lancer le jeu.

Dernière Etape:

Une fois la demande de connexion websocket effectuée, le client attend l'évènement "ws.onopen" qui est levé dès établissement du lien. A réception de cet évènement le client web renvoie au serveur le pseudo du joueur, le numéro de la "skin"/peau utilisée et quelques bits de signature, comme on peut le constater dans l'onglet "websockets" de l'outil de développement Firefox.


Et le jeu peut commencer !!





Aucun commentaire :

Enregistrer un commentaire