{"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\/ro\/blog\/administrirovanie\/portiruem-mnogopolzovatelskuyu-igru-s-s-na-veb-c-cheerp-webrtc-i-firebase","title":{"rendered":"Port\u0103m un joc multiplayer din C++ pe web cu Cheerp, WebRTC \u0219i Firebase","gt_translate_keys":[{"key":"rendered","format":"text"}]},"content":{"rendered":"<h2>Introducere<\/h2>\n<p>\nCompania noastr\u0103 <noindex><a rel=\"nofollow\" href=\"https:\/\/leaningtech.com\">Leaning Technologies<\/a><\/noindex> ofer\u0103 solu\u021bii pentru portarea aplica\u021biilor desktop tradi\u021bionale \u00een web. Compilatorul nostru C++ <noindex><a rel=\"nofollow\" href=\"https:\/\/github.com\/leaningtech\/cheerp-meta\">Cheerp<\/a><\/noindex> genereaz\u0103 o combina\u021bie de WebAssembly \u0219i JavaScript, ceea ce asigur\u0103 at\u00e2t <noindex><a rel=\"nofollow\" href=\"https:\/\/github.com\/leaningtech\/cheerp-meta\/wiki\/Cheerp-Tutorial%3A-Mixed-mode-C++-to-WebAssembly-and-JavaScript\">interac\u021biune simpl\u0103 cu browserul<\/a><\/noindex>, c\u00e2t \u0219i o performan\u021b\u0103 ridicat\u0103.<\/p>\n<p>Ca exemplu al aplic\u0103rii sale, am decis s\u0103 port\u0103m pentru web un joc multiplayer \u0219i am ales pentru aceasta <noindex><a rel=\"nofollow\" href=\"https:\/\/www.teeworlds.com\/\"><strong>Teeworlds<\/strong><\/a><\/noindex>. Teeworlds este un joc retro 2D multiplayer cu o comunitate mic\u0103, dar activ\u0103 de juc\u0103tori (inclusiv eu!). Este mic at\u00e2t \u00een ceea ce prive\u0219te resursele desc\u0103rcabile, c\u00e2t \u0219i cerin\u021bele pentru CPU \u0219i GPU - un candidat ideal.<\/p>\n<p><img decoding=\"async\" alt=\"Port\u0103m un joc multiplayer din C++ pe web cu Cheerp, WebRTC \u0219i Firebase\" src=\"\/wp-content\/uploads\/2019\/09\/30bff36ee1158ddea2e76d8b11e48d2b.jpg\" style=\"display:block;margin: 0 auto;\" \/><br \/>\n<i>Teeworlds care ruleaz\u0103 \u00een browser<\/i><br \/>\n<noindex><a rel=\"nofollow\" name=\"habracut\"><\/a><\/noindex><br \/>\nAm decis s\u0103 folosim acest proiect pentru a experimenta cu <strong>solu\u021bii generale pentru portarea codului de re\u021bea pentru web<\/strong>. De obicei, acest lucru se realizeaz\u0103 \u00een urm\u0103toarele moduri:<\/p>\n<ul>\n<li><strong>XMLHttpRequest\/fetch<\/strong>, dac\u0103 partea de re\u021bea const\u0103 doar din cereri HTTP, sau<\/li>\n<li><strong>WebSockets<\/strong>.<\/li>\n<\/ul>\n<p>\nAmbele solu\u021bii necesit\u0103 g\u0103zduirea componentei server pe partea serverului, iar niciuna dintre ele nu permite utilizarea ca protocol de transport <noindex><a rel=\"nofollow\" href=\"https:\/\/en.wikipedia.org\/wiki\/User_Datagram_Protocol\">UDP<\/a><\/noindex>. Acest lucru este important pentru aplica\u021biile \u00een timp real, cum ar fi software-ul pentru videoconferin\u021be \u0219i jocuri, deoarece garan\u021biile de livrare \u0219i ordinea pachetelor protocolului <noindex><a rel=\"nofollow\" href=\"https:\/\/en.wikipedia.org\/wiki\/Transmission_Control_Protocol\">TCP<\/a><\/noindex> pot deveni un obstacol pentru laten\u021be sc\u0103zute.<\/p>\n<p>Exist\u0103 \u0219i o a treia cale - utilizarea re\u021belei din browser: <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> suport\u0103 at\u00e2t transmiterea fiabil\u0103, c\u00e2t \u0219i cea nefiabil\u0103 (\u00een acest ultim caz \u00eencearc\u0103, pe c\u00e2t posibil, s\u0103 foloseasc\u0103 ca protocol de transport UDP) \u0219i poate fi utilizat at\u00e2t cu servere externe, c\u00e2t \u0219i \u00eentre browsere. <strong>Aceasta \u00eenseamn\u0103 c\u0103 putem porta \u00een browser \u00eentreaga aplica\u021bie, inclusiv componenta server!<\/strong><\/p>\n<p>Cu toate acestea, exist\u0103 o dificultate suplimentar\u0103: \u00eenainte ca dou\u0103 p\u0103r\u021bi WebRTC s\u0103 poat\u0103 schimba date, trebuie s\u0103 execute o procedur\u0103 relativ complex\u0103 de \u00abstr\u00e2ngere de m\u00e2ini\u00bb (handshake) pentru conectare, ceea ce necesit\u0103 c\u00e2teva entit\u0103\u021bi externe (server de semnalizare \u0219i unul sau mai multe servere <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>Ideal ar fi s\u0103 cre\u0103m un API de re\u021bea, care s\u0103 utilizeze WebRTC \u00een interior, dar c\u00e2t mai aproape de interfa\u021ba UDP Sockets, care nu necesit\u0103 stabilirea unei conexiuni.<\/p>\n<p>Aceasta ne va permite s\u0103 beneficiem de avantajele WebRTC f\u0103r\u0103 a fi necesar s\u0103 dezv\u0103luim detalii complexe despre codul aplica\u021biei (pe care \u00een proiectul nostru am dorit s\u0103-l modific\u0103m c\u00e2t mai pu\u021bin).<\/p>\n<h1>WebRTC minim<\/h1>\n<p>\nWebRTC este un set de API-uri disponibile \u00een browsere, care asigur\u0103 transmiterea de sunet, video \u0219i date arbitrary de la peer la peer.<\/p>\n<p>Conexiunea \u00eentre perechi este stabilit\u0103 (chiar \u0219i \u00een cazul \u00een care exist\u0103 NAT de o parte sau de ambele p\u0103r\u021bi) prin intermediul serverelor STUN \u0219i\/sau TURN printr-un mecanism numit ICE. Perechile schimb\u0103 informa\u021bii ICE \u0219i parametrii canalelor prin intermediul ofertei \u0219i r\u0103spunsului protocolului SDP.<\/p>\n<p>Wow! At\u00e2tea acronime deodat\u0103. S\u0103 explic\u0103m pe scurt ce \u00eenseamn\u0103 aceste concepte:<\/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 protocol pentru a ocoli NAT \u0219i a ob\u021bine o pereche (IP, port) pentru a schimba date direct cu gazda. Dac\u0103 reu\u0219e\u0219te s\u0103 \u00ee\u0219i \u00eendeplineasc\u0103 sarcina, perechile pot schimba date \u00eentre ele.<\/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> de asemenea, este folosit pentru a ocoli NAT, dar o face redirec\u021bion\u00e2nd datele printr-un proxy vizibil ambelor perechi. Adaug\u0103 \u00eent\u00e2rziere \u0219i este mai costisitor de realizat dec\u00e2t STUN (deoarece este utilizat pe \u00eentreaga durat\u0103 a sesiunii de comunicare), dar uneori este singura op\u021biune posibil\u0103.<\/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> este utilizat pentru a alege cea mai bun\u0103 modalitate posibil\u0103 de conectare a dou\u0103 perechi pe baza informa\u021biilor ob\u021binute \u00een timpul conexiunii directe, precum \u0219i a informa\u021biilor ob\u021binute de la un num\u0103r de servere STUN \u0219i 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> \u2014 este un format pentru descrierea parametrilor canalului de conectare, cum ar fi candida\u021bii ICE, codec-urile media (\u00een cazul canalelor audio\/video) etc. O pereche trimite SDP Offer (\u201eofert\u0103\u201d), iar cealalt\u0103 r\u0103spunde cu SDP Answer (\u201er\u0103spuns\u201d). Dup\u0103 aceea, se creeaz\u0103 canalul.<\/li>\n<\/ul>\n<p>\nPentru a crea o astfel de conexiune, perechile trebuie s\u0103 adune informa\u021biile ob\u021binute de la serverele STUN \u0219i TURN \u0219i s\u0103 le schimbe \u00eentre ele.<\/p>\n<p>Problema este c\u0103 nu au \u00eenc\u0103 capacitatea de a schimba date direct, a\u0219a c\u0103 trebuie s\u0103 existe un mecanism offline pentru a schimba aceste date: un server de semnalizare.<\/p>\n<p>Serverul de semnalizare poate fi foarte simplu, deoarece singura sa sarcin\u0103 este de a redirec\u021biona datele \u00eentre perechi \u00een etapa de 'str\u00e2ngere de m\u00e2n\u0103' (a\u0219a cum este ar\u0103tat \u00een schema de mai jos).<\/p>\n<p><img decoding=\"async\" alt=\"Port\u0103m un joc multiplayer din C++ pe web cu Cheerp, WebRTC \u0219i Firebase\" src=\"\/wp-content\/uploads\/2019\/09\/0fc39ba5e0942bc19183e8cf06c60868.jpg\" style=\"display:block;margin: 0 auto;\" \/><br \/>\n<i>Schema simplificat\u0103 a secven\u021bei de \u201estr\u00e2ngere de m\u00e2ini\u201d WebRTC<\/i><\/p>\n<h1>Prezentare a modelului de re\u021bea Teeworlds<\/h1>\n<p>\nArhitectura re\u021belei Teeworlds este foarte simpl\u0103:<\/p>\n<ul>\n<li>Componentele clientului \u0219i serverului sunt dou\u0103 programe diferite.<\/li>\n<li>Clien\u021bii se al\u0103tur\u0103 jocului conect\u00e2ndu-se la unul dintre mai multe servere, fiecare dintre ele g\u0103zduind doar un singur joc \u00een acela\u0219i timp.<\/li>\n<li>Toat\u0103 transmiterea de date \u00een joc se face prin intermediul serverului.<\/li>\n<li>Un server master special este utilizat pentru a colecta lista tuturor serverelor publice, care sunt afi\u0219ate \u00een clientul de joc.<\/li>\n<\/ul>\n<p>\nPrin utilizarea WebRTC pentru schimbul de date, putem muta componenta de server a jocului \u00een browser, unde se afl\u0103 clientul. Aceasta ne ofer\u0103 o oportunitate minunat\u0103\u2026<\/p>\n<h1>De a sc\u0103pa de servere<\/h1>\n<p>\nLipsa logicii serverului are un avantaj pl\u0103cut: putem desf\u0103\u0219ura \u00eentreaga aplica\u021bie ca un con\u021binut static pe Github Pages sau pe echipamentul nostru \u00een spatele Cloudflare, asigur\u00e2ndu-ne astfel \u00eenc\u0103rc\u0103ri rapide \u0219i o disponibilitate ridicat\u0103, complet gratuit. Practic, le putem uita, iar dac\u0103 avem noroc \u0219i jocul devine popular, nu va trebui s\u0103 moderniz\u0103m infrastructura.<\/p>\n<p>Cu toate acestea, pentru ca sistemul s\u0103 func\u021bioneze, va trebui totu\u0219i s\u0103 folosim o arhitectur\u0103 extern\u0103:<\/p>\n<ul>\n<li>Unul sau mai multe servere STUN: avem de ales dintre mai multe op\u021biuni gratuite.<\/li>\n<li>Cel pu\u021bin un server TURN: aici nu exist\u0103 op\u021biuni gratuite, a\u0219a c\u0103 putem fie s\u0103 ne configur\u0103m propriul server, fie s\u0103 pl\u0103tim pentru un serviciu. Din fericire, majoritatea timpului conexiunea se va putea stabili prin intermediul serverelor STUN (asigur\u00e2nd astfel un p2p real), dar TURN este necesar ca variant\u0103 de rezerv\u0103.<\/li>\n<li>Server de semnalizare: spre deosebire de celelalte dou\u0103 aspecte, semnalizarea nu este standardizat\u0103. Ceea ce va face efectiv serverul de semnalizare depinde, \u00een oarecare m\u0103sur\u0103, de aplica\u021bie. \u00cen cazul nostru, \u00eenainte de a stabili o conexiune, trebuie s\u0103 schimb\u0103m un volum mic de date.<\/li>\n<li>Server master Teeworlds: este utilizat de celelalte servere pentru a-\u0219i anun\u021ba existen\u021ba \u0219i de clien\u021bi pentru a g\u0103si servere publice. De\u0219i nu este obligatoriu (clien\u021bii se pot conecta \u00eentotdeauna manual la serverul pe care \u00eel cunosc), ar fi bine s\u0103 existe pentru a permite juc\u0103torilor s\u0103 participe la jocuri cu oameni aleatori.<\/li>\n<\/ul>\n<p>\nAm decis s\u0103 folosim serverele STUN gratuite ale Google, iar un server TURN l-am desf\u0103\u0219urat noi \u00een\u0219ine.<\/p>\n<p>Pentru ultimele dou\u0103 puncte, am folosit <noindex><a rel=\"nofollow\" href=\"https:\/\/en.wikipedia.org\/wiki\/Firebase\">Firebase<\/a><\/noindex>:<\/p>\n<ul>\n<li>Serverul master Teeworlds este realizat foarte simplu: ca o list\u0103 de obiecte, care con\u021bin informa\u021bii (nume, IP, hart\u0103, mod, \u2026) despre fiecare server activ. Serverele public\u0103 \u0219i \u00ee\u0219i actualizeaz\u0103 propriul obiect, iar clien\u021bii iau \u00eentreaga list\u0103 \u0219i o afi\u0219eaz\u0103 juc\u0103torului. De asemenea, afi\u0219\u0103m lista pe pagina principal\u0103 ca HTML, astfel \u00eenc\u00e2t juc\u0103torii s\u0103 poat\u0103 pur \u0219i simplu s\u0103 dea clic pe server \u0219i s\u0103 intre direct \u00een joc.<\/li>\n<li>Semnalizarea este str\u00e2ns legat\u0103 de implementarea noastr\u0103 a socket-urilor, descris\u0103 \u00een sec\u021biunea urm\u0103toare.<\/li>\n<\/ul>\n<p>\n<img decoding=\"async\" alt=\"Port\u0103m un joc multiplayer din C++ pe web cu Cheerp, WebRTC \u0219i Firebase\" src=\"\/wp-content\/uploads\/2019\/09\/5e30bc22643f8baecf850053bf69e130.jpg\" style=\"display:block;margin: 0 auto;\" \/><br \/>\n<i>Lista serverelor din joc \u0219i pe pagina principal\u0103<\/i><\/p>\n<h1>Implementarea socket-urilor<\/h1>\n<p>\nDorim s\u0103 cre\u0103m un API c\u00e2t mai apropiat de socket-urile Posix UDP, pentru a minimiza num\u0103rul de modific\u0103ri necesare.<\/p>\n<p>De asemenea, dorim s\u0103 implement\u0103m minimul necesar pentru un schimb de date simplu prin re\u021bea.<\/p>\n<p>De exemplu, nu avem nevoie de rutare real\u0103: toate nodurile se afl\u0103 \u00eentr-o \u201eLAN virtual\u0103\u201d, legat\u0103 de o instan\u021b\u0103 specific\u0103 a bazei de date Firebase.<\/p>\n<p>Prin urmare, nu avem nevoie de adrese IP unice: pentru identificarea unic\u0103 a nodurilor, este suficient s\u0103 folosim valori unice ale cheilor Firebase (similar cu numele de domenii), iar fiecare nod \u00ee\u0219i aloc\u0103 local \u201efalsuri\u201d IP pentru fiecare cheie care trebuie convertit\u0103. Acest lucru ne scute\u0219te complet de necesitatea aloc\u0103rii globale a adreselor IP, ceea ce este o sarcin\u0103 non-trivial\u0103.<\/p>\n<p>Iat\u0103 API-ul minim pe care trebuie s\u0103-l implement\u0103m:<\/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>\nAPI-ul este simplu \u0219i seam\u0103n\u0103 cu API-ul socket-urilor Posix, dar are c\u00e2teva diferen\u021be importante: <strong>\u00eenregistrarea callback-urilor, alocarea IP-urilor locale \u0219i conexiune \u201elene\u0219\u0103\u201d<\/strong>.<\/p>\n<h2>\u00cenregistrarea callback-urilor<\/h2>\n<p>\nChiar dac\u0103 programul ini\u021bial folose\u0219te intrare\/ie\u0219ire non-blocant\u0103, pentru a rula \u00een browserul web, codul trebuie refactorizat.<\/p>\n<p>Motivul este c\u0103 ciclul de evenimente din browser este ascuns de program (fie c\u0103 este vorba de JavaScript sau WebAssembly).<\/p>\n<p>\u00centr-un mediu nativ, putem scrie cod \u00een acest mod<\/p>\n<pre><code class=\"cpp\">while(running) {\n  select(...); \/\/ a\u0219teapt\u0103 evenimente I\/O\n  while(true) {\n    int r = readfrom(...); \/\/ \u00eencearc\u0103 s\u0103 citeasc\u0103\n    if (r &lt; 0 &amp;&amp; errno == EWOULDBLOCK) \/\/ nu mai sunt date disponibile\n      break;\n    ...\n  }\n  ...\n}<\/code><\/pre>\n<p>\nDac\u0103 ciclul de evenimente este ascuns pentru noi, atunci trebuie s\u0103-l transform\u0103m \u00eentr-un mod similar:<\/p>\n<pre><code class=\"cpp\">auto cb = []() { \/\/ acest lucru va fi apelat c\u00e2nd sunt disponibile date noi\n  while(true) {\n    int r = readfrom(...); \/\/ \u00eencearc\u0103 s\u0103 citeasc\u0103\n    if (r &lt; 0 &amp;&amp; errno == EWOULDBLOCK) \/\/ nu mai sunt date disponibile\n      break;\n    ...\n  }\n  ...\n};\nrecvCallback(cb); \/\/ \u00eenregistreaz\u0103 callback-ul<\/code><\/pre>\n<p><\/p>\n<h2>Scopul IP-urilor locale<\/h2>\n<p>\nIdentificatorii nodurilor din \u201ere\u021beaua\u201d noastr\u0103 nu sunt adrese IP, ci chei Firebase (acestea sunt \u0219iruri care arat\u0103 a\u0219a: <code>-LmEC50PYZLCiCP-vqde<\/code> ).<\/p>\n<p>Acest lucru este convenabil, deoarece nu avem nevoie de un mecanism pentru a aloca IP-uri \u0219i a verifica unicitatea lor (precum \u0219i de gestionarea lor dup\u0103 deconectarea clientului), dar este adesea necesar s\u0103 identific\u0103m perechile printr-o valoare numeric\u0103.<\/p>\n<p>Exact pentru aceasta sunt folosite func\u021biile <code>resolve<\/code> \u0219i <code>reverseResolve<\/code>: aplica\u021bia ob\u021bine \u00een vreun fel valoarea \u0219irului cheii (prin introducerea utilizatorului sau prin serverul maestru) \u0219i poate transforma aceasta \u00een adres\u0103 IP pentru utilizare intern\u0103. Restul API-ului de asemenea, pentru simplificare, prime\u0219te \u00een locul \u0219irului aceast\u0103 valoare.<\/p>\n<p>Aceasta este similar cu o c\u0103utare DNS, doar c\u0103 se efectueaz\u0103 local pe client.<\/p>\n<p>Adic\u0103, adresele IP nu pot fi comune pentru diferi\u021bi clien\u021bi, iar dac\u0103 este nevoie de un identificator global, acesta va fi generat printr-o alt\u0103 metod\u0103.<\/p>\n<h2>Conexiune lene\u0219\u0103<\/h2>\n<p>\nUDP nu necesit\u0103 o conexiune, dar, a\u0219a cum am v\u0103zut, \u00eenainte de a \u00eencepe transferul de date \u00eentre dou\u0103 perechi, WebRTC necesit\u0103 un proces lung de conectare.<\/p>\n<p>Dac\u0103 dorim s\u0103 asigur\u0103m acela\u0219i nivel de abstractizare, (<code>sendto<\/code>\/<code>recvfrom<\/code> cu perechi arbitrare f\u0103r\u0103 conectare prealabil\u0103), atunci trebuie s\u0103 realiz\u0103m o conectare \u201elene\u0219\u0103\u201d (\u00eent\u00e2rziat\u0103) \u00een cadrul API-ului.<\/p>\n<p>Iat\u0103 ce se \u00eent\u00e2mpl\u0103 \u00een mod obi\u0219nuit \u00een timpul schimbului de date \u00eentre \u201eserver\u201d \u0219i \u201eclient\u201d \u00een cazul utiliz\u0103rii UDP, \u0219i ce ar trebui s\u0103 fac\u0103 biblioteca noastr\u0103:<\/p>\n<ul>\n<li>Serverul apeleaz\u0103 <code>bind()<\/code>, pentru a informa sistemul de operare c\u0103 dore\u0219te s\u0103 primeasc\u0103 pachete pe portul specificat.<\/li>\n<\/ul>\n<p>\n\u00cen schimb, vom publica un port deschis \u00een Firebase sub cheia serverului \u0219i vom asculta evenimente \u00een subarborele s\u0103u.<\/p>\n<ul>\n<li>Serverul apeleaz\u0103 <code>recvfrom()<\/code>, primind \u00een acel port pachete de la orice gazd\u0103.<\/li>\n<\/ul>\n<p>\n\u00cen cazul nostru, trebuie s\u0103 verific\u0103m coada de intrare a pachetelor trimise pe acel port.<\/p>\n<p>Fiecare port are propria coad\u0103, iar noi ad\u0103ug\u0103m la cap\u0103tul datagramei WebRTC porturile surs\u0103 \u0219i destina\u021bie, pentru a \u0219ti \u00een ce coad\u0103 s\u0103 redirec\u021bion\u0103m un pachet nou la sosire.<\/p>\n<p>Apelul este non-blocant, a\u0219a c\u0103, dac\u0103 nu exist\u0103 pachete, doar return\u0103m -1 \u0219i set\u0103m <code>errno=EWOULDBLOCK<\/code>.<\/p>\n<ul>\n<li>Clientul ob\u021bine prin metode externe IP-ul \u0219i portul serverului \u0219i apeleaz\u0103 <code>sendto()<\/code>. De asemenea, se efectueaz\u0103 un apel intern, astfel \u00eenc\u00e2t urm\u0103torul <code>bind()<\/code>va primi un r\u0103spuns f\u0103r\u0103 a efectua explicit bind. <code>recvfrom()<\/code> va primi un r\u0103spuns f\u0103r\u0103 a efectua explicit bind.<\/li>\n<\/ul>\n<p>\n\u00cen cazul nostru, clientul prime\u0219te extern un cheie de tip string \u0219i folose\u0219te func\u021bia <code>resolve()<\/code> pentru a ob\u021bine adresa IP.<\/p>\n<p>\u00cen acest stadiu, \u00eencepem \u00abstr\u00e2ngerea de m\u00e2n\u0103\u00bb WebRTC, dac\u0103 cei doi peer-uri nu sunt \u00eenc\u0103 conecta\u021bi unul la cel\u0103lalt. Conexiunile la porturi diferite ale aceluia\u0219i peer folosesc aceea\u0219i DataChannel WebRTC.<\/p>\n<p>De asemenea, efectuam indirect <code>bind()<\/code>, pentru ca serverul s\u0103 poat\u0103 restabili conexiunea \u00een urm\u0103torul <code>sendto()<\/code> \u00een cazul \u00een care acesta s-a \u00eenchis din diverse motive.<\/p>\n<p>Serverul este notificat de conectarea clientului atunci c\u00e2nd clientul \u00ee\u0219i scrie oferta SDP sub informa\u021bia portului serverului \u00een Firebase, iar serverul r\u0103spunde tot acolo cu un r\u0103spuns.<\/p>\n<p>\nSchema prezentat\u0103 mai jos arat\u0103 un exemplu de mi\u0219care a mesajelor pentru schema de socket \u0219i transmiterea primei mesaje de la client la server:<\/p>\n<p><img decoding=\"async\" alt=\"Port\u0103m un joc multiplayer din C++ pe web cu Cheerp, WebRTC \u0219i Firebase\" src=\"\/wp-content\/uploads\/2019\/09\/7dc48e816c09e33daf28c307cad0c258.jpg\" style=\"display:block;margin: 0 auto;\" \/><br \/>\n<i>Schema complet\u0103 a etapei de conectare \u00eentre client \u0219i server.<\/i><\/p>\n<h1>Concluzie<\/h1>\n<p>\nDac\u0103 a\u021bi citit p\u00e2n\u0103 la cap\u0103t, probabil c\u0103 sunte\u021bi curios s\u0103 vede\u021bi teoria \u00een ac\u021biune. Pute\u021bi juca jocul pe <noindex><a rel=\"nofollow\" href=\"https:\/\/teeworlds.leaningtech.com\">teeworlds.leaningtech.com<\/a><\/noindex>, da, \u00eencerca\u021bi!<\/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>Un meci prietenesc \u00eentre colegi.<\/i><\/p>\n<p>Codul bibliotecii de re\u021bea este disponibil gratuit pe <noindex><a rel=\"nofollow\" href=\"https:\/\/github.com\/leaningtech\/cheerpnet\">Github<\/a><\/noindex>. Al\u0103tura\u021bi-v\u0103 discu\u021biilor pe canalul nostru din <noindex><a rel=\"nofollow\" href=\"https:\/\/gitter.im\/leaningtech\/cheerp\">Gitter<\/a><\/noindex>!<br \/>\n<br \/>Sursa: <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\/ro\/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=\"ro_RO\" \/>\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\/ro\/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\udd47Port\u0103m un joc multiplayer din C++ pe web cu Cheerp, WebRTC \u0219i Firebase | ProHoster","description":"Introducere Compania noastr\u0103 Leaning Technologies ofer\u0103 solu\u021bii pentru portarea aplica\u021biilor desktop tradi\u021bionale \u00een web.","canonical_url":"https:\/\/prohoster.info\/ro\/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":"ro_RO","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\/ro\/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\/ro\/wp-json\/wp\/v2\/posts\/38370","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/prohoster.info\/ro\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/prohoster.info\/ro\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/prohoster.info\/ro\/wp-json\/wp\/v2\/users\/1"}],"replies":[{"embeddable":true,"href":"https:\/\/prohoster.info\/ro\/wp-json\/wp\/v2\/comments?post=38370"}],"version-history":[{"count":0,"href":"https:\/\/prohoster.info\/ro\/wp-json\/wp\/v2\/posts\/38370\/revisions"}],"wp:featuredmedia":[{"embeddable":true,"href":"https:\/\/prohoster.info\/ro\/wp-json\/wp\/v2\/media\/28801"}],"wp:attachment":[{"href":"https:\/\/prohoster.info\/ro\/wp-json\/wp\/v2\/media?parent=38370"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/prohoster.info\/ro\/wp-json\/wp\/v2\/categories?post=38370"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/prohoster.info\/ro\/wp-json\/wp\/v2\/tags?post=38370"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}