{"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\/cs\/blog\/administrirovanie\/portiruem-mnogopolzovatelskuyu-igru-s-s-na-veb-c-cheerp-webrtc-i-firebase","title":{"rendered":"P\u0159en\u00e1\u0161\u00edme multiplayerovou hru z C++ na web pomoc\u00ed Cheerp, WebRTC a Firebase","gt_translate_keys":[{"key":"rendered","format":"text"}]},"content":{"rendered":"<h2>\u00davod<\/h2>\n<p>\nNa\u0161e spole\u010dnost <noindex><a rel=\"nofollow\" href=\"https:\/\/leaningtech.com\">Leaning Technologies<\/a><\/noindex> poskytuje \u0159e\u0161en\u00ed pro portov\u00e1n\u00ed tradi\u010dn\u00edch desktopov\u00fdch aplikac\u00ed na web. N\u00e1\u0161 C++ kompil\u00e1tor <noindex><a rel=\"nofollow\" href=\"https:\/\/github.com\/leaningtech\/cheerp-meta\">Cheerp<\/a><\/noindex> generuje kombinaci WebAssembly a JavaScript, co\u017e zaji\u0161\u0165uje jak <noindex><a rel=\"nofollow\" href=\"https:\/\/github.com\/leaningtech\/cheerp-meta\/wiki\/Cheerp-Tutorial%3A-Mixed-mode-C++-to-WebAssembly-and-JavaScript\">jednoduchou interakci s prohl\u00ed\u017ee\u010dem<\/a><\/noindex>, tak vysok\u00fd v\u00fdkon.<\/p>\n<p>Jako p\u0159\u00edklad jeho pou\u017eit\u00ed jsme se rozhodli portovat pro web multiplayerovou hru a vybrali jsme pro to <noindex><a rel=\"nofollow\" href=\"https:\/\/www.teeworlds.com\/\"><strong>Teeworlds<\/strong><\/a><\/noindex>. Teeworlds je multiplayerov\u00e1 2D retro hra s malou, ale aktivn\u00ed komunitou hr\u00e1\u010d\u016f (mezi nimi jsem i j\u00e1!). Je mal\u00e1 jak z hlediska stahovan\u00fdch dat, tak po\u017eadavk\u016f na CPU a GPU \u2013 ide\u00e1ln\u00ed kandid\u00e1t.<\/p>\n<p><img decoding=\"async\" alt=\"P\u0159en\u00e1\u0161\u00edme multiplayerovou hru z C++ na web pomoc\u00ed Cheerp, WebRTC a Firebase\" src=\"\/wp-content\/uploads\/2019\/09\/30bff36ee1158ddea2e76d8b11e48d2b.jpg\" style=\"display:block;margin: 0 auto;\" \/><br \/>\n<i>Teeworlds b\u011b\u017e\u00edc\u00ed v prohl\u00ed\u017ee\u010di<\/i><br \/>\n<noindex><a rel=\"nofollow\" name=\"habracut\"><\/a><\/noindex><br \/>\nRozhodli jsme se vyu\u017e\u00edt tento projekt k experimentov\u00e1n\u00ed s <strong>obecn\u00fdmi \u0159e\u0161en\u00edmi pro portov\u00e1n\u00ed s\u00ed\u0165ov\u00e9ho k\u00f3du na web<\/strong>. Obvykle se to prov\u00e1d\u00ed n\u00e1sleduj\u00edc\u00edmi zp\u016fsoby:<\/p>\n<ul>\n<li><strong>XMLHttpRequest\/fetch<\/strong>, pokud se s\u00ed\u0165ov\u00e1 \u010d\u00e1st skl\u00e1d\u00e1 pouze z HTTP po\u017eadavk\u016f, nebo<\/li>\n<li><strong>WebSockets<\/strong>.<\/li>\n<\/ul>\n<p>\nOb\u011b \u0159e\u0161en\u00ed vy\u017eaduj\u00ed hostovat serverovou komponentu na stran\u011b serveru, a \u017e\u00e1dn\u00e9 z nich neumo\u017e\u0148uje pou\u017e\u00edvat jako transportn\u00ed protokol <noindex><a rel=\"nofollow\" href=\"https:\/\/en.wikipedia.org\/wiki\/User_Datagram_Protocol\">UDP<\/a><\/noindex>. To je d\u016fle\u017eit\u00e9 pro aplikace v re\u00e1ln\u00e9m \u010dase, jako jsou software pro videokonference a hry, proto\u017ee z\u00e1ruky doru\u010den\u00ed a po\u0159ad\u00ed paket\u016f protokolu <noindex><a rel=\"nofollow\" href=\"https:\/\/en.wikipedia.org\/wiki\/Transmission_Control_Protocol\">TCP<\/a><\/noindex> mohou b\u00fdt p\u0159ek\u00e1\u017ekou pro n\u00edzk\u00e9 latence.<\/p>\n<p>Existuje i t\u0159et\u00ed zp\u016fsob \u2013 pou\u017e\u00edvat s\u00ed\u0165 z prohl\u00ed\u017ee\u010de: <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> podporuje jak spolehliv\u00fd, tak nespolehliv\u00fd p\u0159enos (v druh\u00e9m p\u0159\u00edpad\u011b se sna\u017e\u00ed pou\u017e\u00edt jako transportn\u00ed protokol UDP), a m\u016f\u017ee b\u00fdt pou\u017eit jak s vzd\u00e1len\u00fdm serverem, tak mezi prohl\u00ed\u017ee\u010di. <strong>To znamen\u00e1, \u017ee m\u016f\u017eeme portovat do prohl\u00ed\u017ee\u010de celou aplikaci, v\u010detn\u011b serverov\u00e9 komponenty!<\/strong><\/p>\n<p>Nicm\u00e9n\u011b s t\u00edm je spojena dal\u0161\u00ed obt\u00ed\u017e: ne\u017e dva peerov\u00e9 WebRTC mohou vym\u011b\u0148ovat data, mus\u00ed prov\u00e9st relativn\u011b slo\u017eit\u00fd postup \u201epod\u00e1n\u00ed ruky\u201c (handshake) k nav\u00e1z\u00e1n\u00ed spojen\u00ed, k \u010demu\u017e je nezbytn\u00e9 n\u011bkolik extern\u00edch entit (sign\u00e1ln\u00ed server a jeden nebo v\u00edce server\u016f <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>Ide\u00e1ln\u011b bychom cht\u011bli vytvo\u0159it s\u00ed\u0165ov\u00e9 API, kter\u00e9 uvnit\u0159 pou\u017e\u00edv\u00e1 WebRTC, ale co nejbl\u00ed\u017ee rozhran\u00ed UDP Sockets, kter\u00e9 nepot\u0159ebuje navazovat spojen\u00ed.<\/p>\n<p>To n\u00e1m umo\u017en\u00ed vyu\u017e\u00edt v\u00fdhod WebRTC, ani\u017e bychom museli odhalovat slo\u017eit\u00e9 detaily k\u00f3du aplikace (kter\u00fd jsme v na\u0161em projektu cht\u011bli co nejm\u00e9n\u011b m\u011bnit).<\/p>\n<h1>Minim\u00e1ln\u00ed WebRTC<\/h1>\n<p>\nWebRTC je sada existuj\u00edc\u00ed sada API v prohl\u00ed\u017ee\u010d\u00edch, kter\u00e1 umo\u017e\u0148uje p\u0159enos peer-to-peer zvuku, videa a libovoln\u00fdch dat.<\/p>\n<p>Spojen\u00ed mezi peer se vytv\u00e1\u0159\u00ed (i v p\u0159\u00edpad\u011b existence NAT na jedn\u00e9 nebo obou stran\u00e1ch) pomoc\u00ed server\u016f STUN a\/nebo TURN prost\u0159ednictv\u00edm mechanismu zvan\u00e9ho ICE. Peery si vym\u011b\u0148uj\u00ed informace ICE a parametry kan\u00e1l\u016f prost\u0159ednictv\u00edm nab\u00eddky a odpov\u011bdi protokolu SDP.<\/p>\n<p>Wow! Kolik zkratek najednou. Poj\u010fme stru\u010dn\u011b vysv\u011btlit, co tyto pojmy znamenaj\u00ed:<\/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 protokol pro obch\u00e1zen\u00ed NAT a z\u00edsk\u00e1n\u00ed p\u00e1ru (IP, port) pro p\u0159enos dat p\u0159\u00edmo s hostitelem. Pokud se mu poda\u0159\u00ed splnit jeho \u00fakol, mohou peery autonomn\u011b vym\u011b\u0148ovat data mezi sebou.<\/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> tak\u00e9 se pou\u017e\u00edv\u00e1 pro obch\u00e1zen\u00ed NAT, ale realizuje to t\u00edm, \u017ee p\u0159esm\u011brov\u00e1v\u00e1 data p\u0159es proxy, kter\u00e1 je viditeln\u00e1 ob\u011bma peer\u016fm. P\u0159id\u00e1v\u00e1 latenci a je n\u00e1kladn\u011bj\u0161\u00ed na v\u00fdkon ne\u017e STUN (proto\u017ee se pou\u017e\u00edv\u00e1 po celou dobu komunikace), ale n\u011bkdy je to jedin\u00e1 mo\u017en\u00e1 varianta.<\/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> se pou\u017e\u00edv\u00e1 pro v\u00fdb\u011br nejlep\u0161\u00ed mo\u017en\u00e9 metody spojen\u00ed dvou peer\u016f na z\u00e1klad\u011b informac\u00ed z\u00edskan\u00fdch p\u0159i p\u0159\u00edm\u00e9m spojen\u00ed peer\u016f a tak\u00e9 informac\u00ed z\u00edskan\u00fdch od jak\u00e9hokoli po\u010dtu server\u016f STUN a TURN.<\/li>\n<li><noindex><a rel=\"nofollow\" href=\"https:\/\/en.wikipedia.org\/wiki\/Session_Description_Protocol\"><strong>Session Description Protocol<\/strong> (<strong>SDP<\/strong>)<\/a><\/noindex> \u2014 to je form\u00e1t popisu parametr\u016f p\u0159ipojovac\u00edho kan\u00e1lu, nap\u0159\u00edklad kandid\u00e1t\u016f ICE, multimedia kodek\u016f (v p\u0159\u00edpad\u011b audio\/video kan\u00e1lu) atd. Jeden z peer\u016f pos\u00edl\u00e1 SDP nab\u00eddku (\u201enab\u00eddku\u201c), a druh\u00fd odpov\u00edd\u00e1 SDP odpov\u011bd\u00ed (\u201eodpov\u011bd\u00ed\u201c). Pot\u00e9 se vytv\u00e1\u0159\u00ed kan\u00e1l.<\/li>\n<\/ul>\n<p>\nAby bylo mo\u017en\u00e9 vytvo\u0159it takov\u00e9 spojen\u00ed, pot\u0159ebuj\u00ed peery shrom\u00e1\u017edit informace, kter\u00e9 od server\u016f STUN a TURN z\u00edskaly, a vym\u011bnit si je navz\u00e1jem.<\/p>\n<p>Probl\u00e9m je v tom, \u017ee zat\u00edm nemaj\u00ed mo\u017enost vym\u011b\u0148ovat si data p\u0159\u00edmo, proto mus\u00ed existovat offline mechanismus: sign\u00e1ln\u00ed server.<\/p>\n<p>Sign\u00e1ln\u00ed server m\u016f\u017ee b\u00fdt velmi jednoduch\u00fd, proto\u017ee jeho jedin\u00fdm \u00fakolem je p\u0159esm\u011brov\u00e1n\u00ed dat mezi peery b\u011bhem f\u00e1ze \u201epod\u00e1n\u00ed ruky\u201c (jak je zn\u00e1zorn\u011bno na n\u00ed\u017ee uveden\u00e9m diagramu).<\/p>\n<p><img decoding=\"async\" alt=\"P\u0159en\u00e1\u0161\u00edme multiplayerovou hru z C++ na web pomoc\u00ed Cheerp, WebRTC a Firebase\" src=\"\/wp-content\/uploads\/2019\/09\/0fc39ba5e0942bc19183e8cf06c60868.jpg\" style=\"display:block;margin: 0 auto;\" \/><br \/>\n<i>Zjednodu\u0161en\u00fd diagram posloupnosti \u201epod\u00e1n\u00ed ruky\u201c WebRTC<\/i><\/p>\n<h1>P\u0159ehled s\u00ed\u0165ov\u00e9ho modelu Teeworlds<\/h1>\n<p>\nS\u00ed\u0165ov\u00e1 architektura Teeworlds je velmi jednoduch\u00e1:<\/p>\n<ul>\n<li>Komponenty klienta a serveru jsou dv\u011b r\u016fzn\u00e9 programy.<\/li>\n<li>Klienti vstupuj\u00ed do hry p\u0159ipojen\u00edm k jednomu z n\u011bkolika server\u016f, z nich\u017e ka\u017ed\u00fd host\u00ed pouze jednu hru najednou.<\/li>\n<li>V\u0161echna data v h\u0159e se p\u0159en\u00e1\u0161ej\u00ed prost\u0159ednictv\u00edm serveru.<\/li>\n<li>Speci\u00e1ln\u00ed hlavn\u00ed server se pou\u017e\u00edv\u00e1 k shroma\u017e\u010fov\u00e1n\u00ed seznamu v\u0161ech ve\u0159ejn\u00fdch server\u016f, kter\u00e9 jsou zobrazeny v hern\u00edm klientu.<\/li>\n<\/ul>\n<p>\nD\u00edky pou\u017eit\u00ed WebRTC pro v\u00fdm\u011bnu dat m\u016f\u017eeme p\u0159esunout serverovou \u010d\u00e1st hry do prohl\u00ed\u017ee\u010de, kde je klient. To n\u00e1m d\u00e1v\u00e1 skv\u011blou p\u0159\u00edle\u017eitost\u2026<\/p>\n<h1>Zbavit se server\u016f<\/h1>\n<p>\nAbsence serverov\u00e9 logiky m\u00e1 p\u0159\u00edjemnou v\u00fdhodu: m\u016f\u017eeme nasadit celou aplikaci jako statick\u00fd obsah na Github Pages nebo na vlastn\u00ed infrastruktu\u0159e za Cloudflare, \u010d\u00edm\u017e si zdarma zajist\u00edme rychl\u00e9 na\u010d\u00edt\u00e1n\u00ed a vysokou dostupnost. V podstat\u011b na n\u011b m\u016f\u017eeme zapomenout, a pokud budeme m\u00edt \u0161t\u011bst\u00ed a hra se stane popul\u00e1rn\u00ed, infrastrukturu nebude t\u0159eba modernizovat.<\/p>\n<p>Aby v\u0161ak syst\u00e9m fungoval, budeme i tak muset vyu\u017e\u00edvat extern\u00ed architekturu:<\/p>\n<ul>\n<li>Jeden nebo v\u00edce server\u016f STUN: m\u00e1me na v\u00fdb\u011br z n\u011bkolika bezplatn\u00fdch variant.<\/li>\n<li>Nejm\u00e9n\u011b jeden server TURN: zde nen\u00ed \u017e\u00e1dn\u00e1 bezplatn\u00e1 varianta, tak\u017ee si m\u016f\u017eeme bu\u010f nastavit vlastn\u00ed, nebo platit za slu\u017ebu. Na\u0161t\u011bst\u00ed v\u011bt\u0161inu \u010dasu bude mo\u017en\u00e9 p\u0159ipojen\u00ed navazovat p\u0159es servery STUN (a zajistit skute\u010dn\u00e9 p2p), ale TURN je pot\u0159ebn\u00fd jako z\u00e1lo\u017en\u00ed mo\u017enost.<\/li>\n<li>Sign\u00e1ln\u00ed server: na rozd\u00edl od dvou dal\u0161\u00edch aspekt\u016f nen\u00ed signalizace standardizov\u00e1na. To, za co bude sign\u00e1ln\u00ed server skute\u010dn\u011b odpov\u011bdn\u00fd, do jist\u00e9 m\u00edry z\u00e1vis\u00ed na aplikaci. V na\u0161em p\u0159\u00edpad\u011b je t\u0159eba p\u0159i navazov\u00e1n\u00ed spojen\u00ed vym\u011bnit mal\u00e9 mno\u017estv\u00ed dat.<\/li>\n<li>Hlavn\u00ed server Teeworlds: pou\u017e\u00edvaj\u00ed ho jin\u00e9 servery k ozn\u00e1men\u00ed sv\u00e9 existence a klienti k hled\u00e1n\u00ed ve\u0159ejn\u00fdch server\u016f. A\u010dkoli nen\u00ed povinn\u00fd (klienti se v\u017edy mohou ru\u010dn\u011b p\u0159ipojit k zn\u00e1m\u00e9mu serveru), bylo by dobr\u00e9 ho m\u00edt, aby se hr\u00e1\u010di mohli zapojit do her s n\u00e1hodn\u00fdmi lidmi.<\/li>\n<\/ul>\n<p>\nRozhodli jsme se pou\u017e\u00edt bezplatn\u00e9 servery STUN od spole\u010dnosti Google a jeden server TURN jsme nasadili sami.<\/p>\n<p>Pro dva posledn\u00ed body jsme pou\u017eili <noindex><a rel=\"nofollow\" href=\"https:\/\/en.wikipedia.org\/wiki\/Firebase\">Firebase<\/a><\/noindex>:<\/p>\n<ul>\n<li>Hlavn\u00ed server Teeworlds je realizov\u00e1n velmi jednodu\u0161e: jako seznam objekt\u016f, kter\u00e9 obsahuj\u00ed informace (n\u00e1zev, IP, mapa, re\u017eim, ...) ka\u017ed\u00e9ho aktivn\u00edho serveru. Servery publikuj\u00ed a aktualizuj\u00ed sv\u00e9 vlastn\u00ed objekty, zat\u00edmco klienti berou cel\u00fd seznam a zobrazuj\u00ed ho hr\u00e1\u010di. Tento seznam tak\u00e9 zobrazujeme na domovsk\u00e9 str\u00e1nce jako HTML, aby hr\u00e1\u010di mohli jednodu\u0161e kliknout na server a p\u0159\u00edmo se dostat do hry.<\/li>\n<li>Signalizace je \u00fazce spjata s na\u0161\u00ed implementac\u00ed soket\u016f, popsanou v n\u00e1sleduj\u00edc\u00ed \u010d\u00e1sti.<\/li>\n<\/ul>\n<p>\n<img decoding=\"async\" alt=\"P\u0159en\u00e1\u0161\u00edme multiplayerovou hru z C++ na web pomoc\u00ed Cheerp, WebRTC a Firebase\" src=\"\/wp-content\/uploads\/2019\/09\/5e30bc22643f8baecf850053bf69e130.jpg\" style=\"display:block;margin: 0 auto;\" \/><br \/>\n<i>Seznam server\u016f uvnit\u0159 hry a na domovsk\u00e9 str\u00e1nce<\/i><\/p>\n<h1>Implementace soket\u016f<\/h1>\n<p>\nChceme vytvo\u0159it API, kter\u00e9 bude co nejbl\u00ed\u017ee Posix UDP soket\u016fm, abychom minimalizovali pot\u0159ebn\u00e9 zm\u011bny.<\/p>\n<p>Chceme tak\u00e9 implementovat nezbytn\u00e9 minimum, kter\u00e9 je nutn\u00e9 pro z\u00e1kladn\u00ed v\u00fdm\u011bnu dat p\u0159es s\u00ed\u0165.<\/p>\n<p>Nap\u0159\u00edklad nepot\u0159ebujeme skute\u010dn\u00e9 sm\u011brov\u00e1n\u00ed: v\u0161echny peer jsou um\u00edst\u011bny v jedn\u00e9 'virtu\u00e1ln\u00ed LAN', kter\u00e1 je spojena s konkr\u00e9tn\u00edm instance datab\u00e1ze Firebase.<\/p>\n<p>Proto nepot\u0159ebujeme unik\u00e1tn\u00ed IP adresy: pro unik\u00e1tn\u00ed identifikaci peer sta\u010d\u00ed pou\u017e\u00edt unik\u00e1tn\u00ed hodnoty kl\u00ed\u010d\u016f Firebase (podobn\u011b jako dom\u00e9nov\u00e1 jm\u00e9na), a ka\u017ed\u00fd peer m\u00edstn\u011b p\u0159i\u0159azuje 'fale\u0161n\u00e9' IP adresy ka\u017ed\u00e9mu kl\u00ed\u010di, kter\u00fd je t\u0159eba p\u0159ev\u00e9st. To n\u00e1s zcela osvobozuje od pot\u0159eby glob\u00e1ln\u00edho p\u0159id\u011blov\u00e1n\u00ed IP adres, co\u017e je netrivi\u00e1ln\u00ed \u00fakol.<\/p>\n<p>Zde je minim\u00e1ln\u00ed API, kter\u00e9 mus\u00edme implementovat:<\/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 je jednoduch\u00e9 a podob\u00e1 se API Posix soket\u016f, ale m\u00e1 n\u011bkolik d\u016fle\u017eit\u00fdch rozd\u00edl\u016f: <strong>registrace zp\u011btn\u00fdch vol\u00e1n\u00ed, p\u0159i\u0159azen\u00ed m\u00edstn\u00edch IP a 'leniv\u00e9' p\u0159ipojen\u00ed<\/strong>.<\/p>\n<h2>Registrace zp\u011btn\u00fdch vol\u00e1n\u00ed<\/h2>\n<p>\nI kdy\u017e p\u016fvodn\u00ed program pou\u017e\u00edv\u00e1 neblokuj\u00edc\u00ed vstup\/v\u00fdstup, pro spu\u0161t\u011bn\u00ed v webov\u00e9m prohl\u00ed\u017ee\u010di je t\u0159eba k\u00f3d refaktorovat.<\/p>\n<p>D\u016fvodem je, \u017ee cyklus ud\u00e1lost\u00ed v prohl\u00ed\u017ee\u010di je skryt p\u0159ed programem (a\u0165 u\u017e se jedn\u00e1 o JavaScript nebo WebAssembly).<\/p>\n<p>V nativn\u00edm prost\u0159ed\u00ed m\u016f\u017eeme ps\u00e1t k\u00f3d t\u00edmto zp\u016fsobem<\/p>\n<pre><code class=\"cpp\">while(running) {\n  select(...); \/\/ \u010dekejte na I\/O ud\u00e1losti\n  while(true) {\n    int r = readfrom(...); \/\/ pokus o \u010dten\u00ed\n    if (r &lt; 0 &amp;&amp; errno == EWOULDBLOCK) \/\/ \u017e\u00e1dn\u00e1 dal\u0161\u00ed data k dispozici\n      break;\n    ...\n  }\n  ...\n}<\/code><\/pre>\n<p>\nPokud je cyklus ud\u00e1lost\u00ed pro n\u00e1s skryt\u00fd, mus\u00edme ho p\u0159ev\u00e9st na n\u011bco podobn\u00e9ho:<\/p>\n<pre><code class=\"cpp\">auto cb = []() { \/\/ toto bude vol\u00e1no, kdy\u017e jsou nov\u00e1 data k dispozici\n  while(true) {\n    int r = readfrom(...); \/\/ pokus o \u010dten\u00ed\n    if (r &lt; 0 &amp;&amp; errno == EWOULDBLOCK) \/\/ \u017e\u00e1dn\u00e1 dal\u0161\u00ed data k dispozici\n      break;\n    ...\n  }\n  ...\n};\nrecvCallback(cb); \/\/ registrace zp\u011btn\u00e9ho vol\u00e1n\u00ed<\/code><\/pre>\n<p><\/p>\n<h2>P\u0159i\u0159azen\u00ed m\u00edstn\u00edch IP<\/h2>\n<p>\nIdentifik\u00e1tory uzl\u016f v na\u0161\u00ed \"s\u00edti\" nejsou IP adresy, ale kl\u00ed\u010de Firebase (jsou to \u0159et\u011bzce, kter\u00e9 vypadaj\u00ed takto: <code>-LmEC50PYZLCiCP-vqde<\/code> ).<\/p>\n<p>Je to pohodln\u00e9, proto\u017ee nepot\u0159ebujeme mechanismus pro p\u0159i\u0159azen\u00ed IP a kontrolu jejich jedine\u010dnosti (ani jejich likvidaci po odpojen\u00ed klienta), ale \u010dasto je nutn\u00e9 identifikovat peer pomoc\u00ed \u010d\u00edseln\u00e9 hodnoty.<\/p>\n<p>Pr\u00e1v\u011b k tomu se pou\u017e\u00edvaj\u00ed funkce <code>resolve<\/code> a <code>reverseResolve<\/code>: aplikace n\u011bjak\u00fdm zp\u016fsobem z\u00edsk\u00e1v\u00e1 \u0159et\u011bzcovou hodnotu kl\u00ed\u010de (prost\u0159ednictv\u00edm vstupu u\u017eivatele nebo p\u0159es master server) a m\u016f\u017ee ji p\u0159ev\u00e9st na IP adresu pro intern\u00ed pou\u017eit\u00ed. Zbytek API tak\u00e9 pro jednoduchost dost\u00e1v\u00e1 m\u00edsto \u0159et\u011bzce tuto hodnotu.<\/p>\n<p>Je to podobn\u00e9 jako DNS dotaz, jen se prov\u00e1d\u00ed lok\u00e1ln\u011b u klienta.<\/p>\n<p>To znamen\u00e1, \u017ee IP adresy nemohou b\u00fdt sd\u00edleny mezi r\u016fzn\u00fdmi klienty, a pokud je pot\u0159eba n\u011bjak\u00fd glob\u00e1ln\u00ed identifik\u00e1tor, mus\u00ed b\u00fdt generov\u00e1n jin\u00fdm zp\u016fsobem.<\/p>\n<h2>Lazy connection<\/h2>\n<p>\nUDP nepot\u0159ebuje p\u0159ipojen\u00ed, ale, jak jsme vid\u011bli, p\u0159ed zah\u00e1jen\u00edm p\u0159enosu dat mezi dv\u011bma peer, WebRTC vy\u017eaduje dlouh\u00fd proces p\u0159ipojen\u00ed.<\/p>\n<p>Pokud chceme zajistit stejnou \u00farove\u0148 abstrakce, (<code>sendto<\/code>\/<code>recvfrom<\/code> s libovoln\u00fdmi peery bez p\u0159edchoz\u00edho p\u0159ipojen\u00ed), mus\u00edme prov\u00e9st \"l\u00edn\u00e9\" (odlo\u017een\u00e9) p\u0159ipojen\u00ed uvnit\u0159 API.<\/p>\n<p>To, co se d\u011bje p\u0159i b\u011b\u017en\u00e9 v\u00fdm\u011bn\u011b dat mezi \"serverem\" a \"klientem\" v p\u0159\u00edpad\u011b pou\u017eit\u00ed UDP, a co by m\u011bla na\u0161e knihovna vykon\u00e1vat:<\/p>\n<ul>\n<li>Server zavol\u00e1 <code>bind()<\/code>, aby ozn\u00e1mil opera\u010dn\u00edmu syst\u00e9mu, \u017ee chce p\u0159ij\u00edmat pakety na ur\u010den\u00e9m portu.<\/li>\n<\/ul>\n<p>\nM\u00edsto toho zve\u0159ejn\u00edme otev\u0159en\u00fd port v Firebase pod kl\u00ed\u010dem serveru a budeme poslouchat ud\u00e1losti v jeho podstromu.<\/p>\n<ul>\n<li>Server zavol\u00e1 <code>recvfrom()<\/code>, p\u0159ij\u00edmat na tento port pakety p\u0159ich\u00e1zej\u00edc\u00ed od jak\u00e9hokoli hostitele.<\/li>\n<\/ul>\n<p>\nV na\u0161em p\u0159\u00edpad\u011b je t\u0159eba kontrolovat p\u0159\u00edchoz\u00ed frontu paket\u016f odeslan\u00fdch do tohoto portu.<\/p>\n<p>Ka\u017ed\u00fd port m\u00e1 svou vlastn\u00ed frontu a na za\u010d\u00e1tek datagram\u016f WebRTC p\u0159id\u00e1v\u00e1me zdrojov\u00e9 a c\u00edlov\u00e9 porty, abychom v\u011bd\u011bli, do kter\u00e9 fronty p\u0159esm\u011brovat nov\u00fd paket p\u0159i jeho p\u0159\u00edjmu.<\/p>\n<p>Zavol\u00e1n\u00ed je neblokuj\u00edc\u00ed, tak\u017ee pokud nejsou \u017e\u00e1dn\u00e9 pakety, jednodu\u0161e vr\u00e1t\u00edme -1 a nastav\u00edme <code>errno=EWOULDBLOCK<\/code>.<\/p>\n<ul>\n<li>Klient z\u00edsk\u00e1v\u00e1 IP a port serveru n\u011bjak\u00fdmi extern\u00edmi prost\u0159edky a vol\u00e1 <code>sendto()<\/code>. Tak\u00e9 se p\u0159itom prov\u00e1d\u00ed intern\u00ed vol\u00e1n\u00ed <code>bind()<\/code>, tak\u017ee n\u00e1sledn\u00fd <code>recvfrom()<\/code> dostane odpov\u011b\u010f bez explicitn\u00edho proveden\u00ed bind.<\/li>\n<\/ul>\n<p>\nV na\u0161em p\u0159\u00edpad\u011b klient extern\u011b z\u00edsk\u00e1v\u00e1 \u0159et\u011bzcov\u00fd kl\u00ed\u010d a pou\u017e\u00edv\u00e1 funkci <code>resolve()<\/code> k z\u00edsk\u00e1n\u00ed IP adresy.<\/p>\n<p>V tomto kroku zahajujeme \u201eruce se pod\u00e1vaj\u00edc\u00ed\u201c WebRTC, pokud dva peer je\u0161t\u011b nejsou spojeni mezi sebou. P\u0159ipojen\u00ed k r\u016fzn\u00fdm port\u016fm jednoho peer pou\u017e\u00edv\u00e1 stejn\u00fd DataChannel WebRTC.<\/p>\n<p>Tak\u00e9 prov\u00e1d\u00edme nep\u0159\u00edm\u00e9 <code>bind()<\/code>, aby server mohl obnovit p\u0159ipojen\u00ed v n\u00e1sleduj\u00edc\u00edm <code>sendto()<\/code> v p\u0159\u00edpad\u011b, \u017ee bylo z n\u011bjak\u00e9ho d\u016fvodu uzav\u0159eno.<\/p>\n<p>Server je informov\u00e1n o p\u0159ipojen\u00ed klienta, kdy\u017e klient zapisuje sv\u016fj SDP nab\u00eddku pod informac\u00ed o portu serveru do Firebase, a server zde tak\u00e9 odpov\u00edd\u00e1 svou odpov\u011bd\u00ed.<\/p>\n<p>\nNa n\u00ed\u017ee uveden\u00e9m sch\u00e9matu je uveden p\u0159\u00edklad pohybu zpr\u00e1v pro sch\u00e9ma socket\u016f a p\u0159ed\u00e1n\u00ed prvn\u00ed zpr\u00e1vy od klienta k serveru:<\/p>\n<p><img decoding=\"async\" alt=\"P\u0159en\u00e1\u0161\u00edme multiplayerovou hru z C++ na web pomoc\u00ed Cheerp, WebRTC a Firebase\" src=\"\/wp-content\/uploads\/2019\/09\/7dc48e816c09e33daf28c307cad0c258.jpg\" style=\"display:block;margin: 0 auto;\" \/><br \/>\n<i>Cel\u00e9 sch\u00e9ma f\u00e1ze p\u0159ipojen\u00ed mezi klientem a serverem<\/i><\/p>\n<h1>Z\u00e1v\u011br<\/h1>\n<p>\nPokud jste do\u010detli a\u017e do konce, asi v\u00e1s zaj\u00edm\u00e1 pod\u00edvat se na teorii v akci. Hru si m\u016f\u017eete zahr\u00e1t na <noindex><a rel=\"nofollow\" href=\"https:\/\/teeworlds.leaningtech.com\">teeworlds.leaningtech.com<\/a><\/noindex>, zkuste to!<\/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>P\u0159\u00e1telsk\u00e9 utk\u00e1n\u00ed mezi kolegy<\/i><\/p>\n<p>K\u00f3d s\u00ed\u0165ov\u00e9 knihovny je voln\u011b dostupn\u00fd na <noindex><a rel=\"nofollow\" href=\"https:\/\/github.com\/leaningtech\/cheerpnet\">Github<\/a><\/noindex>. P\u0159ipojte se k diskuzi na na\u0161em kan\u00e1lu v <noindex><a rel=\"nofollow\" href=\"https:\/\/gitter.im\/leaningtech\/cheerp\">Gitter<\/a><\/noindex>!<br \/>\n<br \/>Zdroj: <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.3 - 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\/cs\/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.3\" \/>\n\t\t<meta property=\"og:locale\" content=\"cs_CZ\" \/>\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\/cs\/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\udd47Portujeme multiplayerovou hru z C++ na web pomoc\u00ed Cheerp, WebRTC a Firebase | ProHoster","description":"\u00davod Na\u0161e spole\u010dnost Leaning Technologies poskytuje \u0159e\u0161en\u00ed pro portov\u00e1n\u00ed tradi\u010dn\u00edch desktopov\u00fdch aplikac\u00ed do webu.","canonical_url":"https:\/\/prohoster.info\/cs\/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":"cs_CZ","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\/cs\/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\/cs\/wp-json\/wp\/v2\/posts\/38370","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/prohoster.info\/cs\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/prohoster.info\/cs\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/prohoster.info\/cs\/wp-json\/wp\/v2\/users\/1"}],"replies":[{"embeddable":true,"href":"https:\/\/prohoster.info\/cs\/wp-json\/wp\/v2\/comments?post=38370"}],"version-history":[{"count":0,"href":"https:\/\/prohoster.info\/cs\/wp-json\/wp\/v2\/posts\/38370\/revisions"}],"wp:featuredmedia":[{"embeddable":true,"href":"https:\/\/prohoster.info\/cs\/wp-json\/wp\/v2\/media\/28801"}],"wp:attachment":[{"href":"https:\/\/prohoster.info\/cs\/wp-json\/wp\/v2\/media?parent=38370"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/prohoster.info\/cs\/wp-json\/wp\/v2\/categories?post=38370"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/prohoster.info\/cs\/wp-json\/wp\/v2\/tags?post=38370"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}