{"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\/it\/blog\/administrirovanie\/portiruem-mnogopolzovatelskuyu-igru-s-s-na-veb-c-cheerp-webrtc-i-firebase","title":{"rendered":"Portiamo un gioco multiplayer da C++ al web con Cheerp, WebRTC e Firebase","gt_translate_keys":[{"key":"rendered","format":"text"}]},"content":{"rendered":"<h2>Introduzione<\/h2>\n<p>\nLa nostra azienda <noindex><a rel=\"nofollow\" href=\"https:\/\/leaningtech.com\">Leaning Technologies<\/a><\/noindex> fornisce soluzioni per il porting delle tradizionali applicazioni desktop nel web. Il nostro compilatore C++ <noindex><a rel=\"nofollow\" href=\"https:\/\/github.com\/leaningtech\/cheerp-meta\">Cheerp<\/a><\/noindex> genera una combinazione di WebAssembly e JavaScript, garantendo sia <noindex><a rel=\"nofollow\" href=\"https:\/\/github.com\/leaningtech\/cheerp-meta\/wiki\/Cheerp-Tutorial%3A-Mixed-mode-C++-to-WebAssembly-and-JavaScript\">un'interazione semplice con il browser<\/a><\/noindex>, sia alte prestazioni.<\/p>\n<p>Come esempio del suo utilizzo, abbiamo deciso di portare sul web un gioco multiplayer e abbiamo scelto <noindex><a rel=\"nofollow\" href=\"https:\/\/www.teeworlds.com\/\"><strong>Teeworlds<\/strong><\/a><\/noindex>. Teeworlds \u00e8 un gioco retro 2D multiplayer con una comunit\u00e0 di giocatori piccola ma attiva (incluso me!). \u00c8 leggero sia in termini di risorse scaricate che di requisiti per CPU e GPU: il candidato ideale.<\/p>\n<p><img decoding=\"async\" alt=\"Portiamo un gioco multiplayer da C++ al web con Cheerp, WebRTC e Firebase\" src=\"\/wp-content\/uploads\/2019\/09\/30bff36ee1158ddea2e76d8b11e48d2b.jpg\" style=\"display:block;margin: 0 auto;\" \/><br \/>\n<i>Teeworlds in esecuzione nel browser<\/i><br \/>\n<noindex><a rel=\"nofollow\" name=\"habracut\"><\/a><\/noindex><br \/>\nAbbiamo deciso di utilizzare questo progetto per sperimentare con <strong>soluzioni generali per il porting del codice di rete per il web<\/strong>. Di solito questo viene realizzato nei seguenti modi:<\/p>\n<ul>\n<li><strong>XMLHttpRequest\/fetch<\/strong>, se la parte di rete consiste solo in richieste HTTP, oppure<\/li>\n<li><strong>WebSockets<\/strong>.<\/li>\n<\/ul>\n<p>\nEntrambe le soluzioni richiedono di ospitare il componente server sul lato server, e nessuna di esse consente di utilizzare come protocollo di trasporto <noindex><a rel=\"nofollow\" href=\"https:\/\/en.wikipedia.org\/wiki\/User_Datagram_Protocol\">UDP<\/a><\/noindex>. Questo \u00e8 fondamentale per le applicazioni in tempo reale, come software per videoconferenze e giochi, poich\u00e9 le garanzie di consegna e l'ordine dei pacchetti possono interferire con la bassa latenza. <noindex><a rel=\"nofollow\" href=\"https:\/\/en.wikipedia.org\/wiki\/Transmission_Control_Protocol\">TCP<\/a><\/noindex> possono ostacolare le basse latenze.<\/p>\n<p>Esiste anche un terzo modo: utilizzare una rete tra 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> supporta sia la trasmissione affidabile che quella non affidabile (in quest'ultimo caso cerca di utilizzare UDP come protocollo di trasporto quando possibile) e pu\u00f2 essere utilizzato sia con un server remoto che tra browser. <strong>Questo significa che possiamo portare l'intera applicazione nel browser, incluso il componente server!<\/strong><\/p>\n<p>Tuttavia, ci\u00f2 comporta una difficolt\u00e0 aggiuntiva: prima che due peer WebRTC possano scambiarsi dati, devono eseguire una procedura di \"handshake\" relativamente complessa per connettersi, che richiede diverse entit\u00e0 esterne (server di segnalazione e uno o pi\u00f9 server <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>In ideale, vorremmo creare un'API di rete che utilizzi WebRTC internamente, ma che sia il pi\u00f9 vicino possibile all'interfaccia di UDP Sockets, la quale non richiede l'instaurazione di una connessione.<\/p>\n<p>Questo ci permetter\u00e0 di sfruttare i vantaggi di WebRTC senza la necessit\u00e0 di rivelare dettagli complessi del codice dell'applicazione (che nel nostro progetto volevamo modificare il meno possibile).<\/p>\n<h1>WebRTC minimale<\/h1>\n<p>\nWebRTC \u00e8 un insieme di API disponibili nei browser che consente la trasmissione di audio, video e dati arbitrari in peer-to-peer.<\/p>\n<p>La connessione tra i peer \u00e8 stabilita (anche in presenza di NAT da una o entrambe le parti) tramite server STUN e\/o TURN attraverso un meccanismo chiamato ICE. I peer scambiano informazioni ICE e parametri dei canali tramite l'offerta e la risposta del protocollo SDP.<\/p>\n<p>Wow! Quante abbreviazioni in una sola volta. Spieghiamo brevemente cosa significano questi concetti:<\/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 un protocollo per il bypass del NAT e l'ottenimento di una coppia (IP, porta) per scambiare dati direttamente con l'host. Se riesce nel suo compito, i peer possono scambiarsi dati tra di loro in autonomia.<\/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> viene utilizzato anche per bypassare il NAT, ma lo fa inoltrando i dati tramite un proxy visibile a entrambi i peer. Aggiunge un ritardo ed \u00e8 pi\u00f9 costoso in termini di esecuzione rispetto a STUN (perch\u00e9 viene applicato per tutta la durata della sessione), ma a volte \u00e8 l'unica opzione possibile.<\/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> viene utilizzato per selezionare il miglior metodo possibile per connettere due peer sulla base delle informazioni ottenute durante la connessione diretta tra i peer, oltre alle informazioni ricevute da un numero qualunque di server STUN e 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> \u00e8 un formato per descrivere i parametri del canale di connessione, ad esempio i candidati ICE, i codec multimediali (nel caso di canali audio\/video), e cos\u00ec via... Uno dei peer invia un SDP Offer (\"offerta\") e l'altro risponde con un SDP Answer (\"risposta\"). Dopo questo, viene creato un canale.<\/li>\n<\/ul>\n<p>\nPer stabilire tale connessione, i peer devono raccogliere le informazioni ricevute dai server STUN e TURN e scambiarsi queste informazioni tra di loro.<\/p>\n<p>Il problema \u00e8 che al momento non hanno la possibilit\u00e0 di scambiarsi dati direttamente, quindi deve esistere un meccanismo out-of-band per scambiare questi dati: il server di segnalazione.<\/p>\n<p>Un server di segnalazione pu\u00f2 essere molto semplice, poich\u00e9 il suo unico compito \u00e8 quello di reindirizzare i dati tra i peer durante la fase di \u00abhandshake\u00bb (come mostrato nello schema qui sotto).<\/p>\n<p><img decoding=\"async\" alt=\"Portiamo un gioco multiplayer da C++ al web con Cheerp, WebRTC e Firebase\" src=\"\/wp-content\/uploads\/2019\/09\/0fc39ba5e0942bc19183e8cf06c60868.jpg\" style=\"display:block;margin: 0 auto;\" \/><br \/>\n<i>Schema semplificato della sequenza di \u00abhandshake\u00bb WebRTC<\/i><\/p>\n<h1>Panoramica del modello di rete di Teeworlds<\/h1>\n<p>\nL'architettura di rete di Teeworlds \u00e8 molto semplice:<\/p>\n<ul>\n<li>I componenti client e server sono due programmi distinti.<\/li>\n<li>I client partecipano al gioco connettendosi a uno dei diversi server, ciascuno dei quali ospita solo una partita alla volta.<\/li>\n<li>Tutta la trasmissione dati nel gioco avviene tramite il server.<\/li>\n<li>Un master server speciale \u00e8 utilizzato per raccogliere l'elenco di tutti i server pubblici, che vengono visualizzati nel client di gioco.<\/li>\n<\/ul>\n<p>\nGrazie all'uso di WebRTC per lo scambio di dati, possiamo trasferire il componente server del gioco nel browser, dove si trova il client. Questo ci offre un'opportunit\u00e0 fantastica...<\/p>\n<h1>Eliminare i server.<\/h1>\n<p>\nL'assenza di logica server ha un vantaggio notevole: possiamo distribuire l'intera applicazione come contenuto statico su Github Pages o su hardware dedicato dietro Cloudflare, garantendo cos\u00ec caricamenti rapidi e alta disponibilit\u00e0 senza costi. In effetti, potremmo dimenticarcene, e se siamo fortunati e il gioco diventa popolare, non dovremo aggiornare l'infrastruttura.<\/p>\n<p>Tuttavia, affinch\u00e9 il sistema funzioni, dovremo comunque utilizzare un'architettura esterna:<\/p>\n<ul>\n<li>Uno o pi\u00f9 server STUN: abbiamo diverse opzioni gratuite tra cui scegliere.<\/li>\n<li>Almeno un server TURN: qui non ci sono opzioni gratuite, quindi possiamo o configurare il nostro server, o pagare un servizio. Fortunatamente, la maggior parte delle volte la connessione pu\u00f2 essere stabilita tramite i server STUN (garantendo un vero p2p), ma il TURN \u00e8 necessario come opzione di riserva.<\/li>\n<li>Server di segnalazione: a differenza degli altri due aspetti, la segnalazione non \u00e8 standardizzata. Ci\u00f2 di cui si occupa effettivamente il server di segnalazione dipende in parte dall'applicazione. Nel nostro caso, \u00e8 necessario scambiare un piccolo volume di dati prima di stabilire la connessione.<\/li>\n<li>Server master di Teeworlds: esso viene utilizzato da altri server per notificare la propria esistenza e dai client per cercare server pubblici. Anche se non \u00e8 obbligatorio (i client possono sempre connettersi manualmente ai server di cui sono a conoscenza), sarebbe utile averlo affinch\u00e9 i giocatori possano partecipare a giochi con persone casuali.<\/li>\n<\/ul>\n<p>\nAbbiamo deciso di utilizzare i server STUN gratuiti di Google, mentre un server TURN \u00e8 stato distribuito autonomamente.<\/p>\n<p>Per gli ultimi due punti abbiamo utilizzato <noindex><a rel=\"nofollow\" href=\"https:\/\/en.wikipedia.org\/wiki\/Firebase\">Firebase<\/a><\/noindex>:<\/p>\n<ul>\n<li>Il master server di Teeworlds \u00e8 implementato in modo molto semplice: come un elenco di oggetti che contengono informazioni (nome, IP, mappa, modalit\u00e0, \u2026) di ciascun server attivo. I server pubblicano e aggiornano il proprio oggetto, mentre i client prendono l'intero elenco e lo visualizzano per il giocatore. Mostriamo anche l'elenco nella homepage come HTML, consentendo ai giocatori di cliccare direttamente sul server e accedere al gioco.<\/li>\n<li>La segnalazione \u00e8 strettamente legata alla nostra implementazione dei socket, descritta nel prossimo capitolo.<\/li>\n<\/ul>\n<p>\n<img decoding=\"async\" alt=\"Portiamo un gioco multiplayer da C++ al web con Cheerp, WebRTC e Firebase\" src=\"\/wp-content\/uploads\/2019\/09\/5e30bc22643f8baecf850053bf69e130.jpg\" style=\"display:block;margin: 0 auto;\" \/><br \/>\n<i>Elenco dei server all'interno del gioco e sulla homepage<\/i><\/p>\n<h1>Implementazione dei socket<\/h1>\n<p>\nVogliamo creare un'API il pi\u00f9 vicina possibile ai socket UDP Posix, per ridurre al minimo le modifiche necessarie.<\/p>\n<p>Vogliamo anche implementare il minimo necessario richiesto per il pi\u00f9 semplice scambio di dati in rete.<\/p>\n<p>Ad esempio, non abbiamo bisogno di vera routing: tutti i peer si trovano all'interno di una 'LAN virtuale' collegata a una specifica istanza del database Firebase.<\/p>\n<p>Pertanto, non abbiamo bisogno di indirizzi IP unici: per la identificazione unica dei peer \u00e8 sufficiente utilizzare valori unici delle chiavi Firebase (simile ai nomi di dominio), e ogni peer assegna localmente degli indirizzi IP \"falsi\" a ciascuna chiave da convertire. Questo ci libera completamente dalla necessit\u00e0 di una assegnazione globale di indirizzi IP, che \u00e8 un compito non banale.<\/p>\n<p>Ecco l'API minima che dobbiamo implementare:<\/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>\nL'API \u00e8 semplice e simile all'API dei Posix Sockets, ma presenta alcune differenze importanti: <strong>registrazione dei callback, assegnazione di IP locali e connessione \"pigra\"<\/strong>.<\/p>\n<h2>Registrazione dei callback<\/h2>\n<p>\nAnche se il programma originale utilizza input\/output non bloccante, il codice deve essere rifattorizzato per l'esecuzione nel browser.<\/p>\n<p>La ragione \u00e8 che il ciclo degli eventi nel browser \u00e8 nascosto al programma (che sia JavaScript o WebAssembly).<\/p>\n<p>Nell'ambiente nativo, possiamo scrivere il codice in questo modo<\/p>\n<pre><code class=\"cpp\">while(running) {\n  select(...); \/\/ aspetta eventi I\/O\n  while(true) {\n    int r = readfrom(...); \/\/ prova a leggere\n    if (r &lt; 0 &amp;&amp; errno == EWOULDBLOCK) \/\/ non ci sono pi\u00f9 dati disponibili\n      break;\n    ...\n  }\n  ...\n}<\/code><\/pre>\n<p>\nSe il ciclo degli eventi \u00e8 nascosto per noi, dobbiamo trasformarlo in qualcosa di simile:<\/p>\n<pre><code class=\"cpp\">auto cb = []() { \/\/ questo sar\u00e0 chiamato quando sono disponibili nuovi dati\n  while(true) {\n    int r = readfrom(...); \/\/ prova a leggere\n    if (r &lt; 0 &amp;&amp; errno == EWOULDBLOCK) \/\/ non ci sono pi\u00f9 dati disponibili\n      break;\n    ...\n  }\n  ...\n};\nrecvCallback(cb); \/\/ registra il callback<\/code><\/pre>\n<p><\/p>\n<h2>Assegnazione degli IP locali<\/h2>\n<p>\nGli identificatori dei nodi nella nostra \"rete\" non sono indirizzi IP, ma chiavi Firebase (sono stringhe che appaiono cos\u00ec: <code>-LmEC50PYZLCiCP-vqde<\/code> ).<\/p>\n<p>Questo \u00e8 comodo, perch\u00e9 non abbiamo bisogno di un meccanismo per assegnare IP e controllarne l'unicit\u00e0 (cos\u00ec come per la loro dismissione dopo la disconnessione del cliente), ma spesso \u00e8 necessario identificare i peer tramite un valore numerico.<\/p>\n<p>\u00c8 proprio per questo che vengono utilizzate le funzioni <code>resolve<\/code> e <code>reverseResolve<\/code>: l'applicazione ottiene in qualche modo un valore stringa della chiave (attraverso l'immissione dell'utente o tramite un server principale) e pu\u00f2 convertirlo in un indirizzo IP per uso interno. Il resto dell'API riceve anche per semplicit\u00e0 questo valore al posto della stringa.<\/p>\n<p>\u00c8 simile a una ricerca DNS, solo che viene eseguita localmente sul client.<\/p>\n<p>Cio\u00e8, gli indirizzi IP non possono essere condivisi tra diversi clienti, e se \u00e8 necessario un identificatore globale, allora deve essere generato in un altro modo.<\/p>\n<h2>Connessione pigra<\/h2>\n<p>\nUDP non richiede una connessione, ma, come abbiamo visto, prima di iniziare a trasferire dati tra due peer, WebRTC richiede un lungo processo di connessione.<\/p>\n<p>Se vogliamo garantire lo stesso livello di astrazione, (<code>sendto<\/code>\/<code>recvfrom<\/code> con peer casuali senza una connessione preliminare), dobbiamo eseguire una connessione \u00abpigra\u00bb (ritardata) all'interno dell'API.<\/p>\n<p>Ecco cosa accade durante uno scambio di dati normale tra un \u00abserver\u00bb e un \u00abclient\u00bb quando si utilizza UDP, e cosa deve fare la nostra libreria:<\/p>\n<ul>\n<li>Il server chiama <code>bind()<\/code>, per informare il sistema operativo che desidera ricevere pacchetti sulla porta specificata.<\/li>\n<\/ul>\n<p>\nInvece, pubblicheremo una porta aperta in Firebase con la chiave del server e ascolteremo gli eventi nel suo sottoalbero.<\/p>\n<ul>\n<li>Il server chiama <code>recvfrom()<\/code>, accettando in questa porta pacchetti provenienti da qualsiasi host.<\/li>\n<\/ul>\n<p>\nNel nostro caso, dobbiamo controllare la coda dei pacchetti in arrivo inviati a questa porta.<\/p>\n<p>Ogni porta ha la sua coda, e noi aggiungiamo in testa ai datagrammi WebRTC le porte sorgente e destinazione, per sapere in quale coda deviare il nuovo pacchetto al suo arrivo.<\/p>\n<p>La chiamata \u00e8 non bloccante, quindi se non ci sono pacchetti, restituiamo semplicemente -1 e poniamo <code>errno=EWOULDBLOCK<\/code>.<\/p>\n<ul>\n<li>Il client ottiene l'IP e la porta del server tramite mezzi esterni e invoca <code>sendto()<\/code>. Viene inoltre effettuata una chiamata interna <code>bind()<\/code>, quindi la successiva <code>recvfrom()<\/code> ricever\u00e0 una risposta senza un'esplicita esecuzione di bind.<\/li>\n<\/ul>\n<p>\nNel nostro caso, il client ottiene esternamente una chiave in formato stringa e utilizza la funzione <code>resolve()<\/code> per ottenere l'indirizzo IP.<\/p>\n<p>A questo punto, iniziamo il \u00abhandshake\u00bb WebRTC, se i due peer non sono gi\u00e0 connessi tra loro. Le connessioni a porte diverse dello stesso peer utilizzano lo stesso DataChannel WebRTC.<\/p>\n<p>Inoltre, eseguiamo un collegamento indiretto <code>bind()<\/code>, affinch\u00e9 il server possa ripristinare la connessione nel caso in cui questa si fosse chiusa per qualche motivo. <code>sendto()<\/code> Il server viene informato della connessione del client quando il client registra la propria offerta SDP sotto le informazioni sulla porta del server in Firebase e il server risponde con la propria risposta.<\/p>\n<p>Nello schema mostrato di seguito \u00e8 illustrato un esempio del flusso dei messaggi per lo schema dei socket e la trasmissione del primo messaggio dal client al server:<\/p>\n<p>\nSchema completo della fase di connessione tra client e server<\/p>\n<p><img decoding=\"async\" alt=\"Portiamo un gioco multiplayer da C++ al web con Cheerp, WebRTC e Firebase\" src=\"\/wp-content\/uploads\/2019\/09\/7dc48e816c09e33daf28c307cad0c258.jpg\" style=\"display:block;margin: 0 auto;\" \/><br \/>\n<i>Se sei arrivato fino a qui, probabilmente sei curioso di vedere la teoria in azione. Puoi giocare su<\/i><\/p>\n<h1>Conclusione<\/h1>\n<p>\nteeworlds.leaningtech.com <noindex><a rel=\"nofollow\" href=\"https:\/\/teeworlds.leaningtech.com\">, prova!<\/a><\/noindex>, prova!<\/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>Partita amichevole tra colleghi<\/i><\/p>\n<p>Il codice della libreria di rete \u00e8 liberamente disponibile su <noindex><a rel=\"nofollow\" href=\"https:\/\/github.com\/leaningtech\/cheerpnet\">Github<\/a><\/noindex>. Unisciti alla conversazione sul nostro canale in <noindex><a rel=\"nofollow\" href=\"https:\/\/gitter.im\/leaningtech\/cheerp\">Gitter<\/a><\/noindex>!<br \/>\n<br \/>Fonte: <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\/it\/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=\"it_IT\" \/>\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\/it\/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\udd47Portiamo un gioco multiplayer da C++ al web con Cheerp, WebRTC e Firebase | ProHoster","description":"Introduzione La nostra azienda Leaning Technologies offre soluzioni per il porting delle tradizionali applicazioni desktop al web. Il nostro compilatore C++ Cheerp genera una combinazione di WebAssembly e JavaScript, garantendo sia una semplice interazione con il browser sia alte prestazioni. Come esempio della sua applicazione, abbiamo deciso di portare per il web un gioco multiplayer e abbiamo scelto Teeworlds. Teeworlds \u00e8 un gioco retro 2D multiplayer.","canonical_url":"https:\/\/prohoster.info\/it\/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":"it_IT","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\/it\/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\/it\/wp-json\/wp\/v2\/posts\/38370","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/prohoster.info\/it\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/prohoster.info\/it\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/prohoster.info\/it\/wp-json\/wp\/v2\/users\/1"}],"replies":[{"embeddable":true,"href":"https:\/\/prohoster.info\/it\/wp-json\/wp\/v2\/comments?post=38370"}],"version-history":[{"count":0,"href":"https:\/\/prohoster.info\/it\/wp-json\/wp\/v2\/posts\/38370\/revisions"}],"wp:featuredmedia":[{"embeddable":true,"href":"https:\/\/prohoster.info\/it\/wp-json\/wp\/v2\/media\/28801"}],"wp:attachment":[{"href":"https:\/\/prohoster.info\/it\/wp-json\/wp\/v2\/media?parent=38370"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/prohoster.info\/it\/wp-json\/wp\/v2\/categories?post=38370"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/prohoster.info\/it\/wp-json\/wp\/v2\/tags?post=38370"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}