{"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\/sv\/blog\/administrirovanie\/portiruem-mnogopolzovatelskuyu-igru-s-s-na-veb-c-cheerp-webrtc-i-firebase","title":{"rendered":"Vi porterar ett flerspelarskap fr\u00e5n C++ till webben med Cheerp, WebRTC och Firebase","gt_translate_keys":[{"key":"rendered","format":"text"}]},"content":{"rendered":"<h2>Introduktion<\/h2>\n<p>\nV\u00e5rt f\u00f6retag <noindex><a rel=\"nofollow\" href=\"https:\/\/leaningtech.com\">Leaning Technologies<\/a><\/noindex> tillhandah\u00e5ller l\u00f6sningar f\u00f6r portering av traditionella desktopapplikationer till webben. V\u00e5r C++-kompilator <noindex><a rel=\"nofollow\" href=\"https:\/\/github.com\/leaningtech\/cheerp-meta\">Cheerp<\/a><\/noindex> genererar en kombination av WebAssembly och JavaScript, vilket m\u00f6jligg\u00f6r b\u00e5de <noindex><a rel=\"nofollow\" href=\"https:\/\/github.com\/leaningtech\/cheerp-meta\/wiki\/Cheerp-Tutorial%3A-Mixed-mode-C++-to-WebAssembly-and-JavaScript\">enkelt samspel med webbl\u00e4saren<\/a><\/noindex>, och h\u00f6g prestanda.<\/p>\n<p>Som exempel p\u00e5 dess till\u00e4mpning har vi beslutat att portera ett flerspelarspel f\u00f6r webben och valde <noindex><a rel=\"nofollow\" href=\"https:\/\/www.teeworlds.com\/\"><strong>Teeworlds<\/strong><\/a><\/noindex>. Teeworlds \u00e4r ett flerspelars 2D-retrospel med ett litet men aktivt spelargemenskap (bland dem jag!). Det \u00e4r litet b\u00e5de n\u00e4r det g\u00e4ller nedladdningsresurser och krav p\u00e5 CPU och GPU \u2013 en idealisk kandidat.<\/p>\n<p><img decoding=\"async\" alt=\"Vi porterar ett flerspelarskap fr\u00e5n C++ till webben med Cheerp, WebRTC och Firebase\" src=\"\/wp-content\/uploads\/2019\/09\/30bff36ee1158ddea2e76d8b11e48d2b.jpg\" style=\"display:block;margin: 0 auto;\" \/><br \/>\n<i>Teeworlds som fungerar i webbl\u00e4saren<\/i><br \/>\n<noindex><a rel=\"nofollow\" name=\"habracut\"><\/a><\/noindex><br \/>\nVi beslutade att anv\u00e4nda detta projekt f\u00f6r att experimentera med <strong>gemensamma l\u00f6sningar f\u00f6r portering av n\u00e4tverkskod till webben<\/strong>. Detta utf\u00f6rs vanligtvis p\u00e5 f\u00f6ljande s\u00e4tt:<\/p>\n<ul>\n<li><strong>XMLHttpRequest\/fetch<\/strong>, om n\u00e4tverksdelen endast best\u00e5r av HTTP-f\u00f6rfr\u00e5gningar, eller<\/li>\n<li><strong>WebSockets<\/strong>.<\/li>\n<\/ul>\n<p>\nB\u00e5da l\u00f6sningarna kr\u00e4ver att hysa serverkomponenten p\u00e5 serversidan, och ingen av dem till\u00e5ter anv\u00e4ndning av <noindex><a rel=\"nofollow\" href=\"https:\/\/en.wikipedia.org\/wiki\/User_Datagram_Protocol\">UDP<\/a><\/noindex>. Detta \u00e4r viktigt f\u00f6r realtidsapplikationer, s\u00e5som programvara f\u00f6r videokonferenser och spel, eftersom garantier f\u00f6r leverans och paketordning f\u00f6r protokollet <noindex><a rel=\"nofollow\" href=\"https:\/\/en.wikipedia.org\/wiki\/Transmission_Control_Protocol\">TCP<\/a><\/noindex> kan st\u00f6ra l\u00e5ga latenser.<\/p>\n<p>Det finns ocks\u00e5 ett tredje s\u00e4tt \u2013 att anv\u00e4nda n\u00e4tverket fr\u00e5n webbl\u00e4saren: <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> st\u00f6djer b\u00e5de p\u00e5litlig och op\u00e5litlig \u00f6verf\u00f6ring (i det senare fallet f\u00f6rs\u00f6ker det i m\u00f6jligaste m\u00e5n anv\u00e4nda UDP som transportprotokoll), och kan anv\u00e4ndas b\u00e5de med en fj\u00e4rrserver och mellan webbl\u00e4sare. <strong>Det betyder att vi kan portera hela applikationen till webbl\u00e4saren, inklusive serverkomponenten!<\/strong><\/p>\n<p>Men det finns en ytterligare sv\u00e5righet: innan tv\u00e5 WebRTC-peerar kan utbyta data m\u00e5ste de genomf\u00f6ra en relativt komplex \"handskakning\"-procedur f\u00f6r att ansluta, vilket kr\u00e4ver flera externa enheter (signalingserver och en eller flera <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>I idealfallet skulle vi vilja skapa ett n\u00e4tverks-API, som inuti anv\u00e4nder WebRTC, men som s\u00e5 n\u00e4ra som m\u00f6jligt liknar gr\u00e4nssnittet f\u00f6r UDP-sockets, vilket inte beh\u00f6ver etablera en anslutning.<\/p>\n<p>Det h\u00e4r g\u00f6r att vi kan utnyttja f\u00f6rdelarna med WebRTC utan att beh\u00f6va avsl\u00f6ja de komplexa detaljerna i applikationens kod (som vi ville \u00e4ndra s\u00e5 lite som m\u00f6jligt i v\u00e5rt projekt).<\/p>\n<h1>Minimal WebRTC<\/h1>\n<p>\nWebRTC \u00e4r en upps\u00e4ttning API:er som finns i webbl\u00e4sare och m\u00f6jligg\u00f6r peer-to-peer \u00f6verf\u00f6ring av ljud, video och godtyckliga data.<\/p>\n<p>Anslutningen mellan peeringarna uppr\u00e4ttas (\u00e4ven om det finns NAT p\u00e5 ena eller b\u00e5da sidor) med hj\u00e4lp av STUN- och\/eller TURN-servrar genom en mekanism som kallas ICE. Peeringar byter information om ICE och kanalparametrar via offer och answer-protokollet SDP.<\/p>\n<p>Oj! S\u00e5 m\u00e5nga f\u00f6rkortningar p\u00e5 en g\u00e5ng. L\u00e5t oss kort f\u00f6rklara vad dessa termer betyder:<\/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> \u00e4r ett protokoll f\u00f6r att kringg\u00e5 NAT och f\u00e5 ett par (IP, port) f\u00f6r att utbyta data direkt med v\u00e4rden. Om det lyckas med sin uppgift kan peeringar utbyta data sinsemellan.<\/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> anv\u00e4nds ocks\u00e5 f\u00f6r att kringg\u00e5 NAT, men det g\u00f6r detta genom att omdirigera data via en proxy som \u00e4r synlig f\u00f6r b\u00e5da peeringarna. Det tillf\u00f6r f\u00f6rdr\u00f6jning och \u00e4r mer resurskr\u00e4vande \u00e4n STUN (eftersom det till\u00e4mpas under hela kommunikationssessionen), men ibland \u00e4r det den enda m\u00f6jliga l\u00f6sningen.<\/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> anv\u00e4nds f\u00f6r att v\u00e4lja det b\u00e4sta m\u00f6jliga s\u00e4ttet att ansluta tv\u00e5 peeringar baserat p\u00e5 information som erh\u00e5llits vid den direkta anslutningen av peeringarna samt information fr\u00e5n ett antal STUN- och TURN-servrar.<\/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> \u00e4r ett format f\u00f6r beskrivning av anslutningskanalens parametrar, till exempel kandidater f\u00f6r ICE, multimedia-codecs (i fallet med ljud\/videokanal), osv. En av peeringarna skickar en SDP Offer (\"erbjudande\"), och den andra svarar med en SDP Answer (\"svar\"). Efter detta skapas en kanal.<\/li>\n<\/ul>\n<p>\nF\u00f6r att skapa en s\u00e5dan anslutning beh\u00f6ver peeringarna samla informationen de f\u00e5tt fr\u00e5n STUN- och TURN-servrarna och utbyta den sinsemellan.<\/p>\n<p>Problemet \u00e4r att de f\u00f6r n\u00e4rvarande inte kan utbyta data direkt, d\u00e4rf\u00f6r m\u00e5ste det finnas en utanf\u00f6r-band mekanism f\u00f6r att utbyta dessa data: en signalserver.<\/p>\n<p>Signalservern kan vara mycket enkel, eftersom dess enda uppgift \u00e4r att omdirigera data mellan peeringarna under \"handtryckssteget\" (som visas i diagrammet nedan).<\/p>\n<p><img decoding=\"async\" alt=\"Vi porterar ett flerspelarskap fr\u00e5n C++ till webben med Cheerp, WebRTC och Firebase\" src=\"\/wp-content\/uploads\/2019\/09\/0fc39ba5e0942bc19183e8cf06c60868.jpg\" style=\"display:block;margin: 0 auto;\" \/><br \/>\n<i>F\u00f6renklad sekvensdiagram f\u00f6r WebRTC-handtryckning<\/i><\/p>\n<h1>\u00d6versikt av n\u00e4tverksmodellen Teeworlds<\/h1>\n<p>\nN\u00e4tverksarkitekturen i Teeworlds \u00e4r mycket enkel:<\/p>\n<ul>\n<li>Klient- och serverkomponenterna \u00e4r tv\u00e5 olika program.<\/li>\n<li>Kunder g\u00e5r med i spelet genom att ansluta till en av flera servrar, var och en av vilka \u00e4r v\u00e4rd f\u00f6r endast ett spel \u00e5t g\u00e5ngen.<\/li>\n<li>All data\u00f6verf\u00f6ring i spelet sker genom servern.<\/li>\n<li>En s\u00e4rskild m\u00e4sterserver anv\u00e4nds f\u00f6r att samla en lista \u00f6ver alla offentliga servrar som visas i spelklienten.<\/li>\n<\/ul>\n<p>\nGenom att anv\u00e4nda WebRTC f\u00f6r datakommunikation kan vi flytta serverkomponenten av spelet till webbl\u00e4saren d\u00e4r klienten finns. Detta ger oss en fantastisk m\u00f6jlighet\u2026<\/p>\n<h1>Att slippa servrar<\/h1>\n<p>\nBristen p\u00e5 serverlogik har en trevlig f\u00f6rdel: vi kan implementera hela applikationen som statiskt inneh\u00e5ll p\u00e5 Github Pages eller p\u00e5 v\u00e5r egen h\u00e5rdvara bakom Cloudflare, vilket p\u00e5 s\u00e5 s\u00e4tt gratis ger oss snabba laddningstider och h\u00f6g drifttid. I huvudsak kan vi gl\u00f6mma dem, och om vi har tur och spelet blir popul\u00e4rt, beh\u00f6ver infrastrukturen inte uppgraderas.<\/p>\n<p>Men f\u00f6r att systemet ska fungera m\u00e5ste vi \u00e4nd\u00e5 anv\u00e4nda extern arkitektur:<\/p>\n<ul>\n<li>En eller flera STUN-servrar: vi har flera gratis alternativ att v\u00e4lja p\u00e5.<\/li>\n<li>Minst en TURN-server: h\u00e4r finns det inga gratis alternativ, s\u00e5 vi kan antingen st\u00e4lla in v\u00e5r egen eller betala f\u00f6r en tj\u00e4nst. Lyckligtvis kan anslutning i de flesta fall uppr\u00e4ttas via STUN-servrar (och m\u00f6jligg\u00f6ra \u00e4kta p2p), men TURN \u00e4r n\u00f6dv\u00e4ndig som en reserv.<\/li>\n<li>Signaleringsserver: till skillnad fr\u00e5n de tv\u00e5 andra aspekterna \u00e4r signalering inte standardiserad. Vad som faktiskt kommer att ansvara f\u00f6r signaleringsservern beror delvis p\u00e5 applikationen. I v\u00e5rt fall \u00e4r det n\u00f6dv\u00e4ndigt att byta en liten m\u00e4ngd data innan anslutningen kan uppr\u00e4ttas.<\/li>\n<li>Teeworlds m\u00e4sterserver: den anv\u00e4nds av andra servrar f\u00f6r att meddela sin existens och av kunder f\u00f6r att s\u00f6ka efter offentliga servrar. \u00c4ven om den inte \u00e4r obligatorisk (kunder kan alltid manuellt ansluta till en server de k\u00e4nner till), skulle det vara bra att ha den s\u00e5 att spelare kan delta i spel med slumpm\u00e4ssiga m\u00e4nniskor.<\/li>\n<\/ul>\n<p>\nVi har valt att anv\u00e4nda Googles gratis STUN-servrar, medan vi har implementerat en TURN-server sj\u00e4lva.<\/p>\n<p>F\u00f6r de tv\u00e5 senaste punkterna anv\u00e4nde vi <noindex><a rel=\"nofollow\" href=\"https:\/\/en.wikipedia.org\/wiki\/Firebase\">Firebase<\/a><\/noindex>:<\/p>\n<ul>\n<li>M\u00e4staren f\u00f6r Teeworlds-servern \u00e4r v\u00e4ldigt enkelt genomf\u00f6rt: som en lista med objekt som inneh\u00e5ller information (namn, IP, karta, l\u00e4ge, ...) om varje aktiv server. Servrar publicerar och uppdaterar sitt eget objekt, och klienterna h\u00e4mtar hela listan och visar den f\u00f6r spelaren. Vi visar ocks\u00e5 listan p\u00e5 startsidan som HTML, s\u00e5 att spelarna enkelt kan klicka p\u00e5 servern och g\u00e5 direkt in i spelet.<\/li>\n<li>Signalering \u00e4r n\u00e4ra kopplad till v\u00e5r implementering av socketar, som beskrivs i n\u00e4sta avsnitt.<\/li>\n<\/ul>\n<p>\n<img decoding=\"async\" alt=\"Vi porterar ett flerspelarskap fr\u00e5n C++ till webben med Cheerp, WebRTC och Firebase\" src=\"\/wp-content\/uploads\/2019\/09\/5e30bc22643f8baecf850053bf69e130.jpg\" style=\"display:block;margin: 0 auto;\" \/><br \/>\n<i>Serverlista inom spelet och p\u00e5 startsidan<\/i><\/p>\n<h1>Implementering av socketar<\/h1>\n<p>\nVi vill skapa ett API som ligger s\u00e5 n\u00e4ra Posix UDP Socketar som m\u00f6jligt f\u00f6r att minimera antalet n\u00f6dv\u00e4ndiga f\u00f6r\u00e4ndringar.<\/p>\n<p>Vi vill ocks\u00e5 implementera det minimala som kr\u00e4vs f\u00f6r den enklaste datakommunikationen \u00f6ver n\u00e4tverket.<\/p>\n<p>Till exempel beh\u00f6ver vi ingen riktig routing: alla peers \u00e4r i ett enda \"virtuellt LAN\" kopplat till en specifik instans av Firebase-databasen.<\/p>\n<p>D\u00e4rf\u00f6r beh\u00f6ver vi inga unika IP-adresser: f\u00f6r unik identifiering av peers r\u00e4cker det med att anv\u00e4nda unika nyckelv\u00e4rden fr\u00e5n Firebase (liknande dom\u00e4nnamn), och varje peer tilldelar lokalt \"falska\" IP-adresser till varje nyckel som beh\u00f6ver \u00f6vers\u00e4ttas. Detta befriar oss helt fr\u00e5n behovet av global IP-tilldelning, vilket \u00e4r en icke-trivial uppgift.<\/p>\n<p>H\u00e4r \u00e4r det minimala API:t som vi beh\u00f6ver implementera:<\/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:t \u00e4r enkelt och liknar API:t f\u00f6r Posix Socketar, men har n\u00e5gra viktiga skillnader: <strong>registrering av callbacks, tilldelning av lokala IP adresser och \"lazy\" anslutning<\/strong>.<\/p>\n<h2>Registrering av callbacks<\/h2>\n<p>\n\u00c4ven om det ursprungliga programmet anv\u00e4nder icke-blockerande I\/O, beh\u00f6ver koden refaktoreras f\u00f6r att k\u00f6ras i webbl\u00e4saren.<\/p>\n<p>Anledningen till detta \u00e4r att h\u00e4ndelseloopen i webbl\u00e4saren \u00e4r dold f\u00f6r programmet (oavsett om det \u00e4r JavaScript eller WebAssembly).<\/p>\n<p>I en inhemsk milj\u00f6 kan vi skriva kod p\u00e5 f\u00f6ljande s\u00e4tt<\/p>\n<pre><code class=\"cpp\">while(running) {\n  select(...); \/\/ v\u00e4nta p\u00e5 I\/O-h\u00e4ndelser\n  while(true) {\n    int r = readfrom(...); \/\/ f\u00f6rs\u00f6k att l\u00e4sa\n    if (r &lt; 0 &amp;&amp; errno == EWOULDBLOCK) \/\/ ingen mer data tillg\u00e4nglig\n      break;\n    ...\n  }\n  ...\n}<\/code><\/pre>\n<p>\nOm h\u00e4ndelseloopen \u00e4r dold f\u00f6r oss, m\u00e5ste vi omvandla den till n\u00e5got som liknar detta:<\/p>\n<pre><code class=\"cpp\">auto cb = []() { \/\/ detta kommer att kallas n\u00e4r ny data \u00e4r tillg\u00e4nglig\n  while(true) {\n    int r = readfrom(...); \/\/ f\u00f6rs\u00f6k att l\u00e4sa\n    if (r &lt; 0 &amp;&amp; errno == EWOULDBLOCK) \/\/ ingen mer data tillg\u00e4nglig\n      break;\n    ...\n  }\n  ...\n};\nrecvCallback(cb); \/\/ registrera callback-funktionen<\/code><\/pre>\n<p><\/p>\n<h2>Tilldelning av lokala IP-adresser<\/h2>\n<p>\nIdentifierarna f\u00f6r noder i v\u00e5rt \"n\u00e4t\" \u00e4r inte IP-adresser, utan Firebase-nycklar (detta \u00e4r str\u00e4ngar som ser ut s\u00e5 h\u00e4r: <code>-LmEC50PYZLCiCP-vqde<\/code> ).<\/p>\n<p>Detta \u00e4r praktiskt eftersom vi inte beh\u00f6ver en mekanism f\u00f6r att tilldela IP-adresser och kontrollera deras unika v\u00e4rde (eller avveckla dem efter att kunden har kopplats bort), men det \u00e4r ofta n\u00f6dv\u00e4ndigt att identifiera peers med ett numeriskt v\u00e4rde.<\/p>\n<p>Det \u00e4r just d\u00e4rf\u00f6r vi anv\u00e4nder funktioner <code>resolve<\/code> och <code>reverseResolve<\/code>: applikationen f\u00e5r p\u00e5 n\u00e5got s\u00e4tt str\u00e4ngv\u00e4rdet av nyckeln (genom anv\u00e4ndarinmatning eller genom en masterserver), och kan omvandla det till en IP-adress f\u00f6r internt bruk. Resterande del av API:et f\u00e5r ocks\u00e5 ist\u00e4llet f\u00f6r str\u00e4ngen detta v\u00e4rde f\u00f6r enkelhetens skull.<\/p>\n<p>Det liknar en DNS-s\u00f6kning, men den g\u00f6rs lokalt hos klienten.<\/p>\n<p>Det vill s\u00e4ga att IP-adresser inte kan vara gemensamma f\u00f6r olika klienter, och om n\u00e5gon global identifierare beh\u00f6vs, m\u00e5ste den genereras p\u00e5 annat s\u00e4tt.<\/p>\n<h2>Lazykoppling<\/h2>\n<p>\nUDP kr\u00e4ver ingen anslutning, men som vi har sett, innan vi b\u00f6rjar \u00f6verf\u00f6ra data mellan tv\u00e5 peers, kr\u00e4ver WebRTC en l\u00e5ng anslutningsprocess.<\/p>\n<p>Om vi vill uppr\u00e4tth\u00e5lla samma abstraktionsniv\u00e5, (<code>sendto<\/code>\/<code>recvfrom<\/code> med godtyckliga peers utan f\u00f6reg\u00e5ende anslutning), m\u00e5ste vi utf\u00f6ra en \"lazy\" (uppskjuten) anslutning inom API:et.<\/p>\n<p>S\u00e5 h\u00e4r fungerar den vanliga datautbytet mellan \"servern\" och \"klienten\" n\u00e4r man anv\u00e4nder UDP, och vad v\u00e5rt bibliotek m\u00e5ste utf\u00f6ra:<\/p>\n<ul>\n<li>Servern anropar <code>bind()<\/code>, f\u00f6r att informera operativsystemet om att den vill ta emot paket p\u00e5 den angivna porten.<\/li>\n<\/ul>\n<p>\nIst\u00e4llet publicerar vi en \u00f6ppen port i Firebase under serverns nyckel och kommer att lyssna p\u00e5 h\u00e4ndelser i dess undertr\u00e4d.<\/p>\n<ul>\n<li>Servern anropar <code>recvfrom()<\/code>, tar emot paket p\u00e5 den h\u00e4r porten fr\u00e5n vilken v\u00e4rd som helst.<\/li>\n<\/ul>\n<p>\nI v\u00e5rt fall m\u00e5ste vi kontrollera ing\u00e5ngsk\u00f6n f\u00f6r paket som skickas till den h\u00e4r porten.<\/p>\n<p>Varje port har sin egen k\u00f6, och vi l\u00e4gger till ursprungliga och slutgiltiga portar i WebRTC-datagrammen f\u00f6r att veta vilken k\u00f6 vi ska dirigera ett nytt paket till n\u00e4r det ankommer.<\/p>\n<p>Anropet \u00e4r icke-blockerande, s\u00e5 om det inte finns n\u00e5gra paket returnerar vi helt enkelt -1 och st\u00e4ller in <code>errno=EWOULDBLOCK<\/code>.<\/p>\n<ul>\n<li>Klienten f\u00e5r IP och serverport genom n\u00e5gra externa medel, och anropar <code>sendto()<\/code>. Samtidigt g\u00f6rs ett internt anrop <code>bind()<\/code>, s\u00e5 efterf\u00f6ljande <code>recvfrom()<\/code> f\u00e5r svaret utan uttrycklig bindning.<\/li>\n<\/ul>\n<p>\nI v\u00e5rt fall f\u00e5r kunden en str\u00e4ngnyckel externt och anv\u00e4nder funktionen <code>resolve()<\/code> f\u00f6r att h\u00e4mta IP-adressen.<\/p>\n<p>I det h\u00e4r steget b\u00f6rjar vi \"handshake\" f\u00f6r WebRTC, om tv\u00e5 peers \u00e4nnu inte \u00e4r anslutna till varandra. Anslutningar till olika portar hos en peer anv\u00e4nder samma DataChannel f\u00f6r WebRTC.<\/p>\n<p>Vi utf\u00f6r ocks\u00e5 en indirekt <code>bind()<\/code>, s\u00e5 att servern kan \u00e5teruppr\u00e4tta anslutningen i n\u00e4sta <code>sendto()<\/code> ifall den av n\u00e5gon anledning har st\u00e4ngts.<\/p>\n<p>Servern meddelas om klientens anslutning n\u00e4r klienten skriver sitt SDP-erbjudande under serverns portinformation i Firebase, och servern svarar med sin respons d\u00e4r.<\/p>\n<p>\nI schemat nedan visas ett exempel p\u00e5 meddelandefl\u00f6det f\u00f6r socket-schemat och \u00f6verf\u00f6ringen av det f\u00f6rsta meddelandet fr\u00e5n klienten till servern:<\/p>\n<p><img decoding=\"async\" alt=\"Vi porterar ett flerspelarskap fr\u00e5n C++ till webben med Cheerp, WebRTC och Firebase\" src=\"\/wp-content\/uploads\/2019\/09\/7dc48e816c09e33daf28c307cad0c258.jpg\" style=\"display:block;margin: 0 auto;\" \/><br \/>\n<i>Den fullst\u00e4ndiga schemat f\u00f6r anslutningssteget mellan klienten och servern<\/i><\/p>\n<h1>Slutsats<\/h1>\n<p>\nOm du har l\u00e4st \u00e4nda hit s\u00e5 \u00e4r du nog intresserad av att se teorin i praktiken. Du kan spela spelet p\u00e5 <noindex><a rel=\"nofollow\" href=\"https:\/\/teeworlds.leaningtech.com\">teeworlds.leaningtech.com<\/a><\/noindex>, ge det ett f\u00f6rs\u00f6k!<\/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>En v\u00e4nskapsmatch mellan kollegor<\/i><\/p>\n<p>Koden f\u00f6r det n\u00e4tverksbiblioteket \u00e4r fritt tillg\u00e4nglig p\u00e5 <noindex><a rel=\"nofollow\" href=\"https:\/\/github.com\/leaningtech\/cheerpnet\">Github<\/a><\/noindex>. G\u00e5 med i diskussionen p\u00e5 v\u00e5r kanal i <noindex><a rel=\"nofollow\" href=\"https:\/\/gitter.im\/leaningtech\/cheerp\">Gitter<\/a><\/noindex>!<br \/>\n<br \/>K\u00e4lla: <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.4 - 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\/sv\/blog\/administrirovanie\/portiruem-mnogopolzovatelskuyu-igru-s-s-na-veb-c-cheerp-webrtc-i-firebase\" \/>\n\t\t<meta name=\"generator\" content=\"All in One SEO (AIOSEO) 5.0.4\" \/>\n\t\t<meta property=\"og:locale\" content=\"sv_SE\" \/>\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\/sv\/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\udd47Portering av ett flerspelarspel fr\u00e5n C++ till webben med Cheerp, WebRTC och Firebase | ProHoster","description":"Introduktion V\u00e5rt f\u00f6retag Leaning Technologies tillhandah\u00e5ller l\u00f6sningar f\u00f6r att portera traditionella skrivbordsapplikationer till webben.","canonical_url":"https:\/\/prohoster.info\/sv\/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":"sv_SE","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\/sv\/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\/sv\/wp-json\/wp\/v2\/posts\/38370","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/prohoster.info\/sv\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/prohoster.info\/sv\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/prohoster.info\/sv\/wp-json\/wp\/v2\/users\/1"}],"replies":[{"embeddable":true,"href":"https:\/\/prohoster.info\/sv\/wp-json\/wp\/v2\/comments?post=38370"}],"version-history":[{"count":0,"href":"https:\/\/prohoster.info\/sv\/wp-json\/wp\/v2\/posts\/38370\/revisions"}],"wp:featuredmedia":[{"embeddable":true,"href":"https:\/\/prohoster.info\/sv\/wp-json\/wp\/v2\/media\/28801"}],"wp:attachment":[{"href":"https:\/\/prohoster.info\/sv\/wp-json\/wp\/v2\/media?parent=38370"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/prohoster.info\/sv\/wp-json\/wp\/v2\/categories?post=38370"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/prohoster.info\/sv\/wp-json\/wp\/v2\/tags?post=38370"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}