{"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\/de\/blog\/administrirovanie\/portiruem-mnogopolzovatelskuyu-igru-s-s-na-veb-c-cheerp-webrtc-i-firebase","title":{"rendered":"Wir portieren ein Multiplayer-Spiel von C++ auf das Web mit Cheerp, WebRTC und Firebase","gt_translate_keys":[{"key":"rendered","format":"text"}]},"content":{"rendered":"<h2>Einf\u00fchrung<\/h2>\n<p>\nUnser Unternehmen <noindex><a rel=\"nofollow\" href=\"https:\/\/leaningtech.com\">Leaning Technologies<\/a><\/noindex> bietet L\u00f6sungen f\u00fcr die Portierung traditioneller Desktop-Anwendungen ins Web an. Unser C++ Compiler <noindex><a rel=\"nofollow\" href=\"https:\/\/github.com\/leaningtech\/cheerp-meta\">Cheerp<\/a><\/noindex> generiert eine Kombination aus WebAssembly und JavaScript, was sowohl <noindex><a rel=\"nofollow\" href=\"https:\/\/github.com\/leaningtech\/cheerp-meta\/wiki\/Cheerp-Tutorial%3A-Mixed-mode-C++-to-WebAssembly-and-JavaScript\">eine einfache Interaktion mit dem Browser<\/a><\/noindex>, als auch eine hohe Leistung gew\u00e4hrleistet.<\/p>\n<p>Als Beispiel f\u00fcr seine Anwendung haben wir uns entschieden, ein Multiplayer-Spiel f\u00fcr das Web zu portieren und w\u00e4hlten daf\u00fcr <noindex><a rel=\"nofollow\" href=\"https:\/\/www.teeworlds.com\/\"><strong>Teeworlds<\/strong><\/a><\/noindex>. Teeworlds ist ein Multiplayer-2D-Retro-Spiel mit einer kleinen, aber aktiven Community von Spielern (darunter auch ich!). Es ist sowohl in Bezug auf die herunterladbaren Ressourcen als auch auf die Anforderungen an CPU und GPU gering \u2014 der ideale Kandidat.<\/p>\n<p><img decoding=\"async\" alt=\"Wir portieren ein Multiplayer-Spiel von C++ auf das Web mit Cheerp, WebRTC und Firebase\" src=\"\/wp-content\/uploads\/2019\/09\/30bff36ee1158ddea2e76d8b11e48d2b.jpg\" style=\"display:block;margin: 0 auto;\" \/><br \/>\n<i>Teeworlds, das im Browser l\u00e4uft<\/i><br \/>\n<noindex><a rel=\"nofollow\" name=\"habracut\"><\/a><\/noindex><br \/>\nWir haben uns entschieden, dieses Projekt zu nutzen, um mit <strong>allgemeinen L\u00f6sungen zur Portierung von Netzwerkkode ins Web<\/strong>experimentieren. Normalerweise geschieht dies auf folgende Weise:<\/p>\n<ul>\n<li><strong>XMLHttpRequest\/fetch<\/strong>, wenn der Netzwerkteil nur aus HTTP-Anfragen besteht, oder<\/li>\n<li><strong>WebSockets<\/strong>.<\/li>\n<\/ul>\n<p>\nBeide L\u00f6sungen erfordern, dass die serverseitige Komponente auf der Serverseite gehostet wird, und keine von ihnen erm\u00f6glicht die Nutzung als Transportprotokoll. <noindex><a rel=\"nofollow\" href=\"https:\/\/en.wikipedia.org\/wiki\/User_Datagram_Protocol\">UDP<\/a><\/noindex>Das ist wichtig f\u00fcr Echtzeitanwendungen, wie Videokonferenzsoftware und Spiele, da die Garantien f\u00fcr die Lieferung und die Reihenfolge der Pakete des Protokolls <noindex><a rel=\"nofollow\" href=\"https:\/\/en.wikipedia.org\/wiki\/Transmission_Control_Protocol\">TCP<\/a><\/noindex> niedrige Latenzen st\u00f6ren k\u00f6nnen.<\/p>\n<p>Es gibt auch einen dritten Weg \u2014 das Nutzen des Netzwerks aus dem Browser: <noindex><a rel=\"nofollow\" href=\"https:\/\/developer.mozilla.org\/en-US\/docs\/Glossary\/WebRTC\"><strong>WebRTC<\/strong><\/a><\/noindex>.<\/p>\n<p><noindex><a rel=\"nofollow\" href=\"https:\/\/developer.mozilla.org\/en-US\/docs\/Web\/API\/RTCDataChannel\"><strong>RTCDataChannel<\/strong><\/a><\/noindex> unterst\u00fctzt sowohl die zuverl\u00e4ssige als auch die unreliable \u00dcbertragung (im letzteren Fall versucht es, wenn m\u00f6glich, UDP als Transportprotokoll zu verwenden) und kann sowohl mit einem entfernten Server als auch zwischen Browsern verwendet werden. <strong>Das bedeutet, dass wir die gesamte Anwendung, einschlie\u00dflich der serverseitigen Komponente, in den Browser portieren k\u00f6nnen!<\/strong><\/p>\n<p>Allerdings gibt es dabei eine zus\u00e4tzliche Schwierigkeit: Bevor zwei WebRTC-Peers Daten austauschen k\u00f6nnen, m\u00fcssen sie ein relativ komplexes Verfahren zum \u00abHandshake\u00bb zur Verbindung durchlaufen, f\u00fcr das mehrere externe Entit\u00e4ten erforderlich sind (Signalisierungsserver und einer oder mehrere <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>Idealerweise m\u00f6chten wir eine Netzwerk-API schaffen, die intern WebRTC verwendet, aber so nah wie m\u00f6glich am Interface von UDP-Sockets liegt, das keine Verbindung aufbauen muss.<\/p>\n<p>Das w\u00fcrde es uns erm\u00f6glichen, die Vorteile von WebRTC zu nutzen, ohne komplizierte Details des Anwendungscodes offenlegen zu m\u00fcssen (den wir in unserem Projekt so wenig wie m\u00f6glich \u00e4ndern wollten).<\/p>\n<h1>Minimales WebRTC<\/h1>\n<p>\nWebRTC ist eine in Browsern vorhandene API-Sammlung, die eine peer-to-peer \u00dcbertragung von Audio, Video und beliebigen Daten erm\u00f6glicht.<\/p>\n<p>Die Verbindung zwischen Peers wird (auch bei NAT auf einer oder beiden Seiten) mit Hilfe von STUN- und\/oder TURN-Servern \u00fcber einen Mechanismus namens ICE hergestellt. Peers tauschen ICE-Informationen und Kanalparameter \u00fcber das Angebot und die Antwort des SDP-Protokolls aus.<\/p>\n<p>Wow! So viele Abk\u00fcrzungen auf einmal. Lassen Sie uns kurz erkl\u00e4ren, was diese Begriffe bedeuten:<\/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 ein Protokoll zur Umgehung von NAT und zur Ermittlung eines Paares (IP, Port) f\u00fcr den direkten Datenaustausch mit dem Host. Wenn es ihm gelingt, seine Aufgabe zu erf\u00fcllen, k\u00f6nnen Peers Daten direkt untereinander austauschen.<\/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> wird ebenfalls verwendet, um NAT zu umgehen, aber es implementiert dies, indem es Daten \u00fcber einen Proxy weiterleitet, der f\u00fcr beide Peers sichtbar ist. Es f\u00fcgt Verz\u00f6gerungen hinzu und ist in der Ausf\u00fchrung teurer als STUN (da es w\u00e4hrend der gesamten Kommunikationssitzung verwendet wird), aber manchmal ist es die einzige m\u00f6gliche Option.<\/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> wird verwendet, um die bestm\u00f6gliche Methode zur Verbindung von zwei Peers basierend auf Informationen auszuw\u00e4hlen, die w\u00e4hrend der direkten Verbindung der Peers sowie von einer beliebigen Anzahl von STUN- und TURN-Servern gewonnen wurden.<\/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> ist ein Format zur Beschreibung von Verbindungsparametern, wie z.B. ICE-Kandidaten, Multimedia-Codecs (im Fall von Audio-\/Videokan\u00e4len) usw. Einer der Peers sendet ein SDP-Angebot (\u201eAngebot\u201c), und der andere antwortet mit einer SDP-Antwort (\u201eAntwort\u201c). Danach wird ein Kanal hergestellt.<\/li>\n<\/ul>\n<p>\nUm eine solche Verbindung herzustellen, m\u00fcssen Peers die Informationen, die sie von den STUN- und TURN-Servern erhalten haben, sammeln und untereinander austauschen.<\/p>\n<p>Das Problem ist, dass es ihnen derzeit nicht m\u00f6glich ist, Daten direkt auszutauschen, daher muss es einen Offline-Mechanismus f\u00fcr den Austausch dieser Daten geben: einen Signalisierungsserver.<\/p>\n<p>Ein Signalisierungsserver kann sehr einfach sein, da seine einzige Aufgabe darin besteht, Daten zwischen den Peers w\u00e4hrend der \u201eHandshake\u201c-Phase weiterzuleiten (wie in dem untenstehenden Diagramm dargestellt).<\/p>\n<p><img decoding=\"async\" alt=\"Wir portieren ein Multiplayer-Spiel von C++ auf das Web mit Cheerp, WebRTC und Firebase\" src=\"\/wp-content\/uploads\/2019\/09\/0fc39ba5e0942bc19183e8cf06c60868.jpg\" style=\"display:block;margin: 0 auto;\" \/><br \/>\n<i>Vereinfachtes Diagramm des Handshake-Prozesses von WebRTC<\/i><\/p>\n<h1>\u00dcberblick \u00fcber das Netzwerkmodell von Teeworlds<\/h1>\n<p>\nDie Netzwerkarchitektur von Teeworlds ist sehr einfach:<\/p>\n<ul>\n<li>Die Komponenten von Client und Server sind zwei unterschiedliche Programme.<\/li>\n<li>Kunden treten in das Spiel ein, indem sie sich mit einem der mehreren Server verbinden, wobei jeder Server jeweils nur ein Spiel hostet.<\/li>\n<li>Die gesamte Daten\u00fcbertragung im Spiel erfolgt \u00fcber den Server.<\/li>\n<li>Ein spezieller Master-Server wird verwendet, um eine Liste aller \u00f6ffentlichen Server zu sammeln, die im Spiel-Client angezeigt werden.<\/li>\n<\/ul>\n<p>\nDurch die Nutzung von WebRTC f\u00fcr den Datenaustausch k\u00f6nnen wir die serverseitige Komponente des Spiels in den Browser verlagern, wo sich der Client befindet. Das er\u00f6ffnet uns gro\u00dfartige M\u00f6glichkeiten\u2026<\/p>\n<h1>Server loswerden<\/h1>\n<p>\nDas Fehlen von Serverlogik hat den angenehmen Vorteil, dass wir die gesamte Anwendung als statischen Inhalt auf Github Pages oder auf unserer eigenen Hardware \u00fcber Cloudflare bereitstellen k\u00f6nnen, wodurch wir uns schnelle Ladezeiten und hohe Verf\u00fcgbarkeit kostenlos sichern. Im Grunde k\u00f6nnen wir sie vergessen, und wenn wir Gl\u00fcck haben und das Spiel popul\u00e4r wird, wird es nicht n\u00f6tig sein, die Infrastruktur aufzur\u00fcsten.<\/p>\n<p>Damit das System jedoch funktioniert, m\u00fcssen wir dennoch eine externe Architektur nutzen:<\/p>\n<ul>\n<li>Einen oder mehrere STUN-Server: Wir haben die Wahl zwischen mehreren kostenlosen Optionen.<\/li>\n<li>Mindestens einen TURN-Server: Hier gibt es keine kostenlosen Optionen, weshalb wir entweder unseren eigenen einrichten oder f\u00fcr einen Service bezahlen m\u00fcssen. Gl\u00fccklicherweise kann die Verbindung die meiste Zeit \u00fcber STUN-Server hergestellt werden (und echtes P2P erm\u00f6glichen), aber TURN ist als Backup erforderlich.<\/li>\n<li>Signalisierungsserver: Im Gegensatz zu den beiden anderen Aspekten ist die Signalisierung nicht standardisiert. Wof\u00fcr der Signalisierungsserver tats\u00e4chlich verantwortlich ist, h\u00e4ngt teilweise von der Anwendung ab. In unserem Fall muss vor der Verbindung ein kleiner Datenvolumen Austausch stattfinden.<\/li>\n<li>Teeworlds Master-Server: Er wird von anderen Servern genutzt, um auf seine Existenz aufmerksam zu machen und von Clients zur Suche nach \u00f6ffentlichen Servern. Auch wenn er nicht zwingend erforderlich ist (Clients k\u00f6nnen sich immer manuell mit einem bekannten Server verbinden), w\u00e4re es vorteilhaft, ihn zu haben, damit Spieler an Spielen mit zuf\u00e4lligen Personen teilnehmen k\u00f6nnen.<\/li>\n<\/ul>\n<p>\nWir haben uns entschieden, die kostenlosen STUN-Server von Google zu nutzen und einen TURN-Server selbst zu betreiben.<\/p>\n<p>F\u00fcr die letzten beiden Punkte haben wir verwendet <noindex><a rel=\"nofollow\" href=\"https:\/\/en.wikipedia.org\/wiki\/Firebase\">Firebase<\/a><\/noindex>:<\/p>\n<ul>\n<li>Der Master-Server von Teeworlds ist sehr einfach implementiert: als Liste von Objekten, die Informationen (Name, IP, Karte, Modus, \u2026) zu jedem aktiven Server enthalten. Die Server ver\u00f6ffentlichen und aktualisieren ihr eigenes Objekt, w\u00e4hrend die Clients die gesamte Liste abrufen und sie dem Spieler anzeigen. Au\u00dferdem zeigen wir die Liste auf der Startseite als HTML an, damit die Spieler einfach auf den Server klicken und direkt ins Spiel gelangen k\u00f6nnen.<\/li>\n<li>Die Signalgebung ist eng mit unserer Implementierung der Sockets verbunden, die im folgenden Abschnitt beschrieben wird.<\/li>\n<\/ul>\n<p>\n<img decoding=\"async\" alt=\"Wir portieren ein Multiplayer-Spiel von C++ auf das Web mit Cheerp, WebRTC und Firebase\" src=\"\/wp-content\/uploads\/2019\/09\/5e30bc22643f8baecf850053bf69e130.jpg\" style=\"display:block;margin: 0 auto;\" \/><br \/>\n<i>Liste der Server im Spiel und auf der Startseite<\/i><\/p>\n<h1>Implementierung der Sockets<\/h1>\n<p>\nWir m\u00f6chten eine API erstellen, die so nah wie m\u00f6glich an Posix UDP Sockets herankommt, um die Anzahl der erforderlichen \u00c4nderungen zu minimieren.<\/p>\n<p>Au\u00dferdem m\u00f6chten wir das notwendige Minimum implementieren, das f\u00fcr den einfachsten Datenaustausch \u00fcber das Netzwerk erforderlich ist.<\/p>\n<p>Zum Beispiel ben\u00f6tigen wir keine echte Routing-Funktion: Alle Peers befinden sich in einem \"virtuellen LAN\", das mit einer bestimmten Instanz der Firebase-Datenbank verbunden ist.<\/p>\n<p>Folglich ben\u00f6tigen wir keine eindeutigen IP-Adressen: Zur eindeutigen Identifizierung der Peers gen\u00fcgt es, eindeutige Werte der Firebase-Schl\u00fcssel (\u00e4hnlich wie bei Domainnamen) zu verwenden, und jeder Peer weist lokal jedem Schl\u00fcssel, der umgewandelt werden muss, \"falsche\" IP-Adressen zu. Das befreit uns vollst\u00e4ndig von der Notwendigkeit einer globalen IP-Adresszuweisung, was eine nicht triviale Aufgabe ist.<\/p>\n<p>Hier ist die minimale API, die wir umsetzen m\u00fcssen:<\/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>\nDie API ist einfach und \u00e4hnelt der API von Posix Sockets, hat jedoch einige wichtige Unterschiede: <strong>Callback-Registrierung, Zuordnung lokaler IPs und \"lazy\" Verbindung<\/strong>.<\/p>\n<h2>Callback-Registrierung<\/h2>\n<p>\nSelbst wenn das urspr\u00fcngliche Programm nicht blockierendes I\/O verwendet, muss der Code zum Ausf\u00fchren im Webbrowser umstrukturiert werden.<\/p>\n<p>Der Grund daf\u00fcr ist, dass die Ereignisschleife im Browser vor dem Programm verborgen ist (egal ob JavaScript oder WebAssembly).<\/p>\n<p>In einer nativen Umgebung k\u00f6nnen wir den Code so schreiben<\/p>\n<pre><code class=\"cpp\">while(running) {\n  select(...); \/\/ auf I\/O-Ereignisse warten\n  while(true) {\n    int r = readfrom(...); \/\/ versuchen zu lesen\n    if (r &lt; 0 &amp;&amp; errno == EWOULDBLOCK) \/\/ keine weiteren Daten verf\u00fcgbar\n      break;\n    ...\n  }\n  ...\n}<\/code><\/pre>\n<p>\nWenn die Ereignisschleife f\u00fcr uns verborgen ist, muss sie in etwas \u00c4hnliches umgewandelt werden:<\/p>\n<pre><code class=\"cpp\">auto cb = []() { \/\/ dies wird aufgerufen, wenn neue Daten verf\u00fcgbar sind\n  while(true) {\n    int r = readfrom(...); \/\/ versuchen zu lesen\n    if (r &lt; 0 &amp;&amp; errno == EWOULDBLOCK) \/\/ keine weiteren Daten verf\u00fcgbar\n      break;\n    ...\n  }\n  ...\n};\nrecvCallback(cb); \/\/ Callback registrieren<\/code><\/pre>\n<p><\/p>\n<h2>Zuordnung lokaler IPs<\/h2>\n<p>\nDie Identifikatoren der Knoten in unserem \u201eNetz\u201c sind keine IP-Adressen, sondern Firebase-Schl\u00fcssel (das sind Zeichenfolgen, die so aussehen: <code>-LmEC50PYZLCiCP-vqde<\/code> ).<\/p>\n<p>Das ist praktisch, da wir keinen Mechanismus zur Zuweisung von IPs und zur \u00dcberpr\u00fcfung ihrer Einzigartigkeit ben\u00f6tigen (sowie deren Entsorgung nach der Abmeldung eines Clients), es jedoch oft notwendig ist, Peers durch numerische Werte zu identifizieren.<\/p>\n<p>Genau dazu werden die Funktionen <code>resolve<\/code> und <code>reverseResolve<\/code>: Die Anwendung erh\u00e4lt auf irgendeine Weise den Schl\u00fcsselwert als Zeichenfolge (durch Benutzereingabe oder \u00fcber einen Master-Server) und kann ihn in eine IP-Adresse f\u00fcr interne Zwecke umwandeln. Der Rest der API erh\u00e4lt auch aus Gr\u00fcnden der Vereinfachung anstelle der Zeichenfolge diesen Wert.<\/p>\n<p>Das \u00e4hnelt einer DNS-Abfrage, wird jedoch lokal beim Client durchgef\u00fchrt.<\/p>\n<p>Das bedeutet, dass IP-Adressen nicht f\u00fcr verschiedene Clients gemeinsam sein k\u00f6nnen, und wenn ein globaler Identifikator ben\u00f6tigt wird, muss dieser auf andere Weise generiert werden.<\/p>\n<h2>Laid-Back-Verbindung<\/h2>\n<p>\nUDP ben\u00f6tigt keine Verbindung, jedoch erfordert WebRTC, wie wir gesehen haben, einen langen Verbindungsprozess, bevor Daten zwischen zwei Peers \u00fcbertragen werden k\u00f6nnen.<\/p>\n<p>Wenn wir das gleiche Abstraktionsniveau gew\u00e4hrleisten wollen, (<code>sendto<\/code>\/<code>recvfrom<\/code> mit beliebigen Peers ohne vorherige Verbindung), m\u00fcssen wir eine \u201elaissez-faire\u201c (verz\u00f6gerte) Verbindung innerhalb der API herstellen.<\/p>\n<p>So funktioniert der normale Datenaustausch zwischen einem \u201eServer\u201c und einem \u201eClient\u201c im Fall von UDP, und das ist, was unsere Bibliothek tun sollte:<\/p>\n<ul>\n<li>Der Server ruft <code>bind()<\/code>, um dem Betriebssystem mitzuteilen, dass er Pakete an den angegebenen Port empfangen m\u00f6chte.<\/li>\n<\/ul>\n<p>\nStattdessen ver\u00f6ffentlichen wir einen offenen Port in Firebase unter dem Schl\u00fcssel des Servers und h\u00f6ren Ereignisse in seinem Unterbaum.<\/p>\n<ul>\n<li>Der Server ruft <code>recvfrom()<\/code>, um Pakete an diesem Port von jedem Host zu empfangen.<\/li>\n<\/ul>\n<p>\nIn unserem Fall m\u00fcssen wir die eingehende Warteschlange der Pakete \u00fcberpr\u00fcfen, die an diesen Port gesendet werden.<\/p>\n<p>Jeder Port hat seine eigene Warteschlange, und wir f\u00fcgen den WebRTC-Datagrammen am Anfang die Quell- und Zielports hinzu, um zu wissen, in welche Warteschlange ein neues Paket weitergeleitet werden soll, wenn es eintrifft.<\/p>\n<p>Der Aufruf ist nicht blockierend, daher geben wir, wenn keine Pakete vorhanden sind, einfach -1 zur\u00fcck und setzen <code>errno=EWOULDBLOCK<\/code>.<\/p>\n<ul>\n<li>Der Client erh\u00e4lt durch externe Mittel die IP und den Port des Servers und ruft <code>sendto()<\/code>. Gleichzeitig erfolgt ein interner Aufruf <code>bind()<\/code>, sodass der folgende <code>recvfrom()<\/code> eine Antwort erh\u00e4lt, ohne explizit bind auszuf\u00fchren.<\/li>\n<\/ul>\n<p>\nIn unserem Fall erh\u00e4lt der Kunde extern einen String-Schl\u00fcssel und verwendet die Funktion <code>resolve()<\/code> um die IP-Adresse zu erhalten.<\/p>\n<p>An diesem Punkt beginnen wir das \u201eHandschlag\u201c von WebRTC, falls zwei Peers noch nicht miteinander verbunden sind. Verbindungen zu verschiedenen Ports eines Peers verwenden denselben WebRTC DataChannel.<\/p>\n<p>Au\u00dferdem f\u00fchren wir eine indirekte <code>bind()<\/code>aus, damit der Server die Verbindung im n\u00e4chsten <code>sendto()<\/code> Weltfall wiederherstellen kann, falls sie aus irgendeinem Grund geschlossen wurde.<\/p>\n<p>Der Server wird \u00fcber die Verbindung des Clients informiert, wenn der Client sein SDP-Angebot unter der Portinformation des Servers in Firebase speichert, und der Server dort mit seiner R\u00fcckmeldung antwortet.<\/p>\n<p>\nDas folgende Diagramm zeigt ein Beispiel f\u00fcr den Nachrichtenaustausch im Socket-Schema und die \u00dcbertragung der ersten Nachricht vom Client an den Server:<\/p>\n<p><img decoding=\"async\" alt=\"Wir portieren ein Multiplayer-Spiel von C++ auf das Web mit Cheerp, WebRTC und Firebase\" src=\"\/wp-content\/uploads\/2019\/09\/7dc48e816c09e33daf28c307cad0c258.jpg\" style=\"display:block;margin: 0 auto;\" \/><br \/>\n<i>Das vollst\u00e4ndige Schema des Verbindungsprozesses zwischen Client und Server<\/i><\/p>\n<h1>Fazit<\/h1>\n<p>\nWenn Sie bis hierher gelesen haben, sind Sie wahrscheinlich daran interessiert, die Theorie in der Praxis zu sehen. Sie k\u00f6nnen das Spiel auf <noindex><a rel=\"nofollow\" href=\"https:\/\/teeworlds.leaningtech.com\">teeworlds.leaningtech.com<\/a><\/noindex>, versuchen Sie es!<\/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>Ein freundliches Match zwischen Kollegen<\/i><\/p>\n<p>Der Code der Netzwerkbibliothek ist frei verf\u00fcgbar unter <noindex><a rel=\"nofollow\" href=\"https:\/\/github.com\/leaningtech\/cheerpnet\">Github<\/a><\/noindex>. Treten Sie unserem Kanal im <noindex><a rel=\"nofollow\" href=\"https:\/\/gitter.im\/leaningtech\/cheerp\">Gitter<\/a><\/noindex>!<br \/>\n<br \/>Quelle: <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\/de\/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=\"de_DE\" \/>\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\/de\/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\udd47Wir portieren ein Mehrspieler-Spiel von C++ ins Web mit Cheerp, WebRTC und Firebase | ProHoster","description":"Einf\u00fchrung Die Firma Leaning Technologies bietet L\u00f6sungen zur Portierung traditioneller Desktop-Anwendungen ins Web an.","canonical_url":"https:\/\/prohoster.info\/de\/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":"de_DE","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\/de\/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\/de\/wp-json\/wp\/v2\/posts\/38370","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/prohoster.info\/de\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/prohoster.info\/de\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/prohoster.info\/de\/wp-json\/wp\/v2\/users\/1"}],"replies":[{"embeddable":true,"href":"https:\/\/prohoster.info\/de\/wp-json\/wp\/v2\/comments?post=38370"}],"version-history":[{"count":0,"href":"https:\/\/prohoster.info\/de\/wp-json\/wp\/v2\/posts\/38370\/revisions"}],"wp:featuredmedia":[{"embeddable":true,"href":"https:\/\/prohoster.info\/de\/wp-json\/wp\/v2\/media\/28801"}],"wp:attachment":[{"href":"https:\/\/prohoster.info\/de\/wp-json\/wp\/v2\/media?parent=38370"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/prohoster.info\/de\/wp-json\/wp\/v2\/categories?post=38370"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/prohoster.info\/de\/wp-json\/wp\/v2\/tags?post=38370"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}