{"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 erm\u00f6glicht <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 hohe Leistung.<\/p>\n<p>Als Beispiel f\u00fcr seine Anwendung haben wir uns entschieden, ein Multiplayer-Spiel f\u00fcr das Web zu portieren und haben uns f\u00fcr <noindex><a rel=\"nofollow\" href=\"https:\/\/www.teeworlds.com\/\"><strong>Teeworlds<\/strong><\/a><\/noindex>entschieden. Teeworlds ist ein Mehrspieler-2D-Retro-Spiel mit einer kleinen, aber aktiven Spielergemeinschaft (zu der auch ich geh\u00f6re!). Es ist sowohl in Bezug auf herunterladbare 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 l\u00e4uft im Browser<\/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 Netzwerkcode ins Web zu experimentieren.<\/strong>Normalerweise wird dies auf folgende Weise ausgef\u00fchrt:<\/p>\n<ul>\n<li><strong>XMLHttpRequest\/fetch<\/strong>, wenn der Netzwerkanteil nur aus HTTP-Anfragen besteht, oder<\/li>\n<li><strong>WebSockets<\/strong>.<\/li>\n<\/ul>\n<p>\nBeide L\u00f6sungen erfordern das Hosten einer Serverkomponente auf der Serverseite, und keine von ihnen erm\u00f6glicht die Verwendung eines Transportprotokolls. <noindex><a rel=\"nofollow\" href=\"https:\/\/en.wikipedia.org\/wiki\/User_Datagram_Protocol\">UDP nachzudenken<\/a><\/noindex>. Dies ist wichtig f\u00fcr Echtzeitanwendungen wie Videokonferenzsoftware und Spiele, da die Garantie f\u00fcr die Zustellung und die Reihenfolge von Paketen im Protokoll. <noindex><a rel=\"nofollow\" href=\"https:\/\/en.wikipedia.org\/wiki\/Transmission_Control_Protocol\">TCP<\/a><\/noindex> kann eine hohe Latenz st\u00f6ren.<\/p>\n<p>Es gibt auch einen dritten Weg \u2013 das Browsersystem zu nutzen: <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> es unterst\u00fctzt sowohl zuverl\u00e4ssige als auch unzuverl\u00e4ssige \u00dcbertragung (im letzten Fall versucht es in der Regel, UDP als Transportprotokoll zu verwenden) und kann sowohl mit einem Remote-Server als auch zwischen Browsern verwendet werden. <strong>Das bedeutet, dass wir die gesamte Anwendung, einschlie\u00dflich der Serverkomponente, in den Browser portieren k\u00f6nnen!<\/strong><\/p>\n<p>Allerdings gibt es hierbei eine zus\u00e4tzliche Schwierigkeit: bevor zwei WebRTC-Peers Daten austauschen k\u00f6nnen, m\u00fcssen sie einen relativ komplexen \u201eHandshake\u201c-Prozess durchlaufen, um eine Verbindung herzustellen, der mehrere externe Entit\u00e4ten erfordert (Signalisierungsserver und einen 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 erstellen, die intern WebRTC nutzt, aber so nah wie m\u00f6glich an der Schnittstelle von UDP Sockets ist, ohne eine Verbindung herstellen zu m\u00fcssen.<\/p>\n<p>Damit k\u00f6nnen wir die Vorteile von WebRTC nutzen, ohne die komplexen Details des Anwendungscodes preisgeben zu m\u00fcssen (den den wir in unserem Projekt so wenig wie m\u00f6glich \u00e4ndern wollten).<\/p>\n<h1>Minimal WebRTC<\/h1>\n<p>\nWebRTC ist eine in Browsern verf\u00fcgbare API-Sammlung, die die peer-to-peer \u00dcbertragung von Audio, Video und beliebigen Daten erm\u00f6glicht.<\/p>\n<p>Die Verbindung zwischen den Peers wird (auch bei NAT auf einer oder beiden Seiten) \u00fcber STUN- und\/oder TURN-Server durch einen Mechanismus namens ICE hergestellt. Peers tauschen ICE-Informationen und Kanalparameter \u00fcber das offer und answer Protokoll von SDP 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> \u2013 ein Protokoll, das NAT umgeht und ein (IP, Port) Paar erh\u00e4lt, um Daten direkt mit dem Host auszutauschen. Wenn es erfolgreich ist, k\u00f6nnen die 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 auch verwendet, um NAT zu umgehen, jedoch geschieht dies durch die Umleitung von Daten \u00fcber einen Proxy, der f\u00fcr beide Peers sichtbar ist. Dies verursacht eine Verz\u00f6gerung und ist im Vergleich zu STUN (da es w\u00e4hrend der gesamten Kommunikationssitzung angewendet wird) kostenintensiver, aber manchmal ist es die einzige Option.<\/li>\n<li><noindex><a rel=\"nofollow\" href=\"https:\/\/en.wikipedia.org\/wiki\/Interactive_Connectivity_Establishment\"><strong>Interaktive Verbindungsherstellung<\/strong> (<strong>ICE<\/strong>)<\/a><\/noindex> wird verwendet, um die bestm\u00f6gliche Methode zur Verbindung zweier Peers basierend auf Informationen auszuw\u00e4hlen, die beim direkten Verbinden der Peers sowie von beliebig vielen STUN- und TURN-Servern erhalten 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 Verbindungsparameter, wie z. B. ICE-Kandidaten, Multimedia-Codecs (im Fall von Audio-\/Video-Kan\u00e4len) usw. Einer der Peers sendet ein SDP-Angebot, w\u00e4hrend der andere mit einer SDP-Antwort antwortet. Danach wird der Kanal erstellt.<\/li>\n<\/ul>\n<p>\nUm eine solche Verbindung herzustellen, m\u00fcssen die Peers die Informationen, die sie von STUN- und TURN-Servern erhalten haben, zusammentragen und austauschen.<\/p>\n<p>Das Problem ist, dass sie derzeit nicht in der Lage sind, Daten direkt auszutauschen, daher muss ein Off-Channel-Mechanismus existieren: ein Signalisierungsserver.<\/p>\n<p>Ein Signalserver kann sehr einfach sein, da seine einzige Aufgabe darin besteht, Daten zwischen den Peers w\u00e4hrend des \"Handshake\"-Prozesses weiterzuleiten (wie im nachstehenden 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 WebRTC-Handshake-Prozesses<\/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 verschiedene Programme.<\/li>\n<li>Clients treten dem Spiel bei, indem sie sich mit einem der mehreren Server verbinden, von denen jeder zu einem bestimmten Zeitpunkt nur ein Spiel hostet.<\/li>\n<li>Der gesamte Datenaustausch 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 Spielclient angezeigt werden.<\/li>\n<\/ul>\n<p>\nDurch die Nutzung von WebRTC f\u00fcr den Datenaustausch k\u00f6nnen wir die Serverkomponente des Spiels in den Browser, in dem sich der Client befindet, \u00fcbertragen. Dies bietet uns die gro\u00dfartige M\u00f6glichkeit...<\/p>\n<h1>Server \u00fcberfl\u00fcssig zu machen.<\/h1>\n<p>\nDas Fehlen von Serverlogik hat einen netten Vorteil: Wir k\u00f6nnen die gesamte Anwendung als statischen Inhalt auf Github Pages oder auf unserer eigenen Hardware hinter Cloudflare bereitstellen, wodurch wir uns schnelle Ladezeiten und hohe Verf\u00fcgbarkeit kostenlos sichern. Im Grunde genommen k\u00f6nnen wir sie vergessen, und sollten wir Gl\u00fcck haben und das Spiel wird popul\u00e4r, ist eine Infrastrukturmodernisierung nicht erforderlich.<\/p>\n<p>Um jedoch das System am Laufen zu halten, m\u00fcssen wir trotzdem externe Architektur verwenden:<\/p>\n<ul>\n<li>Einen oder mehrere STUN-Server: Hier haben wir die Auswahl aus mehreren kostenlosen Optionen.<\/li>\n<li>Mindestens einen TURN-Server: Hier gibt es keine kostenlosen Optionen, daher k\u00f6nnen wir entweder unseren eigenen einrichten oder f\u00fcr einen Dienst bezahlen. Gl\u00fccklicherweise k\u00f6nnen bei den meisten Verbindungen die STUN-Server verwendet werden (und somit echtes P2P erm\u00f6glichen), aber TURN ist als Backup erforderlich.<\/li>\n<li>Signalisierungsserver: Anders als in den beiden anderen Aspekten ist die Signalgebung nicht standardisiert. Was der Signalisierungsserver tats\u00e4chlich \u00fcbernehmen wird, h\u00e4ngt teilweise von der Anwendung ab. In unserem Fall ist es notwendig, vor der Verbindungsherstellung eine kleine Datenmenge auszutauschen.<\/li>\n<li>Teeworlds Master-Server: Er wird von anderen Servern verwendet, um auf sich aufmerksam zu machen, und von Clients, um \u00f6ffentliche Server zu finden. Obwohl 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 haben wir selbst bereitgestellt.<\/p>\n<p>F\u00fcr die beiden letzten 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 umgesetzt: als eine 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. Zudem stellen wir die Liste auf der Startseite als HTML dar, sodass Spieler einfach auf den Server klicken k\u00f6nnen, um direkt ins Spiel zu gelangen.<\/li>\n<li>Die Signalgebung steht in engem Zusammenhang mit unserer Socket-Implementierung, die im n\u00e4chsten 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>Die Serverliste innerhalb des Spiels und auf der Startseite<\/i><\/p>\n<h1>Implementierung von Sockets<\/h1>\n<p>\nWir m\u00f6chten eine API erstellen, die so nah wie m\u00f6glich an Posix UDP Sockets ist, um die erforderlichen \u00c4nderungen auf ein Minimum zu reduzieren.<\/p>\n<p>Wir m\u00f6chten auch das notwendige Minimum umsetzen, das f\u00fcr einen einfachen 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>Daher ben\u00f6tigen wir keine einzigartigen IP-Adressen: F\u00fcr die eindeutige Identifikation von Peers gen\u00fcgt es, einzigartige Firebase-Schl\u00fcsselwerte zu verwenden (\u00e4hnlich wie bei Domainnamen). Jeder Peer weist lokal \"falsche\" IP-Adressen jedem Schl\u00fcssel zu, die dann umgewandelt werden m\u00fcssen. Dies befreit uns vollst\u00e4ndig von der Notwendigkeit, IP-Adressen global zuzuweisen, was eine nicht triviale Aufgabe darstellt.<\/p>\n<p>Hier ist die minimale API, die wir implementieren 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 der Posix-Sockets, hat jedoch einige wichtige Unterschiede: <strong>Registrierung von R\u00fcckrufen, Zuweisung lokaler IPs und \"lazy\" Verbindungen<\/strong>.<\/p>\n<h2>Registrierung von R\u00fcckrufen<\/h2>\n<p>\nSelbst wenn das urspr\u00fcngliche Programm nicht blockierenden I\/O verwendet, muss der Code f\u00fcr die Ausf\u00fchrung im Webbrowser refaktoriert werden.<\/p>\n<p>Der Grund daf\u00fcr ist, dass die Ereignisschleife im Browser der Anwendung verborgen ist (ob es sich nun um JavaScript oder WebAssembly handelt).<\/p>\n<p>In einer nativen Umgebung k\u00f6nnen wir Code so schreiben<\/p>\n<pre><code class=\"cpp\">while(running) {\n  select(...); \/\/ warte auf I\/O-Events\n  while(true) {\n    int r = readfrom(...); \/\/ versuche 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 uns die Ereignisschleife verborgen bleibt, m\u00fcssen wir sie in etwas \u00c4hnliches umwandeln:<\/p>\n<pre><code class=\"cpp\">auto cb = []() { \/\/ dies wird aufgerufen, wenn neue Daten verf\u00fcgbar sind\n  while(true) {\n    int r = readfrom(...); \/\/ versuche 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>Zuweisung lokaler IPs<\/h2>\n<p>\nDie Identifikatoren der Knoten in unserem \"Netzwerk\" sind keine IP-Adressen, sondern Firebase-Schl\u00fcssel (das sind Strings, 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 Eindeutigkeit ben\u00f6tigen (sowie deren Nutzung nach der Trennung des Clients), aber oft ist es notwendig, Peers durch einen numerischen Wert zu identifizieren.<\/p>\n<p>Genau daf\u00fcr werden die Funktionen verwendet <code>resolve<\/code> und <code>reverseResolve<\/code>: die Anwendung erh\u00e4lt auf irgendeine Weise den Stringwert des Schl\u00fcssels (durch Benutzereingabe oder \u00fcber den Master-Server) und kann ihn in eine IP-Adresse f\u00fcr interne Zwecke umwandeln. Der Rest der API erh\u00e4lt ebenfalls der Einfachheit halber anstelle des Strings diesen Wert.<\/p>\n<p>Das ist \u00e4hnlich wie eine DNS-Anfrage, die jedoch lokal beim Client ausgef\u00fchrt wird.<\/p>\n<p>Das hei\u00dft, IP-Adressen k\u00f6nnen nicht f\u00fcr verschiedene Clients gemeinsam sein, und wenn eine globale Kennung erforderlich ist, muss diese auf andere Weise generiert werden.<\/p>\n<h2>Faules Verbindung<\/h2>\n<p>\nUDP ben\u00f6tigt keine Verbindung, aber wie wir gesehen haben, erfordert WebRTC einen umfangreichen Verbindungsprozess, bevor Daten zwischen zwei Peers \u00fcbertragen werden k\u00f6nnen.<\/p>\n<p>Wenn wir dasselbe Abstraktionsniveau gew\u00e4hrleisten wollen, (<code>sendto<\/code>\/<code>recvfrom<\/code> mit beliebigen Peers ohne vorherige Verbindung), m\u00fcssen wir eine \u201elazy\u201c (verz\u00f6gerte) Verbindung innerhalb der API ausf\u00fchren.<\/p>\n<p>Das passiert beim normalen Datenaustausch zwischen \u201eServer\u201c und \u201eClient\u201c, wenn UDP verwendet wird, und was unsere Bibliothek tun sollte:<\/p>\n<ul>\n<li>Der Server ruft <code>bind()<\/code>auf, um dem Betriebssystem mitzuteilen, dass er Pakete am 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 lauschen Ereignissen in seinem untergeordneten Baum.<\/p>\n<ul>\n<li>Der Server ruft <code>recvfrom()<\/code>, um Pakete zu empfangen, die von jedem Host an diesen Port gesendet werden.<\/li>\n<\/ul>\n<p>\nIn unserem Fall m\u00fcssen wir die Eingangsqueue der Pakete \u00fcberpr\u00fcfen, die an diesen Port gesendet wurden.<\/p>\n<p>Jeder Port hat seine eigene Warteschlange, und wir f\u00fcgen den WebRTC-Datagrammen die Quell- und Zielports am Anfang hinzu, um zu wissen, in welche Warteschlange ein neues Paket umgeleitet werden soll.<\/p>\n<p>Der Aufruf ist nicht blockierend, daher geben wir einfach -1 zur\u00fcck, wenn keine Pakete vorhanden sind, und setzen fort. <code>errno=EWOULDBLOCK<\/code>.<\/p>\n<ul>\n<li>Der Client erh\u00e4lt \u00fcber externe Mittel die IP-Adresse und den Port des Servers und ruft auf <code>sendto()<\/code>. Gleichzeitig erfolgt ein interner Aufruf <code>bind()<\/code>, daher erh\u00e4lt der folgende <code>recvfrom()<\/code> eine Antwort, ohne dass ein bind ausgef\u00fchrt werden muss.<\/li>\n<\/ul>\n<p>\nIn unserem Fall erh\u00e4lt der Client extern einen String-Schl\u00fcssel und verwendet die Funktion <code>resolve()<\/code> zur Ermittlung der IP-Adresse.<\/p>\n<p>An dieser Stelle beginnen wir das \u201eHandschlag\u201c-Verfahren von WebRTC, wenn die beiden Peers noch nicht miteinander verbunden sind. Verbindungen zu unterschiedlichen Ports eines Peers verwenden denselben WebRTC DataChannel.<\/p>\n<p>Au\u00dferdem f\u00fchren wir einen indirekten <code>bind()<\/code>, damit der Server die Verbindung im n\u00e4chsten <code>sendto()<\/code> wiederherstellen kann, falls diese aus irgendeinem Grund geschlossen wurde.<\/p>\n<p>Der Server wird \u00fcber die Verbindung des Clients informiert, wenn der Client sein SDP-Angebot mit den Informationen zum Serverport in Firebase aufzeichnet und der Server darauf mit seiner Antwort reagiert.<\/p>\n<p>\nIn der unten gezeigten Abbildung ist ein Beispiel f\u00fcr den Nachrichtenaustausch im Socket-Schema und die \u00dcbertragung der ersten Nachricht vom Client zum Server 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\/7dc48e816c09e33daf28c307cad0c258.jpg\" style=\"display:block;margin: 0 auto;\" \/><br \/>\n<i>Das vollst\u00e4ndige Schema des Verbindungsaufbaus zwischen Client und Server.<\/i><\/p>\n<h1>Fazit<\/h1>\n<p>\nWenn Sie bis hierher gelesen haben, sind Sie wahrscheinlich interessiert, die Theorie in der Praxis zu sehen. Sie k\u00f6nnen 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>Freundschaftsspiel unter Kollegen<\/i><\/p>\n<p>Der Code der Netzwerkbibliothek ist \u00f6ffentlich zug\u00e4nglich unter <noindex><a rel=\"nofollow\" href=\"https:\/\/github.com\/leaningtech\/cheerpnet\">Github<\/a><\/noindex>. Treten Sie unserem Austausch auf unserem Kanal bei <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 4.9.10 - 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. \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\" \/>\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) 4.9.10\" \/>\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. \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\" \/>\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 Unsere Firma Leaning Technologies bietet L\u00f6sungen zur Portierung traditioneller Desktop-Anwendungen ins Web an. Unser C++ Compiler Cheerp generiert eine Kombination aus WebAssembly und JavaScript, die sowohl eine einfache Interaktion mit dem Browser als auch hohe Leistung gew\u00e4hrleistet. Als Beispiel f\u00fcr seine Anwendung haben wir uns entschieden, ein Mehrspieler-Spiel f\u00fcr das Web zu portieren und haben uns f\u00fcr Teeworlds entschieden. Teeworlds ist ein zweidimensionales Multiplayer-Retro-Spiel","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. \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","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"},"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}]}}