{"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\/pl\/blog\/administrirovanie\/portiruem-mnogopolzovatelskuyu-igru-s-s-na-veb-c-cheerp-webrtc-i-firebase","title":{"rendered":"Portujemy gr\u0119 wieloosobow\u0105 z C++ na web z Cheerp, WebRTC i Firebase","gt_translate_keys":[{"key":"rendered","format":"text"}]},"content":{"rendered":"<h2>Wprowadzenie<\/h2>\n<p>\nNasza firma <noindex><a rel=\"nofollow\" href=\"https:\/\/leaningtech.com\">Leaning Technologies<\/a><\/noindex> oferuje rozwi\u0105zania do portowania tradycyjnych aplikacji desktopowych do sieci. Nasz kompilator C++ <noindex><a rel=\"nofollow\" href=\"https:\/\/github.com\/leaningtech\/cheerp-meta\">Cheerp<\/a><\/noindex> generuje po\u0142\u0105czenie WebAssembly i JavaScript, co zapewnia zar\u00f3wno <noindex><a rel=\"nofollow\" href=\"https:\/\/github.com\/leaningtech\/cheerp-meta\/wiki\/Cheerp-Tutorial%3A-Mixed-mode-C++-to-WebAssembly-and-JavaScript\">proste interakcje z przegl\u0105dark\u0105<\/a><\/noindex>, jak i wysok\u0105 wydajno\u015b\u0107.<\/p>\n<p>Jako przyk\u0142ad jego zastosowania postanowili\u015bmy przenie\u015b\u0107 do sieci gr\u0119 wieloosobow\u0105 i wybrali\u015bmy do tego <noindex><a rel=\"nofollow\" href=\"https:\/\/www.teeworlds.com\/\"><strong>Teeworlds<\/strong><\/a><\/noindex>. Teeworlds to wieloosobowa, dwuwymiarowa gra retro z niewielk\u0105, ale aktywn\u0105 spo\u0142eczno\u015bci\u0105 graczy (w tym mnie!). Jest ma\u0142a zar\u00f3wno pod wzgl\u0119dem pobieranych zasob\u00f3w, jak i wymaga\u0144 dotycz\u0105cych CPU i GPU \u2014 idealny kandydat.<\/p>\n<p><img decoding=\"async\" alt=\"Portujemy gr\u0119 wieloosobow\u0105 z C++ na web z Cheerp, WebRTC i Firebase\" src=\"\/wp-content\/uploads\/2019\/09\/30bff36ee1158ddea2e76d8b11e48d2b.jpg\" style=\"display:block;margin: 0 auto;\" \/><br \/>\n<i>Dzia\u0142aj\u0105ca w przegl\u0105darce Teeworlds<\/i><br \/>\n<noindex><a rel=\"nofollow\" name=\"habracut\"><\/a><\/noindex><br \/>\nZdecydowali\u015bmy si\u0119 wykorzysta\u0107 ten projekt do eksperymentowania z <strong>og\u00f3lnymi rozwi\u0105zaniami do portowania kodu sieciowego do sieci<\/strong>. Zazwyczaj mo\u017cna to osi\u0105gn\u0105\u0107 na kilka sposob\u00f3w:<\/p>\n<ul>\n<li><strong>XMLHttpRequest\/fetch<\/strong>, je\u015bli cz\u0119\u015b\u0107 sieciowa sk\u0142ada si\u0119 wy\u0142\u0105cznie z zapyta\u0144 HTTP, lub<\/li>\n<li><strong>WebSockets<\/strong>.<\/li>\n<\/ul>\n<p>\nOba rozwi\u0105zania wymagaj\u0105 hostowania komponentu serwerowego po stronie serwera, i \u017cadne z nich nie pozwala na u\u017cycie jako protoko\u0142u transportowego <noindex><a rel=\"nofollow\" href=\"https:\/\/en.wikipedia.org\/wiki\/User_Datagram_Protocol\">UDP<\/a><\/noindex>. Jest to istotne dla aplikacji czasu rzeczywistego, takich jak oprogramowanie do wideokonferencji i gry, poniewa\u017c gwarancje dostarczania i porz\u0105dku pakiet\u00f3w protoko\u0142u <noindex><a rel=\"nofollow\" href=\"https:\/\/en.wikipedia.org\/wiki\/Transmission_Control_Protocol\">TCP<\/a><\/noindex> mog\u0105 zak\u0142\u00f3ca\u0107 niskie op\u00f3\u017anienia.<\/p>\n<p>Jest te\u017c trzeci spos\u00f3b \u2014 u\u017cycie sieci z przegl\u0105darki: <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> obs\u0142uguje zar\u00f3wno niezawodne, jak i niezawodne przesy\u0142anie (w drugim przypadku stara si\u0119 wykorzysta\u0107 jako protok\u00f3\u0142 transportowy UDP), i mo\u017ce by\u0107 stosowane zar\u00f3wno z serwerem zdalnym, jak i mi\u0119dzy przegl\u0105darkami. <strong>To oznacza, \u017ce mo\u017cemy portowa\u0107 ca\u0142e aplikacj\u0119 do przegl\u0105darki, w tym komponent serwerowy!<\/strong><\/p>\n<p>Jednak wi\u0105\u017ce si\u0119 to z dodatkow\u0105 trudno\u015bci\u0105: zanim dwa w\u0119z\u0142y WebRTC b\u0119d\u0105 mog\u0142y wymienia\u0107 dane, musz\u0105 przej\u015b\u0107 przez stosunkowo skomplikowan\u0105 procedur\u0119 \u201eu\u015bcisku d\u0142oni\u201d (handshake) w celu po\u0142\u0105czenia, co wymaga kilku zewn\u0119trznych podmiot\u00f3w (serwer sygnalizacyjny i jeden lub wi\u0119cej serwer\u00f3w <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>W idealnym przypadku chcieliby\u015bmy stworzy\u0107 sieciowe API, kt\u00f3re wewn\u0119trznie wykorzystuje WebRTC, ale by\u0142o jak najbardziej zbli\u017cone do interfejsu gniazd UDP, kt\u00f3re nie wymaga ustanawiania po\u0142\u0105czenia.<\/p>\n<p>To pozwoli nam wykorzysta\u0107 zalety WebRTC bez konieczno\u015bci ujawniania skomplikowanych szczeg\u00f3\u0142\u00f3w kodu aplikacji (kt\u00f3ry w naszym projekcie chcieli\u015bmy zmienia\u0107 tak ma\u0142o, jak to mo\u017cliwe).<\/p>\n<h1>Minimalny WebRTC<\/h1>\n<p>\nWebRTC to zestaw interfejs\u00f3w API dost\u0119pnych w przegl\u0105darkach, umo\u017cliwiaj\u0105cy wymian\u0119 d\u017awi\u0119ku, wideo i dowolnych danych w trybie peer-to-peer.<\/p>\n<p>Po\u0142\u0105czenie mi\u0119dzy parami jest ustanawiane (nawet w przypadku istnienia NAT z jednej lub obu stron) przy u\u017cyciu serwer\u00f3w STUN i\/lub TURN przez mechanizm zwany ICE. Parzy wymieniaj\u0105 informacje ICE oraz parametry kana\u0142\u00f3w za pomoc\u0105 oferty i odpowiedzi z protoko\u0142u SDP.<\/p>\n<p>Wow! Ile akronim\u00f3w na raz. Podsumujmy, co te poj\u0119cia oznaczaj\u0105:<\/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> to protok\u00f3\u0142 do omijania NAT i uzyskiwania pary (adres IP, port) do bezpo\u015bredniej wymiany danych z hostem. Je\u015bli uda mu si\u0119 to zrobi\u0107, pary mog\u0105 samodzielnie wymienia\u0107 si\u0119 danymi.<\/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> r\u00f3wnie\u017c jest u\u017cywany do omijania NAT, ale realizuje to przez przekierowywanie danych przez proxy, widoczne dla obu par. Wprowadza to op\u00f3\u017anienie i jest bardziej kosztowne w wykonaniu ni\u017c STUN (poniewa\u017c jest stosowane przez ca\u0142y czas trwania sesji), ale czasami jest to jedyna mo\u017cliwa opcja.<\/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> jest u\u017cywany do wyboru najlepszego mo\u017cliwego sposobu po\u0142\u0105czenia dw\u00f3ch par w oparciu o informacje uzyskane podczas bezpo\u015bredniego po\u0142\u0105czenia par, jak r\u00f3wnie\u017c informacje uzyskane od dowolnej liczby serwer\u00f3w STUN i 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> to format opisu parametr\u00f3w kana\u0142u po\u0142\u0105czenia, takich jak kandydaci ICE, kodeki multimedialne (w przypadku kana\u0142u audio\/wideo) itd. Jeden z par wysy\u0142a ofert\u0119 SDP (\u201eofert\u0119\u201d), a drugi odpowiada odpowiedzi\u0105 SDP (\u201eodpowiedzi\u0105\u201d). Po tym tworzy si\u0119 kana\u0142.<\/li>\n<\/ul>\n<p>\nAby stworzy\u0107 takie po\u0142\u0105czenie, pary musz\u0105 zebra\u0107 informacje uzyskane od serwer\u00f3w STUN i TURN i wymieni\u0107 si\u0119 nimi.<\/p>\n<p>Problem polega na tym, \u017ce wci\u0105\u017c nie maj\u0105 mo\u017cliwo\u015bci wymiany danych bezpo\u015brednio, dlatego musi istnie\u0107 zewn\u0119trzny mechanizm do wymiany tych danych: serwer sygnalizacyjny.<\/p>\n<p>Serwer sygnalizacyjny mo\u017ce by\u0107 bardzo prosty, poniewa\u017c jego jedynym zadaniem jest przekazywanie danych mi\u0119dzy parami na etapie \u201eu\u015bciski d\u0142oni\u201d (jak pokazano na schemacie poni\u017cej).<\/p>\n<p><img decoding=\"async\" alt=\"Portujemy gr\u0119 wieloosobow\u0105 z C++ na web z Cheerp, WebRTC i Firebase\" src=\"\/wp-content\/uploads\/2019\/09\/0fc39ba5e0942bc19183e8cf06c60868.jpg\" style=\"display:block;margin: 0 auto;\" \/><br \/>\n<i>Uproszczony schemat sekwencji \u201eu\u015bciski d\u0142oni\u201d WebRTC<\/i><\/p>\n<h1>Przegl\u0105d modelu sieciowego Teeworlds<\/h1>\n<p>\nArchitektura sieciowa Teeworlds jest bardzo prosta:<\/p>\n<ul>\n<li>Sk\u0142adniki klienta i serwera to dwa r\u00f3\u017cne programy.<\/li>\n<li>Klienci wchodz\u0105 w gr\u0119, \u0142\u0105cz\u0105c si\u0119 z jednym z kilku serwer\u00f3w, z kt\u00f3rych ka\u017cdy jednocze\u015bnie hostuje tylko jedn\u0105 gr\u0119.<\/li>\n<li>Ca\u0142a wymiana danych w grze odbywa si\u0119 przez serwer.<\/li>\n<li>Specjalny serwer masterowy s\u0142u\u017cy do zbierania listy wszystkich publicznych serwer\u00f3w, kt\u00f3re s\u0105 wy\u015bwietlane w kliencie gry.<\/li>\n<\/ul>\n<p>\nDzi\u0119ki wykorzystaniu WebRTC do wymiany danych mo\u017cemy przenie\u015b\u0107 komponent serwera gry do przegl\u0105darki, w kt\u00f3rej jest klient. To daje nam doskona\u0142\u0105 mo\u017cliwo\u015b\u0107\u2026<\/p>\n<h1>Rezygnacja z serwer\u00f3w<\/h1>\n<p>\nBrak logiki serwerowej ma przyjemn\u0105 zalet\u0119: mo\u017cemy wdro\u017cy\u0107 ca\u0142\u0105 aplikacj\u0119 jako statyczn\u0105 zawarto\u015b\u0107 na Github Pages lub na w\u0142asnym sprz\u0119cie za Cloudflare, zapewniaj\u0105c sobie w ten spos\u00f3b darmowe szybkie \u0142adowanie i wysoki czas pracy. W zasadzie mo\u017cna o nich zapomnie\u0107, a je\u015bli b\u0119dziemy mieli szcz\u0119\u015bcie i gra stanie si\u0119 popularna, nie b\u0119dzie potrzeby modernizacji infrastruktury.<\/p>\n<p>Jednak aby system dzia\u0142a\u0142, b\u0119dziemy musieli korzysta\u0107 z zewn\u0119trznej architektury:<\/p>\n<ul>\n<li>Jeden lub kilka serwer\u00f3w STUN: mamy do wyboru kilka darmowych opcji.<\/li>\n<li>Co najmniej jeden serwer TURN: tutaj nie ma darmowych opcji, wi\u0119c mo\u017cemy albo skonfigurowa\u0107 w\u0142asny, albo p\u0142aci\u0107 za us\u0142ug\u0119. Na szcz\u0119\u015bcie, przez wi\u0119kszo\u015b\u0107 czasu po\u0142\u0105czenie mo\u017cna nawi\u0105za\u0107 przez serwery STUN (zapewniaj\u0105c prawdziwy p2p), ale TURN jest potrzebny jako opcja zapasowa.<\/li>\n<li>Serwer sygnalizacyjny: w przeciwie\u0144stwie do dw\u00f3ch innych aspekt\u00f3w, sygnalizacja nie jest standaryzowana. To, za co w\u0142a\u015bciwie b\u0119dzie odpowiedzialny serwer sygnalizacyjny, cz\u0119\u015bciowo zale\u017cy od aplikacji. W naszym przypadku przed nawi\u0105zaniem po\u0142\u0105czenia konieczna jest wymiana niewielkiej ilo\u015bci danych.<\/li>\n<li>Serwer masterowy Teeworlds: jest u\u017cywany przez inne serwery do powiadamiania o swojej obecno\u015bci i przez klient\u00f3w do wyszukiwania publicznych serwer\u00f3w. Chocia\u017c nie jest obowi\u0105zkowy (klienci zawsze mog\u0105 r\u0119cznie po\u0142\u0105czy\u0107 si\u0119 z znanym im serwerem), by\u0142oby dobrze go mie\u0107, aby gracze mogli bra\u0107 udzia\u0142 w grach z przypadkowymi osobami.<\/li>\n<\/ul>\n<p>\nPostanowili\u015bmy skorzysta\u0107 z darmowych serwer\u00f3w STUN firmy Google, a jeden serwer TURN wdro\u017cyli\u015bmy samodzielnie.<\/p>\n<p>Do dw\u00f3ch ostatnich punkt\u00f3w wykorzystali\u015bmy <noindex><a rel=\"nofollow\" href=\"https:\/\/en.wikipedia.org\/wiki\/Firebase\">Firebase<\/a><\/noindex>:<\/p>\n<ul>\n<li>Serwer g\u0142\u00f3wny Teeworlds jest bardzo prosty w realizacji: to lista obiekt\u00f3w zawieraj\u0105cych informacje (nazwa, IP, mapa, tryb, ...) ka\u017cdego aktywnego serwera. Serwery publikuj\u0105 i aktualizuj\u0105 swoje w\u0142asne obiekty, a klienci pobieraj\u0105 ca\u0142\u0105 list\u0119 i wy\u015bwietlaj\u0105 j\u0105 graczowi. Dodatkowo wy\u015bwietlamy list\u0119 na stronie g\u0142\u00f3wnej jako HTML, aby gracze mogli po prostu klikn\u0105\u0107 na serwer i bezpo\u015brednio wej\u015b\u0107 do gry.<\/li>\n<li>Sygnalizacja jest \u015bci\u015ble zwi\u0105zana z nasz\u0105 implementacj\u0105 gniazd, opisan\u0105 w nast\u0119pnym rozdziale.<\/li>\n<\/ul>\n<p>\n<img decoding=\"async\" alt=\"Portujemy gr\u0119 wieloosobow\u0105 z C++ na web z Cheerp, WebRTC i Firebase\" src=\"\/wp-content\/uploads\/2019\/09\/5e30bc22643f8baecf850053bf69e130.jpg\" style=\"display:block;margin: 0 auto;\" \/><br \/>\n<i>Lista serwer\u00f3w w grze i na stronie g\u0142\u00f3wnej<\/i><\/p>\n<h1>Implementacja gniazd<\/h1>\n<p>\nChcemy stworzy\u0107 API jak najbli\u017csze do gniazd Posix UDP, aby zminimalizowa\u0107 ilo\u015b\u0107 wymaganych zmian.<\/p>\n<p>Chcemy r\u00f3wnie\u017c zaimplementowa\u0107 minimalny zestaw funkcji wymaganych do najprostszej wymiany danych przez sie\u0107.<\/p>\n<p>Na przyk\u0142ad, nie potrzebujemy prawdziwego routingu: wszystkie w\u0119z\u0142y znajduj\u0105 si\u0119 w jednej \"wirtualnej LAN\", po\u0142\u0105czonej z konkretnym egzemplarzem bazy danych Firebase.<\/p>\n<p>W zwi\u0105zku z tym nie potrzebujemy unikalnych adres\u00f3w IP: do unikalnej identyfikacji w\u0119z\u0142\u00f3w wystarcz\u0105 unikalne warto\u015bci kluczy Firebase (analogicznie do nazw domen), a ka\u017cdy w\u0119ze\u0142 lokalnie przypisuje \"fa\u0142szywe\" adresy IP do ka\u017cdego klucza, kt\u00f3ry wymaga konwersji. Ca\u0142kowicie eliminuje to potrzeb\u0119 globalnego przypisywania adres\u00f3w IP, co jest zadaniem nie\u0142atwym.<\/p>\n<p>Oto minimalne API, kt\u00f3re musimy zaimplementowa\u0107:<\/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 jest proste i podobne do API gniazd Posix, ale ma kilka istotnych r\u00f3\u017cnic: <strong>rejestracja wywo\u0142a\u0144 zwrotnych, przypisanie lokalnych adres\u00f3w IP i \u201eleniwe\u201d \u0142\u0105czenie<\/strong>.<\/p>\n<h2>Rejestracja wywo\u0142a\u0144 zwrotnych<\/h2>\n<p>\nNawet je\u015bli oryginalny program u\u017cywa asynchronicznego wej\u015bcia-wyj\u015bcia, aby uruchomi\u0107 go w przegl\u0105darce, kod musi zosta\u0107 zrefaktoryzowany.<\/p>\n<p>Powodem tego jest to, \u017ce p\u0119tla zdarze\u0144 w przegl\u0105darce jest ukryta przed programem (czy to JavaScript, czy WebAssembly).<\/p>\n<p>W \u015brodowisku natywnym mo\u017cemy pisa\u0107 kod w ten spos\u00f3b<\/p>\n<pre><code class=\"cpp\">while(running) {\n  select(...); \/\/ czekaj na zdarzenia I\/O\n  while(true) {\n    int r = readfrom(...); \/\/ pr\u00f3buj odczyta\u0107\n    if (r &lt; 0 &amp;&amp; errno == EWOULDBLOCK) \/\/ nie ma wi\u0119cej dost\u0119pnych danych\n      break;\n    ...\n  }\n  ...\n}<\/code><\/pre>\n<p>\nJe\u015bli p\u0119tla zdarze\u0144 jest dla nas ukryta, to musimy przekszta\u0142ci\u0107 j\u0105 w co\u015b podobnego:<\/p>\n<pre><code class=\"cpp\">auto cb = []() { \/\/ to zostanie wywo\u0142ane, gdy nowe dane b\u0119d\u0105 dost\u0119pne\n  while(true) {\n    int r = readfrom(...); \/\/ pr\u00f3buj odczyta\u0107\n    if (r &lt; 0 &amp;&amp; errno == EWOULDBLOCK) \/\/ nie ma wi\u0119cej dost\u0119pnych danych\n      break;\n    ...\n  }\n  ...\n};\nrecvCallback(cb); \/\/ zarejestruj wywo\u0142anie zwrotne<\/code><\/pre>\n<p><\/p>\n<h2>Przypisanie lokalnych adres\u00f3w IP<\/h2>\n<p>\nIdentyfikatory w\u0119z\u0142\u00f3w w naszej \"sieci\" nie s\u0105 adresami IP, lecz kluczami Firebase (s\u0105 to ci\u0105gi, kt\u00f3re wygl\u0105daj\u0105 tak: <code>-LmEC50PYZLCiCP-vqde<\/code> ).<\/p>\n<p>Jest to wygodne, poniewa\u017c nie potrzebujemy mechanizmu do przydzielania adres\u00f3w IP i sprawdzania ich unikalno\u015bci (ani ich utylizacji po dezaktywacji klienta), ale cz\u0119sto konieczne jest identyfikowanie peer\u00f3w za pomoc\u0105 warto\u015bci numerycznej.<\/p>\n<p>W\u0142a\u015bnie dlatego u\u017cywa si\u0119 funkcji <code>resolve<\/code> i <code>reverseResolve<\/code>: aplikacja w jaki\u015b spos\u00f3b uzyskuje ci\u0105gowy klucz (poprzez wprowadzenie przez u\u017cytkownika lub za pomoc\u0105 serwera g\u0142\u00f3wnego) i mo\u017ce zamieni\u0107 go na adres IP do u\u017cytku wewn\u0119trznego. Pozosta\u0142a cz\u0119\u015b\u0107 API r\u00f3wnie\u017c dla uproszczenia otrzymuje zamiast tego warto\u015b\u0107 jako ci\u0105g.<\/p>\n<p>Przypomina to zapytanie DNS, ale odbywa si\u0119 lokalnie u klienta.<\/p>\n<p>Oznacza to, \u017ce adresy IP nie mog\u0105 by\u0107 wsp\u00f3\u0142dzielone przez r\u00f3\u017cnych klient\u00f3w, a je\u015bli potrzebny jest jaki\u015b globalny identyfikator, trzeba go wygenerowa\u0107 w inny spos\u00f3b.<\/p>\n<h2>Leniwe po\u0142\u0105czenie<\/h2>\n<p>\nUDP nie wymaga po\u0142\u0105czenia, ale, jak widzieli\u015bmy, przed rozpocz\u0119ciem przesy\u0142ania danych mi\u0119dzy dwoma peerami, WebRTC wymaga d\u0142ugiego procesu nawi\u0105zywania po\u0142\u0105czenia.<\/p>\n<p>Je\u015bli chcemy zapewni\u0107 ten sam poziom abstrakcji, (<code>sendto<\/code>\/<code>recvfrom<\/code> z dowolnymi peerami bez wcze\u015bniejszego po\u0142\u0105czenia), musimy wykona\u0107 \u201eleniwe\u201d (od\u0142o\u017cone) po\u0142\u0105czenie wewn\u0105trz API.<\/p>\n<p>Oto, co si\u0119 dzieje podczas zwyk\u0142ej wymiany danych mi\u0119dzy \u201eserwerem\u201d a \u201eklientem\u201d w przypadku u\u017cycia UDP i co musi zrobi\u0107 nasza biblioteka:<\/p>\n<ul>\n<li>Serwer wywo\u0142uje <code>bind()<\/code>, aby poinformowa\u0107 system operacyjny, \u017ce chce otrzymywa\u0107 pakiety na okre\u015blonym porcie.<\/li>\n<\/ul>\n<p>\nZamiast tego opublikujemy otwarty port w Firebase pod kluczem serwera i b\u0119dziemy nas\u0142uchiwa\u0107 zdarze\u0144 w jego poddrzewie.<\/p>\n<ul>\n<li>Serwer wywo\u0142uje <code>recvfrom()<\/code>, odbieraj\u0105c na ten port pakiety przychodz\u0105ce z dowolnego hosta.<\/li>\n<\/ul>\n<p>\nW naszym przypadku nale\u017cy sprawdza\u0107 kolejk\u0119 przychodz\u0105cych pakiet\u00f3w wysy\u0142anych na ten port.<\/p>\n<p>Ka\u017cdy port ma swoj\u0105 w\u0142asn\u0105 kolejk\u0119, a my dodajemy na pocz\u0105tku datagram\u00f3w WebRTC porty \u017ar\u00f3d\u0142owy i docelowy, aby wiedzie\u0107, do kt\u00f3rej kolejki przekierowa\u0107 nowy pakiet przy jego przybyciu.<\/p>\n<p>Wywo\u0142anie jest nieblokuj\u0105ce, dlatego je\u015bli nie ma pakiet\u00f3w, po prostu zwracamy -1 i ustawiamy <code>errno=EWOULDBLOCK<\/code>.<\/p>\n<ul>\n<li>Klient uzyskuje w jaki\u015b spos\u00f3b IP i port serwera z zewn\u0119trznych \u017ar\u00f3de\u0142 i wywo\u0142uje <code>sendto()<\/code>. R\u00f3wnocze\u015bnie wykonywane jest wewn\u0119trzne wywo\u0142anie <code>bind()<\/code>, dlatego p\u00f3\u017aniejszy <code>recvfrom()<\/code> otrzyma odpowied\u017a bez jawnego wykonywania bind.<\/li>\n<\/ul>\n<p>\nW naszym przypadku klient otrzymuje zewn\u0119trzny klucz tekstowy i korzysta z funkcji <code>resolve()<\/code> w celu uzyskania adresu IP.<\/p>\n<p>Na tym etapie rozpoczynamy \u201eu\u015bcisk d\u0142oni\u201d WebRTC, je\u015bli dwa peer-y jeszcze si\u0119 ze sob\u0105 nie po\u0142\u0105czy\u0142y. Po\u0142\u0105czenia na r\u00f3\u017cnych portach jednego peer-a korzystaj\u0105 z tego samego DataChannel WebRTC.<\/p>\n<p>Wykonujemy r\u00f3wnie\u017c po\u015bredni <code>bind()<\/code>, aby serwer m\u00f3g\u0142 przywr\u00f3ci\u0107 po\u0142\u0105czenie w nast\u0119pnym <code>sendto()<\/code> na wypadek, gdyby z jakiego\u015b powodu si\u0119 ono zamkn\u0119\u0142o.<\/p>\n<p>Serwer jest powiadamiany o po\u0142\u0105czeniu klienta, gdy klient zapisuje swoj\u0105 ofert\u0119 SDP pod informacj\u0105 o porcie serwera w Firebase, a serwer tam r\u00f3wnie\u017c odpowiada swoj\u0105 odpowiedzi\u0105.<\/p>\n<p>\nNa poni\u017cszym schemacie pokazano przyk\u0142ad ruchu wiadomo\u015bci dla schematu socket\u00f3w i przesy\u0142ania od klienta do serwera pierwszej wiadomo\u015bci:<\/p>\n<p><img decoding=\"async\" alt=\"Portujemy gr\u0119 wieloosobow\u0105 z C++ na web z Cheerp, WebRTC i Firebase\" src=\"\/wp-content\/uploads\/2019\/09\/7dc48e816c09e33daf28c307cad0c258.jpg\" style=\"display:block;margin: 0 auto;\" \/><br \/>\n<i>Pe\u0142ny schemat etapu po\u0142\u0105czenia mi\u0119dzy klientem a serwerem.<\/i><\/p>\n<h1>Podsumowanie<\/h1>\n<p>\nJe\u015bli dotar\u0142e\u015b do ko\u0144ca, zapewne jeste\u015b ciekaw teorii w dzia\u0142aniu. Mo\u017cesz zagra\u0107 w gr\u0119 na <noindex><a rel=\"nofollow\" href=\"https:\/\/teeworlds.leaningtech.com\">teeworlds.leaningtech.com<\/a><\/noindex>, spr\u00f3buj!<\/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>Przyjacielski mecz mi\u0119dzy kolegami.<\/i><\/p>\n<p>Kod biblioteki sieciowej jest swobodnie dost\u0119pny na <noindex><a rel=\"nofollow\" href=\"https:\/\/github.com\/leaningtech\/cheerpnet\">Podr\u00f3\u017c, kt\u00f3r\u0105 przeszed\u0142em, okaza\u0142a si\u0119 fascynuj\u0105c\u0105 wypraw\u0105 w przesz\u0142o\u015b\u0107 i ciesz\u0119 si\u0119, \u017ce w ko\u0144cu znalaz\u0142em rozwi\u0105zanie. Poza tym:<\/a><\/noindex>. Do\u0142\u0105cz do rozmowy na naszym kanale w <noindex><a rel=\"nofollow\" href=\"https:\/\/gitter.im\/leaningtech\/cheerp\">Gitter<\/a><\/noindex>!<br \/>\n<br \/>\u0179r\u00f3d\u0142o: <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\/pl\/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=\"pl_PL\" \/>\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\/pl\/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\udd47Portujemy gr\u0119 wieloosobow\u0105 z C++ na web z Cheerp, WebRTC i Firebase | ProHoster","description":"Wprowadzenie Nasza firma Leaning Technologies oferuje rozwi\u0105zania do portowania tradycyjnych aplikacji desktopowych do sieci.","canonical_url":"https:\/\/prohoster.info\/pl\/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":"pl_PL","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\/pl\/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\/pl\/wp-json\/wp\/v2\/posts\/38370","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/prohoster.info\/pl\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/prohoster.info\/pl\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/prohoster.info\/pl\/wp-json\/wp\/v2\/users\/1"}],"replies":[{"embeddable":true,"href":"https:\/\/prohoster.info\/pl\/wp-json\/wp\/v2\/comments?post=38370"}],"version-history":[{"count":0,"href":"https:\/\/prohoster.info\/pl\/wp-json\/wp\/v2\/posts\/38370\/revisions"}],"wp:featuredmedia":[{"embeddable":true,"href":"https:\/\/prohoster.info\/pl\/wp-json\/wp\/v2\/media\/28801"}],"wp:attachment":[{"href":"https:\/\/prohoster.info\/pl\/wp-json\/wp\/v2\/media?parent=38370"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/prohoster.info\/pl\/wp-json\/wp\/v2\/categories?post=38370"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/prohoster.info\/pl\/wp-json\/wp\/v2\/tags?post=38370"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}