{"id":38370,"date":"2019-10-31T22:23:18","date_gmt":"2019-10-31T19:23:18","guid":{"rendered":"https:\/\/prohoster.info\/blog\/portiruem-mnogopolzovatelskuyu-igru-s-s-na-veb-c-cheerp-webrtc-i-firebase\/"},"modified":"2019-10-31T22:23:18","modified_gmt":"2019-10-31T19:23:18","slug":"portiruem-mnogopolzovatelskuyu-igru-s-s-na-veb-c-cheerp-webrtc-i-firebase","status":"publish","type":"post","link":"https:\/\/prohoster.info\/fr\/blog\/administrirovanie\/portiruem-mnogopolzovatelskuyu-igru-s-s-na-veb-c-cheerp-webrtc-i-firebase","title":{"rendered":"Nous portons un jeu multijoueur de C++ vers le web avec Cheerp, WebRTC et Firebase","gt_translate_keys":[{"key":"rendered","format":"text"}]},"content":{"rendered":"<h2>Introduction<\/h2>\n<p>\nNotre entreprise <noindex><a rel=\"nofollow\" href=\"https:\/\/leaningtech.com\">Leaning Technologies<\/a><\/noindex> fournit des solutions pour porter des applications de bureau traditionnelles vers le web. Notre compilateur C++ <noindex><a rel=\"nofollow\" href=\"https:\/\/github.com\/leaningtech\/cheerp-meta\">Cheerp<\/a><\/noindex> g\u00e9n\u00e8re une combinaison de WebAssembly et de JavaScript, ce qui permet \u00e0 la fois <noindex><a rel=\"nofollow\" href=\"https:\/\/github.com\/leaningtech\/cheerp-meta\/wiki\/Cheerp-Tutorial%3A-Mixed-mode-C++-to-WebAssembly-and-JavaScript\">une interaction simple avec le navigateur<\/a><\/noindex>et de hautes performances.<\/p>\n<p>\u00c0 titre d'exemple, nous avons d\u00e9cid\u00e9 de porter un jeu multijoueur pour le web et avons choisi pour cela <noindex><a rel=\"nofollow\" href=\"https:\/\/www.teeworlds.com\/\"><strong>Teeworlds<\/strong><\/a><\/noindex>. Teeworlds est un jeu r\u00e9tro multijoueur en 2D avec une petite mais active communaut\u00e9 de joueurs (dont je fais partie !). Il est l\u00e9ger tant en ressources \u00e0 t\u00e9l\u00e9charger qu'en exigences CPU et GPU \u2013 un candidat id\u00e9al.<\/p>\n<p><img decoding=\"async\" alt=\"Nous portons un jeu multijoueur de C++ vers le web avec Cheerp, WebRTC et Firebase\" src=\"\/wp-content\/uploads\/2019\/09\/30bff36ee1158ddea2e76d8b11e48d2b.jpg\" style=\"display:block;margin: 0 auto;\" \/><br \/>\n<i>Teeworlds fonctionne dans le navigateur<\/i><br \/>\n<noindex><a rel=\"nofollow\" name=\"habracut\"><\/a><\/noindex><br \/>\nNous avons d\u00e9cid\u00e9 d'utiliser ce projet pour exp\u00e9rimenter avec <strong>des solutions g\u00e9n\u00e9rales pour porter le code r\u00e9seau vers le web.<\/strong>Cela se fait g\u00e9n\u00e9ralement de deux mani\u00e8res :<\/p>\n<ul>\n<li><strong>XMLHttpRequest\/fetch<\/strong>, si la partie r\u00e9seau ne se compose que de requ\u00eates HTTP, ou<\/li>\n<li><strong>WebSockets<\/strong>.<\/li>\n<\/ul>\n<p>\nLes deux solutions n\u00e9cessitent d'h\u00e9berger un composant serveur c\u00f4t\u00e9 serveur, et aucune d'elles ne permet d'utiliser comme protocole de transport <noindex><a rel=\"nofollow\" href=\"https:\/\/en.wikipedia.org\/wiki\/User_Datagram_Protocol\">UDP<\/a><\/noindex>. Cela est important pour les applications en temps r\u00e9el, telles que les logiciels de vid\u00e9oconf\u00e9rence et les jeux, car les garanties de livraison et d'ordre des paquets avec le protocole <noindex><a rel=\"nofollow\" href=\"https:\/\/en.wikipedia.org\/wiki\/Transmission_Control_Protocol\">TCP<\/a><\/noindex> peuvent nuire \u00e0 la latence faible.<\/p>\n<p>Il existe une troisi\u00e8me fa\u00e7on - utiliser le r\u00e9seau depuis le navigateur : <noindex><a rel=\"nofollow\" href=\"https:\/\/developer.mozilla.org\/en-US\/docs\/Glossary\/WebRTC\"><strong>WebRTC<\/strong><\/a><\/noindex>.<\/p>\n<p><noindex><a rel=\"nofollow\" href=\"https:\/\/developer.mozilla.org\/en-US\/docs\/Web\/API\/RTCDataChannel\"><strong>RTCDataChannel<\/strong><\/a><\/noindex> prend en charge \u00e0 la fois des transmissions fiables et non fiables (dans ce dernier cas, il essaie, si possible, d'utiliser le protocole UDP), et peut \u00eatre utilis\u00e9 aussi bien avec un serveur distant qu'entre navigateurs. <strong>Cela signifie que nous pouvons porter toute l'application dans le navigateur, y compris le composant serveur !<\/strong><\/p>\n<p>Cependant, cela pr\u00e9sente une difficult\u00e9 suppl\u00e9mentaire : avant que deux pairs WebRTC puissent \u00e9changer des donn\u00e9es, ils doivent effectuer une proc\u00e9dure de \u00ab poign\u00e9e de main \u00bb relativement complexe pour se connecter, ce qui n\u00e9cessite plusieurs entit\u00e9s externes (serveur de signalisation et un ou plusieurs serveurs <noindex><a rel=\"nofollow\" href=\"https:\/\/en.wikipedia.org\/wiki\/STUN\">STUN<\/a><\/noindex>\/<noindex><a rel=\"nofollow\" href=\"https:\/\/en.wikipedia.org\/wiki\/Traversal_Using_Relays_around_NAT\">TURN<\/a><\/noindex>).<\/p>\n<p>Id\u00e9alement, nous aimerions cr\u00e9er une API r\u00e9seau utilisant WebRTC, mais aussi proche que possible de l'interface des Sockets UDP, qui n'a pas besoin d'\u00e9tablir de connexion.<\/p>\n<p>Cela nous permettra de tirer parti de WebRTC sans avoir \u00e0 divulguer les d\u00e9tails complexes du code de l'application (que nous souhaitions changer le moins possible dans notre projet).<\/p>\n<h1>WebRTC Minimal<\/h1>\n<p>\nWebRTC est un ensemble d'API pr\u00e9sents dans les navigateurs, permettant la transmission de son, de vid\u00e9o et de donn\u00e9es arbitraires en peer-to-peer.<\/p>\n<p>La connexion entre les pairs est \u00e9tablie (m\u00eame en cas de NAT d'un ou des deux c\u00f4t\u00e9s) \u00e0 l'aide des serveurs STUN et\/ou TURN via un m\u00e9canisme appel\u00e9 ICE. Les pairs \u00e9changent des informations ICE et des param\u00e8tres de canaux via l'offre et la r\u00e9ponse du protocole SDP.<\/p>\n<p>Wow ! Beaucoup d'acronymes en m\u00eame temps. Expliquons bri\u00e8vement la signification de ces termes :<\/p>\n<ul>\n<li><noindex><a rel=\"nofollow\" href=\"https:\/\/en.wikipedia.org\/wiki\/STUN\"><strong>Session Traversal Utilities for NAT<\/strong> (<strong>STUN<\/strong>)<\/a><\/noindex> \u2014 un protocole pour contourner le NAT et obtenir une paire (IP, port) pour \u00e9changer des donn\u00e9es directement avec l'h\u00f4te. S'il r\u00e9ussit, les pairs peuvent \u00e9changer des donn\u00e9es entre eux.<\/li>\n<li><noindex><a rel=\"nofollow\" href=\"https:\/\/en.wikipedia.org\/wiki\/Traversal_Using_Relays_around_NAT\"><strong>Traversal Using Relays around NAT<\/strong> (<strong>TURN<\/strong>)<\/a><\/noindex> est \u00e9galement utilis\u00e9 pour contourner le NAT, mais il le fait en redirigeant les donn\u00e9es via un proxy, visible pour les deux pairs. Cela ajoute de la latence et est plus co\u00fbteux \u00e0 ex\u00e9cuter que STUN (car il est appliqu\u00e9 pendant toute la session de communication), mais parfois c'est la seule option possible.<\/li>\n<li><noindex><a rel=\"nofollow\" href=\"https:\/\/en.wikipedia.org\/wiki\/Interactive_Connectivity_Establishment\"><strong>Interactive Connectivity Establishment<\/strong> (<strong>ICE<\/strong>)<\/a><\/noindex> est utilis\u00e9 pour choisir le meilleur moyen possible de connecter deux pairs sur la base des informations obtenues lors de la connexion directe des pairs, ainsi que des informations re\u00e7ues de plusieurs serveurs STUN et TURN.<\/li>\n<li><noindex><a rel=\"nofollow\" href=\"https:\/\/en.wikipedia.org\/wiki\/Session_Description_Protocol\"><strong>Session Description Protocol<\/strong> (<strong>SDP<\/strong>)<\/a><\/noindex> est un format d\u00e9crivant les param\u00e8tres de la connexion, tels que les candidats ICE, les codecs multim\u00e9dias (dans le cas d'un canal audio\/vid\u00e9o), etc. L'un des pairs envoie une offre SDP (\u00ab proposition \u00bb), et l'autre r\u00e9pond par une r\u00e9ponse SDP (\u00ab r\u00e9ponse \u00bb). Apr\u00e8s cela, un canal est cr\u00e9\u00e9.<\/li>\n<\/ul>\n<p>\nPour \u00e9tablir une telle connexion, les pairs doivent rassembler les informations qu'ils ont re\u00e7ues des serveurs STUN et TURN et les \u00e9changer entre eux.<\/p>\n<p>Le probl\u00e8me est qu'ils n'ont pas encore la possibilit\u00e9 d'\u00e9changer des donn\u00e9es directement, donc il doit exister un m\u00e9canisme hors bande pour \u00e9changer ces donn\u00e9es : un serveur de signalisation.<\/p>\n<p>Le serveur de signalisation peut \u00eatre tr\u00e8s simple, car sa seule t\u00e2che est de rediriger les donn\u00e9es entre les pairs lors de l'\u00e9tape de \u00ab poign\u00e9e de main \u00bb (comme indiqu\u00e9 dans le sch\u00e9ma ci-dessous).<\/p>\n<p><img decoding=\"async\" alt=\"Nous portons un jeu multijoueur de C++ vers le web avec Cheerp, WebRTC et Firebase\" src=\"\/wp-content\/uploads\/2019\/09\/0fc39ba5e0942bc19183e8cf06c60868.jpg\" style=\"display:block;margin: 0 auto;\" \/><br \/>\n<i>Sch\u00e9ma simplifi\u00e9 de la s\u00e9quence de \u00ab poign\u00e9e de main \u00bb WebRTC<\/i><\/p>\n<h1>Aper\u00e7u du mod\u00e8le r\u00e9seau Teeworlds<\/h1>\n<p>\nL'architecture r\u00e9seau de Teeworlds est tr\u00e8s simple :<\/p>\n<ul>\n<li>Les composants client et serveur sont deux programmes diff\u00e9rents.<\/li>\n<li>Les clients rejoignent le jeu en se connectant \u00e0 un des plusieurs serveurs, chacun h\u00f4te d'un seul jeu \u00e0 la fois.<\/li>\n<li>Toute la transmission de donn\u00e9es dans le jeu passe par le serveur.<\/li>\n<li>Un serveur ma\u00eetre sp\u00e9cial est utilis\u00e9 pour rassembler la liste de tous les serveurs publics, qui sont affich\u00e9s dans le client de jeu.<\/li>\n<\/ul>\n<p>\nGr\u00e2ce \u00e0 l'utilisation de WebRTC pour l'\u00e9change de donn\u00e9es, nous pouvons d\u00e9placer le composant serveur du jeu dans le navigateur, o\u00f9 se trouve le client. Cela nous offre une excellente opportunit\u00e9\u2026<\/p>\n<h1>De se passer des serveurs<\/h1>\n<p>\nL'absence de logique serveur pr\u00e9sente un avantage int\u00e9ressant : nous pouvons d\u00e9ployer l'ensemble de l'application en tant que contenu statique sur Github Pages ou sur notre propre \u00e9quipement derri\u00e8re Cloudflare, assurant ainsi des chargements rapides et un temps de disponibilit\u00e9 \u00e9lev\u00e9 gratuitement. En gros, on pourra les oublier, et si nous avons de la chance et que le jeu devient populaire, nous n'aurons pas besoin de moderniser l'infrastructure.<\/p>\n<p>Cependant, pour que le syst\u00e8me fonctionne, nous devrons n\u00e9anmoins utiliser une architecture externe :<\/p>\n<ul>\n<li>Un ou plusieurs serveurs STUN : nous avons le choix parmi plusieurs options gratuites.<\/li>\n<li>Au moins un serveur TURN : il n'y a pas d'options gratuites ici, donc nous pouvons soit configurer le n\u00f4tre, soit payer pour un service. Heureusement, la plupart du temps, la connexion pourra \u00eatre \u00e9tablie via des serveurs STUN (et assurer un v\u00e9ritable p2p), mais TURN est n\u00e9cessaire en tant que solution de secours.<\/li>\n<li>Serveur de signalisation : \u00e0 la diff\u00e9rence des deux autres aspects, la signalisation n'est pas normalis\u00e9e. Ce que le serveur de signalisation devra r\u00e9ellement faire d\u00e9pend en partie de l'application. Dans notre cas, un petit volume de donn\u00e9es doit \u00eatre \u00e9chang\u00e9 avant d'\u00e9tablir la connexion.<\/li>\n<li>Serveur ma\u00eetre de Teeworlds : il est utilis\u00e9 par les autres serveurs pour signaler son existence et par les clients pour rechercher des serveurs publics. Bien qu'il ne soit pas obligatoire (les clients peuvent toujours se connecter \u00e0 un serveur qu'ils connaissent manuellement), il serait bon de l'avoir pour que les joueurs puissent participer \u00e0 des jeux avec des inconnus.<\/li>\n<\/ul>\n<p>\nNous avons d\u00e9cid\u00e9 d'utiliser les serveurs STUN gratuits de Google, et nous avons d\u00e9ploy\u00e9 un serveur TURN nous-m\u00eames.<\/p>\n<p>Pour les deux derniers points, nous avons utilis\u00e9 <noindex><a rel=\"nofollow\" href=\"https:\/\/en.wikipedia.org\/wiki\/Firebase\">Firebase<\/a><\/noindex>:<\/p>\n<ul>\n<li>Le serveur ma\u00eetre de Teeworlds est r\u00e9alis\u00e9 tr\u00e8s simplement : sous la forme d'une liste d'objets contenant des informations (nom, IP, carte, mode, \u2026) de chaque serveur actif. Les serveurs publient et mettent \u00e0 jour leur propre objet, et les clients r\u00e9cup\u00e8rent toute la liste et l'affichent au joueur. Nous affichons \u00e9galement la liste sur la page d'accueil sous forme de HTML, afin que les joueurs puissent simplement cliquer sur le serveur et entrer directement dans le jeu.<\/li>\n<li>La signalisation est \u00e9troitement li\u00e9e \u00e0 notre impl\u00e9mentation des sockets, d\u00e9crite dans la section suivante.<\/li>\n<\/ul>\n<p>\n<img decoding=\"async\" alt=\"Nous portons un jeu multijoueur de C++ vers le web avec Cheerp, WebRTC et Firebase\" src=\"\/wp-content\/uploads\/2019\/09\/5e30bc22643f8baecf850053bf69e130.jpg\" style=\"display:block;margin: 0 auto;\" \/><br \/>\n<i>Liste des serveurs dans le jeu et sur la page d'accueil<\/i><\/p>\n<h1>Impl\u00e9mentation des sockets<\/h1>\n<p>\nNous souhaitons cr\u00e9er une API aussi proche que possible des sockets UDP Posix, afin de minimiser le nombre de modifications n\u00e9cessaires.<\/p>\n<p>De plus, nous voulons impl\u00e9menter le strict minimum requis pour un \u00e9change de donn\u00e9es basique sur le r\u00e9seau.<\/p>\n<p>Par exemple, nous n'avons pas besoin de v\u00e9ritable routage : tous les pairs se trouvent dans un \"LAN virtuel\" connect\u00e9 \u00e0 une instance sp\u00e9cifique de la base de donn\u00e9es Firebase.<\/p>\n<p>Par cons\u00e9quent, nous n'avons pas besoin d'adresses IP uniques : pour identifier les pairs de mani\u00e8re unique, il suffit d'utiliser des valeurs cl\u00e9s uniques de Firebase (similaire aux noms de domaine), et chaque pair attribue localement des adresses IP \"fausses\" \u00e0 chaque cl\u00e9 devant \u00eatre convertie. Cela nous lib\u00e8re compl\u00e8tement de la n\u00e9cessit\u00e9 d'allouer des adresses IP globales, ce qui constitue une t\u00e2che non triviale.<\/p>\n<p>Voici l'API minimale que nous devons impl\u00e9menter :<\/p>\n<pre><code class=\"cpp\">\/\/ Create and destroy a socket\nint socket();\nint close(int fd);\n\/\/ Bind a socket to a port, and publish it on Firebase\nint bind(int fd, AddrInfo* addr);\n\/\/ Send a packet. This lazily create a WebRTC connection to the \n\/\/ peer when necessary\nint sendto(int fd, uint8_t* buf, int len, const AddrInfo* addr);\n\/\/ Receive the packets destined to this socket\nint recvfrom(int fd, uint8_t* buf, int len, AddrInfo* addr);\n\/\/ Be notified when new packets arrived\nint recvCallback(Callback cb);\n\/\/ Obtain a local ip address for this peer key\nuint32_t resolve(client::String* key);\n\/\/ Get the peer key for this ip\nString* reverseResolve(uint32_t addr);\n\/\/ Get the local peer key\nString* local_key();\n\/\/ Initialize the library with the given Firebase database and \n\/\/ WebRTc connection options\nvoid init(client::FirebaseConfig* fb, client::RTCConfiguration* ice);<\/code><\/pre>\n<p>\nL'API est simple et ressemble \u00e0 l'API des sockets Posix, mais avec quelques diff\u00e9rences importantes : <strong>enregistrement des rappels, attribution d'IP locales et connexion \"paresseuse\"<\/strong>.<\/p>\n<h2>Enregistrement des rappels<\/h2>\n<p>\nM\u00eame si le programme d'origine utilise des entr\u00e9es\/sorties non bloquantes, le code doit \u00eatre r\u00e9factoris\u00e9 pour fonctionner dans un navigateur web.<\/p>\n<p>La raison en est que la boucle d'\u00e9v\u00e9nements dans le navigateur est cach\u00e9e du programme (qu'il s'agisse de JavaScript ou de WebAssembly).<\/p>\n<p>Dans un environnement natif, nous pouvons \u00e9crire du code de cette mani\u00e8re<\/p>\n<pre><code class=\"cpp\">while(running) {\n  select(...); \/\/ attendre les \u00e9v\u00e9nements I\/O\n  while(true) {\n    int r = readfrom(...); \/\/ essayer de lire\n    if (r &lt; 0 &amp;&amp; errno == EWOULDBLOCK) \/\/ plus de donn\u00e9es disponibles\n      break;\n    ...\n  }\n  ...\n}<\/code><\/pre>\n<p>\nSi la boucle d'\u00e9v\u00e9nements est cach\u00e9e pour nous, nous devons la transformer en quelque chose comme :<\/p>\n<pre><code class=\"cpp\">auto cb = []() { \/\/ ceci sera appel\u00e9 lorsque de nouvelles donn\u00e9es sont disponibles\n  while(true) {\n    int r = readfrom(...); \/\/ essayer de lire\n    if (r &lt; 0 &amp;&amp; errno == EWOULDBLOCK) \/\/ plus de donn\u00e9es disponibles\n      break;\n    ...\n  }\n  ...\n};\nsrecvCallback(cb); \/\/ enregistrer le rappel<\/code><\/pre>\n<p><\/p>\n<h2>Destinataires des IP locaux<\/h2>\n<p>\nLes identifiants des n\u0153uds dans notre \u00ab r\u00e9seau \u00bb ne sont pas des adresses IP, mais des cl\u00e9s Firebase (ce sont des cha\u00eenes qui ressemblent \u00e0 \u00e7a : <code>-LmEC50PYZLCiCP-vqde<\/code> ).<\/p>\n<p>C'est pratique, car nous n'avons pas besoin d'un m\u00e9canisme pour attribuer des IP et v\u00e9rifier leur unicit\u00e9 (ni de les r\u00e9cup\u00e9rer apr\u00e8s la d\u00e9connexion du client), mais il est souvent n\u00e9cessaire d'identifier les pairs par une valeur num\u00e9rique.<\/p>\n<p>C'est pr\u00e9cis\u00e9ment pour cela que nous utilisons les fonctions <code>resolve<\/code> et <code>reverseResolve<\/code>: l'application obtient d'une mani\u00e8re ou d'une autre la valeur de cha\u00eene de la cl\u00e9 (soit par saisie utilisateur, soit via un serveur ma\u00eetre), et peut la convertir en adresse IP pour une utilisation interne. Le reste de l'API re\u00e7oit \u00e9galement cette valeur au lieu de la cha\u00eene pour simplifier les choses.<\/p>\n<p>C'est similaire \u00e0 une recherche DNS, mais cela se fait localement sur le client.<\/p>\n<p>C'est-\u00e0-dire que les adresses IP ne peuvent pas \u00eatre partag\u00e9es entre diff\u00e9rents clients, et si un identifiant global est n\u00e9cessaire, il devra \u00eatre g\u00e9n\u00e9r\u00e9 d'une autre mani\u00e8re.<\/p>\n<h2>Connexion paresseuse<\/h2>\n<p>\nLe UDP n'a pas besoin de connexion, mais, comme nous l'avons vu, avant de commencer \u00e0 transf\u00e9rer des donn\u00e9es entre deux pairs, WebRTC n\u00e9cessite un processus de connexion long.<\/p>\n<p>Si nous voulons garantir le m\u00eame niveau d'abstraction, (<code>sendto<\/code>\/<code>recvfrom<\/code> avec des pairs arbitraires sans connexion pr\u00e9alable), nous devons effectuer une connexion \u00ab paresseuse \u00bb (diff\u00e9r\u00e9e) au sein de l'API.<\/p>\n<p>Voici ce qui se passe lors de l'\u00e9change normal de donn\u00e9es entre un \u00ab serveur \u00bb et un \u00ab client \u00bb dans le cas de l'utilisation du UDP, et ce que notre biblioth\u00e8que doit faire :<\/p>\n<ul>\n<li>Le serveur appelle <code>bind()<\/code>, pour indiquer au syst\u00e8me d'exploitation qu'il souhaite recevoir des paquets sur le port sp\u00e9cifi\u00e9.<\/li>\n<\/ul>\n<p>\nAu lieu de cela, nous publions un port ouvert dans Firebase sous la cl\u00e9 du serveur et nous \u00e9coutons les \u00e9v\u00e9nements dans son sous-arbre.<\/p>\n<ul>\n<li>Le serveur appelle <code>recvfrom()<\/code>, acceptant sur ce port des paquets provenant de n'importe quel h\u00f4te.<\/li>\n<\/ul>\n<p>\nDans notre cas, nous devons v\u00e9rifier la file d'attente des paquets entrants envoy\u00e9s \u00e0 ce port.<\/p>\n<p>Chaque port a sa propre file d'attente, et nous ajoutons au d\u00e9but des datagrammes WebRTC les ports source et destination, afin de savoir dans quelle file d'attente rediriger lors de l'arriv\u00e9e d'un nouveau paquet.<\/p>\n<p>L'appel est non-bloquant, donc si aucun paquet n'est pr\u00e9sent, nous retournons simplement -1 et d\u00e9finissons <code>errno=EWOULDBLOCK<\/code>.<\/p>\n<ul>\n<li>Le client obtient par des moyens externes l'IP et le port du serveur, et appelle <code>sendto()<\/code>. Cette action entra\u00eene \u00e9galement un appel interne, donc le suivant <code>bind()<\/code>. <code>recvfrom()<\/code> obtiendra une r\u00e9ponse sans ex\u00e9cution explicite du bind.<\/li>\n<\/ul>\n<p>\nDans notre cas, le client obtient une cl\u00e9 de cha\u00eene de mani\u00e8re externe et utilise la fonction <code>resolve()<\/code> pour obtenir l'adresse IP.<\/p>\n<p>\u00c0 ce stade, nous commen\u00e7ons le \u00ab handshake \u00bb WebRTC, si deux pairs ne sont pas encore connect\u00e9s entre eux. Les connexions \u00e0 diff\u00e9rents ports d'un m\u00eame pair utilisent le m\u00eame DataChannel WebRTC.<\/p>\n<p>Nous effectuons \u00e9galement une op\u00e9ration indirecte <code>bind()<\/code>, pour que le serveur puisse restaurer la connexion lors du prochain <code>sendto()<\/code> au cas o\u00f9 elle se serait ferm\u00e9e pour une raison quelconque.<\/p>\n<p>Le serveur est inform\u00e9 de la connexion du client lorsque le client enregistre son offre SDP sous les informations du port du serveur dans Firebase, et le serveur r\u00e9pond \u00e9galement avec sa r\u00e9ponse.<\/p>\n<p>\nLe sch\u00e9ma ci-dessous montre un exemple de transmission des messages pour le sch\u00e9ma de sockets et l'envoi du premier message du client au serveur :<\/p>\n<p><img decoding=\"async\" alt=\"Nous portons un jeu multijoueur de C++ vers le web avec Cheerp, WebRTC et Firebase\" src=\"\/wp-content\/uploads\/2019\/09\/7dc48e816c09e33daf28c307cad0c258.jpg\" style=\"display:block;margin: 0 auto;\" \/><br \/>\n<i>Le sch\u00e9ma complet de l'\u00e9tape de connexion entre le client et le serveur<\/i><\/p>\n<h1>Conclusion<\/h1>\n<p>\nSi vous \u00eates arriv\u00e9 jusqu'ici, vous devez probablement \u00eatre int\u00e9ress\u00e9 \u00e0 voir la th\u00e9orie en action. Vous pouvez jouer \u00e0 <noindex><a rel=\"nofollow\" href=\"https:\/\/teeworlds.leaningtech.com\">teeworlds.leaningtech.com<\/a><\/noindex>, essayez-le !<\/p>\n<p><center><iframe loading=\"lazy\" width=\"560\" height=\"315\" src=\"https:\/\/gfycat.com\/ifr\/newjaggedcoyote\" frameborder=\"0\" allowfullscreen><\/iframe><\/center><br \/>\n<i>Match amical entre coll\u00e8gues<\/i><\/p>\n<p>Le code de la biblioth\u00e8que r\u00e9seau est librement disponible sur <noindex><a rel=\"nofollow\" href=\"https:\/\/github.com\/leaningtech\/cheerpnet\">Github<\/a><\/noindex>. Rejoignez la discussion sur notre canal dans <noindex><a rel=\"nofollow\" href=\"https:\/\/gitter.im\/leaningtech\/cheerp\">Gitter<\/a><\/noindex>!<br \/>\n<br \/>Source : <a content=\"nofollow\" rel=\"nofollow\" href=\"https:\/\/habr.com\/ru\/post\/468031\/\">habr.com<\/a><\/p>","protected":false,"gt_translate_keys":[{"key":"rendered","format":"html"}]},"excerpt":{"rendered":"<p>\u0412\u0432\u0435\u0434\u0435\u043d\u0438\u0435 \u041d\u0430\u0448\u0430 \u043a\u043e\u043c\u043f\u0430\u043d\u0438\u044f Leaning Technologies \u043f\u0440\u0435\u0434\u043e\u0441\u0442\u0430\u0432\u043b\u044f\u0435\u0442 \u0440\u0435\u0448\u0435\u043d\u0438\u044f \u043f\u043e \u043f\u043e\u0440\u0442\u0438\u0440\u043e\u0432\u0430\u043d\u0438\u044e \u0442\u0440\u0430\u0434\u0438\u0446\u0438\u043e\u043d\u043d\u044b\u0445 desktop-\u043f\u0440\u0438\u043b\u043e\u0436\u0435\u043d\u0438\u0439 \u0432 \u0432\u0435\u0431. \u041d\u0430\u0448 \u043a\u043e\u043c\u043f\u0438\u043b\u044f\u0442\u043e\u0440 C++ Cheerp \u0433\u0435\u043d\u0435\u0440\u0438\u0440\u0443\u0435\u0442 \u0441\u043e\u0447\u0435\u0442\u0430\u043d\u0438\u0435 WebAssembly \u0438 JavaScript, \u0447\u0442\u043e \u043e\u0431\u0435\u0441\u043f\u0435\u0447\u0438\u0432\u0430\u0435\u0442 \u0438 \u043f\u0440\u043e\u0441\u0442\u043e\u0435 \u0432\u0437\u0430\u0438\u043c\u043e\u0434\u0435\u0439\u0441\u0442\u0432\u0438\u0435 \u0441 \u0431\u0440\u0430\u0443\u0437\u0435\u0440\u043e\u043c, \u0438 \u0432\u044b\u0441\u043e\u043a\u0443\u044e \u043f\u0440\u043e\u0438\u0437\u0432\u043e\u0434\u0438\u0442\u0435\u043b\u044c\u043d\u043e\u0441\u0442\u044c. \u0412 \u043a\u0430\u0447\u0435\u0441\u0442\u0432\u0435 \u043f\u0440\u0438\u043c\u0435\u0440\u0430 \u0435\u0433\u043e \u043f\u0440\u0438\u043c\u0435\u043d\u0435\u043d\u0438\u044f \u043c\u044b \u0440\u0435\u0448\u0438\u043b\u0438 \u043f\u043e\u0440\u0442\u0438\u0440\u043e\u0432\u0430\u0442\u044c \u0434\u043b\u044f \u0432\u0435\u0431\u0430 \u043c\u043d\u043e\u0433\u043e\u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u0442\u0435\u043b\u044c\u0441\u043a\u0443\u044e \u0438\u0433\u0440\u0443 \u0438 \u0432\u044b\u0431\u0440\u0430\u043b\u0438 \u0434\u043b\u044f \u044d\u0442\u043e\u0433\u043e Teeworlds. Teeworlds \u2014 \u044d\u0442\u043e \u043c\u043d\u043e\u0433\u043e\u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u0442\u0435\u043b\u044c\u0441\u043a\u0430\u044f \u0434\u0432\u0443\u0445\u043c\u0435\u0440\u043d\u0430\u044f \u0440\u0435\u0442\u0440\u043e-\u0438\u0433\u0440\u0430 [&hellip;]<\/p>\n","protected":false,"gt_translate_keys":[{"key":"rendered","format":"html"}]},"author":1,"featured_media":28801,"comment_status":"open","ping_status":"open","sticky":false,"template":"","format":"standard","meta":{"footnotes":""},"categories":[688],"tags":[],"class_list":["post-38370","post","type-post","status-publish","format-standard","has-post-thumbnail","hentry","category-administrirovanie"],"aioseo_notices":[],"aioseo_head":"\n\t\t<!-- All in One SEO 5.0.2.1 - aioseo.com -->\n\t<meta name=\"description\" content=\"\u0412\u0432\u0435\u0434\u0435\u043d\u0438\u0435 \u041d\u0430\u0448\u0430 \u043a\u043e\u043c\u043f\u0430\u043d\u0438\u044f Leaning Technologies \u043f\u0440\u0435\u0434\u043e\u0441\u0442\u0430\u0432\u043b\u044f\u0435\u0442 \u0440\u0435\u0448\u0435\u043d\u0438\u044f \u043f\u043e \u043f\u043e\u0440\u0442\u0438\u0440\u043e\u0432\u0430\u043d\u0438\u044e \u0442\u0440\u0430\u0434\u0438\u0446\u0438\u043e\u043d\u043d\u044b\u0445 desktop-\u043f\u0440\u0438\u043b\u043e\u0436\u0435\u043d\u0438\u0439 \u0432 \u0432\u0435\u0431.\" \/>\n\t<meta name=\"robots\" content=\"max-image-preview:large\" \/>\n\t<meta name=\"author\" content=\"Yuri Gagarin\"\/>\n\t<link rel=\"canonical\" href=\"https:\/\/prohoster.info\/fr\/blog\/administrirovanie\/portiruem-mnogopolzovatelskuyu-igru-s-s-na-veb-c-cheerp-webrtc-i-firebase\" \/>\n\t<meta name=\"generator\" content=\"All in One SEO (AIOSEO) 5.0.2.1\" \/>\n\t\t<meta property=\"og:locale\" content=\"fr_FR\" \/>\n\t\t<meta property=\"og:site_name\" content=\"ProHoster | \u041a\u0443\u043f\u0438\u0442\u044c \u043d\u0430\u0434\u0435\u0436\u043d\u044b\u0439 \u0445\u043e\u0441\u0442\u0438\u043d\u0433 \u0434\u043b\u044f \u0441\u0430\u0439\u0442\u043e\u0432 \u0441 \u0437\u0430\u0449\u0438\u0442\u043e\u0439 \u043e\u0442 DDoS, VPS VDS \u0441\u0435\u0440\u0432\u0435\u0440\u044b\" \/>\n\t\t<meta property=\"og:type\" content=\"article\" \/>\n\t\t<meta property=\"og:title\" content=\"\ud83e\udd47\u041f\u043e\u0440\u0442\u0438\u0440\u0443\u0435\u043c \u043c\u043d\u043e\u0433\u043e\u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u0442\u0435\u043b\u044c\u0441\u043a\u0443\u044e \u0438\u0433\u0440\u0443 \u0441 \u0421++ \u043d\u0430 \u0432\u0435\u0431 c Cheerp, WebRTC \u0438 Firebase | ProHoster\" \/>\n\t\t<meta property=\"og:description\" content=\"\u0412\u0432\u0435\u0434\u0435\u043d\u0438\u0435 \u041d\u0430\u0448\u0430 \u043a\u043e\u043c\u043f\u0430\u043d\u0438\u044f Leaning Technologies \u043f\u0440\u0435\u0434\u043e\u0441\u0442\u0430\u0432\u043b\u044f\u0435\u0442 \u0440\u0435\u0448\u0435\u043d\u0438\u044f \u043f\u043e \u043f\u043e\u0440\u0442\u0438\u0440\u043e\u0432\u0430\u043d\u0438\u044e \u0442\u0440\u0430\u0434\u0438\u0446\u0438\u043e\u043d\u043d\u044b\u0445 desktop-\u043f\u0440\u0438\u043b\u043e\u0436\u0435\u043d\u0438\u0439 \u0432 \u0432\u0435\u0431.\" \/>\n\t\t<meta property=\"og:url\" content=\"https:\/\/prohoster.info\/fr\/blog\/administrirovanie\/portiruem-mnogopolzovatelskuyu-igru-s-s-na-veb-c-cheerp-webrtc-i-firebase\" \/>\n\t\t<meta property=\"og:image\" content=\"https:\/\/prohoster.info\/wp-content\/uploads\/2021\/11\/logo-350.jpg\" \/>\n\t\t<meta property=\"og:image:secure_url\" content=\"https:\/\/prohoster.info\/wp-content\/uploads\/2021\/11\/logo-350.jpg\" \/>\n\t\t<meta property=\"og:image:width\" content=\"350\" \/>\n\t\t<meta property=\"og:image:height\" content=\"350\" \/>\n\t\t<meta property=\"article:published_time\" content=\"2019-10-31T19:23:18+00:00\" \/>\n\t\t<meta property=\"article:modified_time\" content=\"2019-10-31T19:23:18+00:00\" \/>\n\t\t<meta property=\"article:publisher\" content=\"https:\/\/www.facebook.com\/prohoster\" \/>\n\t\t<meta property=\"article:author\" content=\"https:\/\/www.facebook.com\/prohoster\" \/>\n\t\t<!-- All in One SEO -->\n\n","aioseo_head_json":{"title":"\ud83e\udd47Nous portons un jeu multijoueur de C++ vers le web avec Cheerp, WebRTC et Firebase | ProHoster","description":"Introduction Notre entreprise Leaning Technologies fournit des solutions pour porter des applications de bureau traditionnelles sur le web.","canonical_url":"https:\/\/prohoster.info\/fr\/blog\/administrirovanie\/portiruem-mnogopolzovatelskuyu-igru-s-s-na-veb-c-cheerp-webrtc-i-firebase","robots":"max-image-preview:large","keywords":"","webmasterTools":{"miscellaneous":""},"schema":null,"og:locale":"fr_FR","og:site_name":"ProHoster | \u041a\u0443\u043f\u0438\u0442\u044c \u043d\u0430\u0434\u0435\u0436\u043d\u044b\u0439 \u0445\u043e\u0441\u0442\u0438\u043d\u0433 \u0434\u043b\u044f \u0441\u0430\u0439\u0442\u043e\u0432 \u0441 \u0437\u0430\u0449\u0438\u0442\u043e\u0439 \u043e\u0442 DDoS, VPS VDS \u0441\u0435\u0440\u0432\u0435\u0440\u044b","og:type":"article","og:title":"\ud83e\udd47\u041f\u043e\u0440\u0442\u0438\u0440\u0443\u0435\u043c \u043c\u043d\u043e\u0433\u043e\u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u0442\u0435\u043b\u044c\u0441\u043a\u0443\u044e \u0438\u0433\u0440\u0443 \u0441 \u0421++ \u043d\u0430 \u0432\u0435\u0431 c Cheerp, WebRTC \u0438 Firebase | ProHoster","og:description":"\u0412\u0432\u0435\u0434\u0435\u043d\u0438\u0435 \u041d\u0430\u0448\u0430 \u043a\u043e\u043c\u043f\u0430\u043d\u0438\u044f Leaning Technologies \u043f\u0440\u0435\u0434\u043e\u0441\u0442\u0430\u0432\u043b\u044f\u0435\u0442 \u0440\u0435\u0448\u0435\u043d\u0438\u044f \u043f\u043e \u043f\u043e\u0440\u0442\u0438\u0440\u043e\u0432\u0430\u043d\u0438\u044e \u0442\u0440\u0430\u0434\u0438\u0446\u0438\u043e\u043d\u043d\u044b\u0445 desktop-\u043f\u0440\u0438\u043b\u043e\u0436\u0435\u043d\u0438\u0439 \u0432 \u0432\u0435\u0431.","og:url":"https:\/\/prohoster.info\/fr\/blog\/administrirovanie\/portiruem-mnogopolzovatelskuyu-igru-s-s-na-veb-c-cheerp-webrtc-i-firebase","og:image":"https:\/\/prohoster.info\/wp-content\/uploads\/2021\/11\/logo-350.jpg","og:image:secure_url":"https:\/\/prohoster.info\/wp-content\/uploads\/2021\/11\/logo-350.jpg","og:image:width":350,"og:image:height":350,"article:published_time":"2019-10-31T19:23:18+00:00","article:modified_time":"2019-10-31T19:23:18+00:00","article:publisher":"https:\/\/www.facebook.com\/prohoster","article:author":"https:\/\/www.facebook.com\/prohoster"},"aioseo_meta_data":{"post_id":"38370","title":null,"description":null,"keywords":null,"keyphrases":null,"primary_term":null,"canonical_url":null,"og_title":null,"og_description":null,"og_object_type":"default","og_image_type":"default","og_image_url":null,"og_image_width":null,"og_image_height":null,"og_image_custom_url":null,"og_image_custom_fields":null,"og_video":null,"og_custom_url":null,"og_article_section":null,"og_article_tags":null,"twitter_use_og":false,"twitter_card":"default","twitter_image_type":"default","twitter_image_url":null,"twitter_image_custom_url":null,"twitter_image_custom_fields":null,"twitter_title":null,"twitter_description":null,"schema":{"blockGraphs":[],"customGraphs":[],"default":{"data":{"Article":[],"Course":[],"Dataset":[],"FAQPage":[],"Movie":[],"Person":[],"Product":[],"ProductReview":[],"Car":[],"Recipe":[],"Service":[],"SoftwareApplication":[],"WebPage":[]},"graphName":"","isEnabled":true},"graphs":[]},"schema_type":null,"schema_type_options":null,"pillar_content":false,"robots_default":true,"robots_noindex":false,"robots_noarchive":false,"robots_nosnippet":false,"robots_nofollow":false,"robots_noimageindex":false,"robots_noodp":false,"robots_notranslate":false,"robots_max_snippet":null,"robots_max_videopreview":null,"robots_max_imagepreview":"large","priority":null,"frequency":null,"local_seo":null,"seo_analyzer_scan_date":"2026-01-23 21:44:19","breadcrumb_settings":null,"limit_modified_date":false,"reviewed_by":null,"ai":null,"created":"2021-03-01 01:10:24","updated":"2026-01-23 21:44:19","focus_keyword":null,"additional_keywords":null,"truseo_locale":null},"gt_translate_keys":[{"key":"link","format":"url"}],"_links":{"self":[{"href":"https:\/\/prohoster.info\/fr\/wp-json\/wp\/v2\/posts\/38370","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/prohoster.info\/fr\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/prohoster.info\/fr\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/prohoster.info\/fr\/wp-json\/wp\/v2\/users\/1"}],"replies":[{"embeddable":true,"href":"https:\/\/prohoster.info\/fr\/wp-json\/wp\/v2\/comments?post=38370"}],"version-history":[{"count":0,"href":"https:\/\/prohoster.info\/fr\/wp-json\/wp\/v2\/posts\/38370\/revisions"}],"wp:featuredmedia":[{"embeddable":true,"href":"https:\/\/prohoster.info\/fr\/wp-json\/wp\/v2\/media\/28801"}],"wp:attachment":[{"href":"https:\/\/prohoster.info\/fr\/wp-json\/wp\/v2\/media?parent=38370"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/prohoster.info\/fr\/wp-json\/wp\/v2\/categories?post=38370"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/prohoster.info\/fr\/wp-json\/wp\/v2\/tags?post=38370"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}