{"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\/et\/blog\/administrirovanie\/portiruem-mnogopolzovatelskuyu-igru-s-s-na-veb-c-cheerp-webrtc-i-firebase","title":{"rendered":"Kandime mitmikm\u00e4ngu C++-st veebis Cheerp, WebRTC ja Firebase abil","gt_translate_keys":[{"key":"rendered","format":"text"}]},"content":{"rendered":"<h2>Sissejuhatus<\/h2>\n<p>\nMeie ettev\u00f5te <noindex><a rel=\"nofollow\" href=\"https:\/\/leaningtech.com\">Leaning Technologies<\/a><\/noindex> pakub lahendusi traditsiooniliste lauaarvuti rakenduste \u00fcleviimiseks webi. Meie C++ kompilaator <noindex><a rel=\"nofollow\" href=\"https:\/\/github.com\/leaningtech\/cheerp-meta\">Cheerp<\/a><\/noindex> genereerib WebAssembly ja JavaScript'i kombinatsiooni, mis tagab nii <noindex><a rel=\"nofollow\" href=\"https:\/\/github.com\/leaningtech\/cheerp-meta\/wiki\/Cheerp-Tutorial%3A-Mixed-mode-C++-to-WebAssembly-and-JavaScript\">lihtsa interaktsiooni brauseriga<\/a><\/noindex>, kui ka k\u00f5rge j\u00f5udluse.<\/p>\n<p>Kuna n\u00e4ide selle rakendamisest otsustasime portida veebis mitme m\u00e4ngijaga m\u00e4ngu ja valisime selleks <noindex><a rel=\"nofollow\" href=\"https:\/\/www.teeworlds.com\/\"><strong>Teeworlds<\/strong><\/a><\/noindex>. Teeworlds on mitme m\u00e4ngijaga kahe m\u00f5\u00f5tmega retro m\u00e4ng, millel on v\u00e4ike, kuid aktiivne m\u00e4ngijaskond (sealhulgas mina!). See on v\u00e4ike nii allalaadimise mahult kui ka CPU ja GPU n\u00f5udmistes - ideaalne kandidaat.<\/p>\n<p><img decoding=\"async\" alt=\"Kandime mitmikm\u00e4ngu C++-st veebis Cheerp, WebRTC ja Firebase abil\" src=\"\/wp-content\/uploads\/2019\/09\/30bff36ee1158ddea2e76d8b11e48d2b.jpg\" style=\"display:block;margin: 0 auto;\" \/><br \/>\n<i>Brauseris t\u00f6\u00f6tav Teeworlds<\/i><br \/>\n<noindex><a rel=\"nofollow\" name=\"habracut\"><\/a><\/noindex><br \/>\nOtsustasime kasutada seda projekti, et katsetada <strong>\u00fcldisi lahendusi v\u00f5rgu koodi portimiseks veebis<\/strong>. Tavalised teostusviisid on j\u00e4rgmised:<\/p>\n<ul>\n<li><strong>XMLHttpRequest\/fetch<\/strong>, kui v\u00f5rgukoht koosneb ainult HTTP-p\u00e4ringutest, v\u00f5i<\/li>\n<li><strong>WebSockets<\/strong>.<\/li>\n<\/ul>\n<p>\nM\u00f5lemad lahendused n\u00f5uavad serverikomponendi majutamist serveripool, ja kumbki ei v\u00f5imalda kasutada <noindex><a rel=\"nofollow\" href=\"https:\/\/en.wikipedia.org\/wiki\/User_Datagram_Protocol\">UDP<\/a><\/noindex>kui transport protokolli. See on oluline reaalajas rakenduste, n\u00e4iteks videokonverentside ja m\u00e4ngude jaoks, kuna kohaletoimetamise ja paketide j\u00e4rjekorra tagamise protokoll <noindex><a rel=\"nofollow\" href=\"https:\/\/en.wikipedia.org\/wiki\/Transmission_Control_Protocol\">TCP<\/a><\/noindex> v\u00f5ivad h\u00e4irida madalaid latentsuseid.<\/p>\n<p>On ka kolmas tee - kasutada v\u00f5rku brauserist: <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> toetab nii usaldusv\u00e4\u00e4rset kui ka usaldusv\u00f5imetut edastamist (viimasel juhul p\u00fc\u00fctakse v\u00f5imaluse korral kasutada transportprotokollina UDP-d) ning seda saab rakendada nii kaugserveriga kui ka brauserite vahel. <strong>See t\u00e4hendab, et saame portida kogu rakenduse brauserisse, sealhulgas serverikomponendi!<\/strong><\/p>\n<p>Siiski on sellega seotud t\u00e4iendav keerukus: enne kui kaks WebRTC peer'i saavad andmeid vahetada, peavad nad l\u00e4bima suhteliselt keerulise \u201ek\u00e4epigistuse\u201c (handshake) protseduuri \u00fchenduse loomiseks, mille jaoks on vajalik mitme kolmanda osapoole \u00fcksuse kaasamine (signaaliserver ja \u00fcks v\u00f5i mitu <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>Idealjuhul sooviksime luua v\u00f5rguliidese, mis kasutab sisemiselt WebRTC-d, kuid on nii l\u00e4hedane UDP socketi liidesele, mille jaoks ei ole \u00fchenduse loomine vajalik.<\/p>\n<p>See v\u00f5imaldab meil kasutada WebRTC eeliseid, ilma et peaksime paljastama rakenduse koodi keerulisi \u00fcksikasju (mida soovisime oma projektis v\u00f5imalikult v\u00e4he muuta).<\/p>\n<h1>Minimaalne WebRTC<\/h1>\n<p>\nWebRTC on brauserite olev API-de kogum, mis v\u00f5imaldab peer-to-peer heli, videot ja mis tahes andmete edastamist.<\/p>\n<p>\u00dchendus kahe peer'i vahel luuakse (isegi kui NAT on kummalgi v\u00f5i kummalgi pool) STUN ja\/v\u00f5i TURN serverite abil mehhanismi nimega ICE kaudu. Peer'id vahetavad ICE teavet ja kanalite parameetreid l\u00e4bi SDP protokolli offer ja answer.<\/p>\n<p>Vau! Kui palju akron\u00fc\u00fcme korraga. Selgitatagem l\u00fchidalt, mida need m\u00f5isted t\u00e4hendavad:<\/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 protokoll NAT-i \u00fcletamiseks ja (IP, port) paari saamiseks, et vahetada andmeid otse hostiga. Kui see suudab oma \u00fclesande t\u00e4ita, saavad peer'id omavahel andmeid vahetada.<\/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> kasutatakse samuti NAT-i \u00fcletamiseks, kuid see teostab seda suunates andmeid l\u00e4bi proksi, mis on m\u00f5lemale peer'ile n\u00e4htav. See lisab viivituse ja on kallim t\u00e4ita kui STUN (kuna rakendatakse kogu suhtlemise seansi v\u00e4ltel), kuid m\u00f5nikord on see ainus v\u00f5imalik variant.<\/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> kasutatakse kahe peer'i parima \u00fchendusmeetodi valimiseks, tuginedes teabele, mis on saadud otse peer'ide \u00fchendamisel, ning teabele, mis on saadud m\u00f5nelt STUN ja TURN serverilt.<\/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> on \u00fchenduskanali parameetrite, nagu ICE kandidaadid, meedia kodeerijad (heli\/video kanali puhul) jne, kirjeldamise formaat... \u00dcks peer saadab SDP Offer (\u00abettepanek\u00bb), teine vastab SDP Answer (\u00abvastus\u00bb). P\u00e4rast seda luuakse kanal.<\/li>\n<\/ul>\n<p>\nSelle \u00fchenduse loomiseks peavad peer'id koguma teabe, mille nad said STUN ja TURN serveritelt, ja vahetama selle omavahel.<\/p>\n<p>Probleem on selles, et neil pole hetkel v\u00f5imalust andmeid otse vahetada, seega peab andmete vahetamiseks olema olemas v\u00e4line mehhanism: signalisatsiooniserver.<\/p>\n<p>Signalisatsiooniserver v\u00f5ib olla v\u00e4ga lihtne, sest selle ainus \u00fclesanne on andmete suunamine peer'ide vahel \u201ek\u00e4tlemise\u201c faasis (nagu on n\u00e4idatud alloleval skeemil).<\/p>\n<p><img decoding=\"async\" alt=\"Kandime mitmikm\u00e4ngu C++-st veebis Cheerp, WebRTC ja Firebase abil\" src=\"\/wp-content\/uploads\/2019\/09\/0fc39ba5e0942bc19183e8cf06c60868.jpg\" style=\"display:block;margin: 0 auto;\" \/><br \/>\n<i>Lihtsustatud skeem WebRTC k\u00e4tlemise j\u00e4rjestusest<\/i><\/p>\n<h1>Teeworldsi v\u00f5rgu mudeli \u00fclevaade<\/h1>\n<p>\nTeeworldsi v\u00f5rgueelarhitektuur on v\u00e4ga lihtne:<\/p>\n<ul>\n<li>Kliendi ja serveri komponendid on kaks erinevat programmi.<\/li>\n<li>Kliendid astuvad m\u00e4ngu, \u00fchendudes \u00fche mitmest serverist, millest iga\u00fcks hostib korraga ainult \u00fchte m\u00e4ngu.<\/li>\n<li>Kogu m\u00e4ngu andmete edastus toimub serveri kaudu.<\/li>\n<li>Eriline meistriserver kogub nimekirja k\u00f5igist avalikest serveritest, mis kuvatakse m\u00e4ngu kliendis.<\/li>\n<\/ul>\n<p>\nWebRTC kasutamine andmete vahetamiseks v\u00f5imaldab meil viia m\u00e4ngu serveri komponendi brauserisse, kus klient asub. See annab meile suurep\u00e4rase v\u00f5imaluse\u2026<\/p>\n<h1>Vabane serveritest<\/h1>\n<p>\nServeri loogika puudumisel on meeldiv eelis: saame rakenduse \u00fcles seada staatilise sisuna Github Pages'ile v\u00f5i oma seadmetele Cloudflare'i taga, tagades seel\u00e4bi tasuta kiire laadimisaja ja k\u00f5rge t\u00f6\u00f6aja. Tegelikult saame neist unustada ja kui meil veab ning m\u00e4ng muutub populaarseks, ei pea me infrastruktuuri uuendama.<\/p>\n<p>Kuid s\u00fcsteemi toimimiseks peame ikkagi kasutama v\u00e4list arhitektuuri:<\/p>\n<ul>\n<li>\u00dcks v\u00f5i mitu STUN serverit: meil on valida mitmete tasuta variantide vahel.<\/li>\n<li>V\u00e4hemalt \u00fcks TURN server: siin ei ole tasuta variante, seega v\u00f5ime kas seadistada oma v\u00f5i maksta teenuse eest. \u00d5nneks saab enamikul juhtudel \u00fchenduse luua STUN serverite kaudu (tagades t\u00f5elise p2p), kuid TURN on vajalik varuvariantina.<\/li>\n<li>Signaaliserver: erinevalt kahest teisest aspektist ei ole signalisatsioon standardiseeritud. See, mille eest signaaliserver t\u00f5eliselt vastutab, s\u00f5ltub osaliselt rakendusest. Meie puhul tuleb enne \u00fchenduse loomist vahetada v\u00e4ike hulk andmeid.<\/li>\n<li>Teeworldsi meistriserver: seda kasutatakse teiste serverite poolt oma olemasolu teavitamiseks ja klientide poolt avalike serverite otsimiseks. Kuigi see ei ole kohustuslik (kliendid saavad alati \u00fchenduda tuntud serveriga k\u00e4sitsi), oleks hea, kui see oleks olemas, et m\u00e4ngijad saaksid osaleda m\u00e4ngudes juhuslike inimestega.<\/li>\n<\/ul>\n<p>\nOleme otsustanud kasutada Google'i tasuta STUN servereid ja \u00fche TURN serveri oleme seadistanud ise.<\/p>\n<p>Viimaste kahe punkti jaoks kasutasime <noindex><a rel=\"nofollow\" href=\"https:\/\/en.wikipedia.org\/wiki\/Firebase\">Firebase<\/a><\/noindex>:<\/p>\n<ul>\n<li>Teeworldsi mahitusserver on ellu viidatud v\u00e4ga lihtsalt: see on objektide nimekiri, mis sisaldab iga aktiivse serveri teavet (nimi, IP, kaart, re\u017eiim, \u2026). Serverid avaldavad ja uuendavad oma objekti, samas kui kliendid v\u00f5tavad kogu nimekirja ja kuvavad selle m\u00e4ngijale. Kuvame nimekirja ka kodulehel HTMLina, et m\u00e4ngijad saaksid lihtsalt serverile kl\u00f5psata ja otse m\u00e4ngu minna.<\/li>\n<li>Signalisatsioon on tihedalt seotud meie soketite elluviimisega, mis on kirjeldatud j\u00e4rgmises jaotises.<\/li>\n<\/ul>\n<p>\n<img decoding=\"async\" alt=\"Kandime mitmikm\u00e4ngu C++-st veebis Cheerp, WebRTC ja Firebase abil\" src=\"\/wp-content\/uploads\/2019\/09\/5e30bc22643f8baecf850053bf69e130.jpg\" style=\"display:block;margin: 0 auto;\" \/><br \/>\n<i>Serverite loetelu m\u00e4ngus ja kodulehel<\/i><\/p>\n<h1>Sokettide elluviimine<\/h1>\n<p>\nSoovime luua API, mis on v\u00f5imalikult l\u00e4hedane Posix UDP soketitele, et minimeerida vajalike muudatuste hulka.<\/p>\n<p>Samuti soovime rakendada minimaalset, mis on vajalik lihtsaimaks andmevahetuseks \u00fcle v\u00f5rgu.<\/p>\n<p>N\u00e4iteks ei vaja me t\u00f5elist marsruutimist: k\u00f5ik peers asuvad \u00fches \"virtuaalses LANis\", mis on seotud konkreetse Firebase andmebaasi instantsiga.<\/p>\n<p>Seet\u00f5ttu ei vaja me ainulaadseid IP-aadresse: piisab, kui kasutada peerside unikaalsete Firebase v\u00f5tmearvude (sarnane domeeninimedele) p\u00f5hjal ainulaadset tuvastamist ning iga peer m\u00e4\u00e4rab iga v\u00f5tme jaoks kohalikult \"vale\" IP-aadressi, mille tuleb teisendada. See vabastab meid t\u00e4ielikult globaalsete IP-aadresside m\u00e4\u00e4ramise vajadusest, mis on mitte trivialne \u00fclesanne.<\/p>\n<p>Siin on minimaalne API, mille peame rakendama:<\/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 on lihtne ja sarnaneb Posix sokettide APIle, kuid tal on m\u00f5ned olulised erinevused: <strong>tagasikutsumise registreerimine, kohalike IP-de m\u00e4\u00e4ramine ja \"liskontakt\"<\/strong>.<\/p>\n<h2>Tagasikutsumise registreerimine<\/h2>\n<p>\nIeven kui algne programm kasutab mittesaggivat sisend-v\u00e4ljundit, tuleb koodi veebibrauseris k\u00e4ivitamiseks refaktoreerida.<\/p>\n<p>Selle p\u00f5hjuseks on see, et brauseri s\u00fcndmuste ts\u00fckkel on programmilt varjatud (olgu see siis JavaScript v\u00f5i WebAssembly).<\/p>\n<p>Kohalikus keskkonnas saame kirjutada koodi j\u00e4rgmiselt<\/p>\n<pre><code class=\"cpp\">while(running) {\n  select(...); \/\/ oota I\/O s\u00fcndmusi\n  while(true) {\n    int r = readfrom(...); \/\/ proovi lugeda\n    if (r &lt; 0 &amp;&amp; errno == EWOULDBLOCK) \/\/ ei rohkem andmeid saadaval\n      break;\n    ...\n  }\n  ...\n}<\/code><\/pre>\n<p>\nKui s\u00fcndmuste ts\u00fckkel on meie jaoks varjatud, tuleb see muuta millekski selliseks:<\/p>\n<pre><code class=\"cpp\">auto cb = []() { \/\/ see kutsub v\u00e4lja, kui uus teave on saadaval\n  while(true) {\n    int r = readfrom(...); \/\/ proovi lugeda\n    if (r &lt; 0 &amp;&amp; errno == EWOULDBLOCK) \/\/ ei rohkem andmeid saadaval\n      break;\n    ...\n  }\n  ...\n};\nrecvCallback(cb); \/\/ registreeri tagasikutsumine<\/code><\/pre>\n<p><\/p>\n<h2>Kohalike IP-de m\u00e4\u00e4ramine<\/h2>\n<p>\nS\u00f5lmide identifikaatorid meie \"v\u00f5rgus\" ei ole IP-aadressid, vaid Firebase'i v\u00f5tmed (need on stringid, mis n\u00e4evad v\u00e4lja nii: <code>-LmEC50PYZLCiCP-vqde<\/code> ).<\/p>\n<p>See on mugav, kuna me ei vaja mehhanismi IP-de m\u00e4\u00e4ramiseks ja nende unikaalsuse kontrollimiseks (ka nende utiliseerimine p\u00e4rast kliendi v\u00e4ljal\u00fclitamist), kuid sageli on vajalik s\u00fcnkroniseerida peer'e numbrilise v\u00e4\u00e4rtuse j\u00e4rgi.<\/p>\n<p>Just selleks kasutatakse funktsioone <code>resolve<\/code> ja <code>reverseResolve<\/code>: rakendus saab somehow stringi v\u00f5tme v\u00e4\u00e4rtuse (kasutaja sisendi v\u00f5i meister-serveri kaudu) ja suudab selle sisendada IP-aadressiks sisemiselt kasutamiseks. Puuduvad ka API teised osad selle lihtsuse huvides saavad selle v\u00e4\u00e4rtuse stringi asemel.<\/p>\n<p>See meenutab DNS-i otsingut, ainult et see toimub kliendi kohapeal.<\/p>\n<p>Teisis\u00f5nu, IP-aadresse ei saa jagada erinevate klientide vahel, ja kui on vajalik mingi globaalne identifikaator, siis tuleb see genereerida muul viisil.<\/p>\n<h2>Laine\u00fchendus<\/h2>\n<p>\nUDP ei vaja \u00fchendust, kuid nagu oleme n\u00e4inud, peab WebRTC enne kahe peer'i andmevahetuse alustamist l\u00e4bi viima pika \u00fchendamisprotsessi.<\/p>\n<p>Kui soovime tagada sama abstraktsiooni taseme, (<code>sendto<\/code>\/<code>recvfrom<\/code> suvaliste peer'idega eelneva \u00fchendamiseta), siis peame API-s l\u00e4bi viima \"laisk\" (edasi l\u00fckatud) \u00fchendamise.<\/p>\n<p>Nii toimub tavap\u00e4rane andmevahetus \"serveri\" ja \"kliendi\" vahel UDP kasutamisel ning mida peab meie teek t\u00e4itma:<\/p>\n<ul>\n<li>Server kutsub \u00fcles <code>bind()<\/code>, et teavitada operatsioonis\u00fcsteemi, et soovib saada pakette m\u00e4\u00e4ratud sadamasse.<\/li>\n<\/ul>\n<p>\nSelle asemel avaldame Firebase'is avatud sadama serveri v\u00f5tme all ja kuulame selle aladressi s\u00fcndmusi.<\/p>\n<ul>\n<li>Server kutsub \u00fcles <code>recvfrom()<\/code>, v\u00f5ttes vastu pakette, mis tulevad mistahes hostilt selle sadama sisse.<\/li>\n<\/ul>\n<p>\nMeie juhul peame kontrollima sissetulevate pakettide j\u00e4rjekorda, mis on saadetud sellele sadamale.<\/p>\n<p>Igal sadamal on oma j\u00e4rjekord, ja me lisame WebRTC datagrammidesse alg- ja l\u00f5ppsadama, et teada, kuhu suunata uus pakk, kui see saabub.<\/p>\n<p>Kutse on mittesurmav, seega kui pakette ei ole, siis tagastame lihtsalt -1 ja seadistame <code>errno=EWOULDBLOCK<\/code>.<\/p>\n<ul>\n<li>Klient saab mitmete v\u00e4liste vahendite kaudu serveri IP ja sadama ning kutsub v\u00e4lja <code>sendto()<\/code>. Samuti tehakse sisemine kutse <code>bind()<\/code>, seega j\u00e4rgnev <code>recvfrom()<\/code> saab vastuse ilma selge 'bind' teostamiseta.<\/li>\n<\/ul>\n<p>\nKliendile antakse stringi v\u00f5ti ja kasutatakse funktsiooni <code>resolve()<\/code> IP-aadressi saamiseks.<\/p>\n<p>Sellel etapil alustame WebRTC \"k\u00e4epigistust\", kui kaks peer'it ei ole veel omavahel \u00fchendatud. Erinevatele pordile \u00fchendused kasutavad sama WebRTC DataChannel'i.<\/p>\n<p>Samuti teeme kaudse <code>bind()<\/code>, et server saaks \u00fchenduse j\u00e4rgmisel korral taastada, <code>sendto()<\/code> kui see mingil p\u00f5hjusel suleti.<\/p>\n<p>Server saab teate kliendi \u00fchendusest, kui klient salvestab oma SDP pakkumise serveri sadama teabe alla Firebase'is ning server vastab seal samas oma vastusega.<\/p>\n<p>\nAlloleval skeemil on n\u00e4idatud s\u00f5numite liikumise n\u00e4ide soketite skeemi ja esimesed s\u00f5numid kliendilt serverisse edastamisel:<\/p>\n<p><img decoding=\"async\" alt=\"Kandime mitmikm\u00e4ngu C++-st veebis Cheerp, WebRTC ja Firebase abil\" src=\"\/wp-content\/uploads\/2019\/09\/7dc48e816c09e33daf28c307cad0c258.jpg\" style=\"display:block;margin: 0 auto;\" \/><br \/>\n<i>T\u00e4ielik \u00fchenduse loomise etapi skeem kliendi ja serveri vahel<\/i><\/p>\n<h1>Kokkuv\u00f5te<\/h1>\n<p>\nKui olete l\u00f5puni lugenud, siis ilmselt huvitab teid teooria praktikas. M\u00e4ngu saab m\u00e4ngida aadressil <noindex><a rel=\"nofollow\" href=\"https:\/\/teeworlds.leaningtech.com\">teeworlds.leaningtech.com<\/a><\/noindex>, proovige kindlasti!<\/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>S\u00f5bralik m\u00e4ng kolleegide vahel<\/i><\/p>\n<p>Tulem\u00fc\u00fcgiteegi koodi on vabalt saadaval <noindex><a rel=\"nofollow\" href=\"https:\/\/github.com\/leaningtech\/cheerpnet\">Github<\/a><\/noindex>. Liituge meie kanaliga <noindex><a rel=\"nofollow\" href=\"https:\/\/gitter.im\/leaningtech\/cheerp\">Gitter<\/a><\/noindex>!<br \/>\n<br \/>Allikas: <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.1.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\/et\/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.1.1\" \/>\n\t\t<meta property=\"og:locale\" content=\"et_EE\" \/>\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\/et\/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\udd47\u00dcleviime mitme m\u00e4ngijaga m\u00e4ngu C++-st veebisse Cheerp, WebRTC ja Firebase'iga | ProHoster","description":"Sissejuhatus Meie ettev\u00f5te Leaning Technologies pakub lahendusi traditsiooniliste desktop-rakenduste \u00fcleviimiseks veebisse.","canonical_url":"https:\/\/prohoster.info\/et\/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":"et_EE","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\/et\/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\/et\/wp-json\/wp\/v2\/posts\/38370","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/prohoster.info\/et\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/prohoster.info\/et\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/prohoster.info\/et\/wp-json\/wp\/v2\/users\/1"}],"replies":[{"embeddable":true,"href":"https:\/\/prohoster.info\/et\/wp-json\/wp\/v2\/comments?post=38370"}],"version-history":[{"count":0,"href":"https:\/\/prohoster.info\/et\/wp-json\/wp\/v2\/posts\/38370\/revisions"}],"wp:featuredmedia":[{"embeddable":true,"href":"https:\/\/prohoster.info\/et\/wp-json\/wp\/v2\/media\/28801"}],"wp:attachment":[{"href":"https:\/\/prohoster.info\/et\/wp-json\/wp\/v2\/media?parent=38370"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/prohoster.info\/et\/wp-json\/wp\/v2\/categories?post=38370"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/prohoster.info\/et\/wp-json\/wp\/v2\/tags?post=38370"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}