Felhasználásra kész, nyílt forráskódú felhős játékzés WebRTC-n: p2p, multiplayer, zero latency.

Felhasználásra kész, nyílt forráskódú felhős játékzés WebRTC-n: p2p, multiplayer, zero latency.
Szolgáltatásként megvalósított ESET, infrastruktúra mint szolgáltatás, platform mint szolgáltatás, kommunikációs platform mint szolgáltatás, videokonferenciák mint szolgáltatás, mi a helyzet a felhő alapú játékokkal mint szolgáltatással? Már számos kísérlet történt a felhőalapú játékok (Cloud Gaming) létrehozására, például a Stadia, amelyet a Google indított el nemrégiben. Stadia nem újdonság a WebRTC világában, de tudják-e mások is használni a WebRTC-t ugyanúgy?

Thanh Nguyen úgy döntött, hogy ellenőrzi ezt a lehetőséget az open-source CloudRetro projektjével. A CloudRetro a Pion-ra épül, népszerű Go alapú WebRTC könyvtár (köszönjük Shonnak a Pion fejlesztőcsapatából a cikk előkészítésében nyújtott segítségért). Ebben a cikkben Thanh áttekinti projektje architektúráját, valamint elmondja, hogy mit tanult és milyen kihívásokkal találkozott a munka során.

Bevezetés

Tavaly, amikor a Google bejelentette a Stadiát, egyszerűen lenyűgözött. Az ötlet olyan egyedi és innovatív, hogy folyamatosan azon tűnődtem, hogyan lehetséges ez a meglévő technológiák mellett. Az, hogy jobban megértsem ezt a témát, arra ösztökélt, hogy létrehozzam a saját open-source felhős játékomat. Az eredmény egyszerűen fantasztikus volt. Az alábbiakban szeretném megosztani a munkám folyamatát a három éves projekt.

TLDR: rövid diaképes verzió a főbb pontokkal

Miért van a felhőalapú játékoknak jövője

Hiszem, hogy a Cloud Gaming hamarosan az új generációt képviseli nemcsak a játékokban, hanem más informatikai területeken is. A felhőalapú játékok a kliens/szerver modell csúcsa. Ez a modell maximalizálja a háttérkezelést és minimalizálja a frontend munkáját azzal, hogy a játéklogikát távoli szerveren helyezi el, miközben képek/audio streamelését biztosítja a kliens felé. A szerver végzi a nehéz feldolgozást, így a kliens már nem függ a hardver korlátaitól.

A Google Stadia lényegében lehetővé teszi AAA-játékok (azaz prémium, blokbusterek közé sorolt játékok) játszását a YouTube-szerű felületen. Ugyanazt a metodológiát más nehéz offline alkalmazásokra is lehet alkalmazni, mint például egy operációs rendszer vagy 2D/3D grafikai tervezés, stb., hogy stabilan futtathassuk őket alacsony teljesítményű eszközökön különböző platformokon. Ennek a technológiának a jövője: képzelje el, hogy a Microsoft Windows 10 böngészőben futna, mint a Chrome?

Felhasználásra kész, nyílt forráskódú felhős játékzés WebRTC-n: p2p, multiplayer, zero latency.
A felhőalapú játékok technikailag bonyolultak.

A felhő alapú játékok technikailag bonyolultak

A játékzás az egyik ritka terület, ahol a felhasználó folyamatos gyors reakciója szükséges. Ha ritkán találkozunk 2 másodperces késéssel a weboldalon, az elfogadható. Az élő videostreaming általában néhány másodperces lemaradással működik, de még mindig megfelelő kényelmet biztosít. Azonban ha a játék gyakran 500 ms-os késleltetést tapasztal, már lehetetlenné válik a játék. Célunk az extrém alacsony késés elérése, hogy a bemenet és a média közötti szakadék a lehető legkisebb legyen. Ezért a hagyományos video streaming megközelítések itt nem alkalmazhatók.

Felhasználásra kész, nyílt forráskódú felhős játékzés WebRTC-n: p2p, multiplayer, zero latency.
Átfogó sablon a felhős játékhoz

Nyílt forráskódú CloudRetro projekt

Úgy döntöttem, hogy létrehozok egy tesztpéldányt a felhős játékhoz, hogy ellenőrizzem, hogy mindez lehetséges-e ilyen szigorú hálózati korlátok mellett. A koncepció teszteléséhez a Golang-ot választottam, mivel ez a számomra legjobban ismert nyelv, és sok más okból is jól illik ehhez a megvalósításhoz, ahogy később kiderült. A Go egyszerű és nagyon gyorsan fejlődik; a Go csatornák tökéletesen alkalmasak a párhuzamosság kezelésére.

Projekt CloudRetro.io – nyílt forráskódú felhős játékszolgáltatás retro játékokhoz. A projekt célja, hogy a hagyományos retro játékokba a lehető legkényelmesebb játékélményt hozza, és multiplayer lehetőségeket adjon hozzá.
A projektről részletesen itt tájékozódhat: https://github.com/giongto35/cloud-game.

CloudRetro funkcionalitás

A CloudRetro bemutatásához a retro játékok mutatják be a felhős játékok teljes erejét. Ez számos egyedi játékélményt eredményez.

  • A játék hordozhatósága
    • Azonnali lejátszás az oldal megnyitásakor; letöltés és telepítés nem szükséges
    • Mobil böngészőben működik, így nincs szükség szoftver telepítésére a futtatáshoz

  • A játék ülését több eszközön megoszthatja, és felhőben tárolhatja a következő bejelentkezéshez
  • A játékot streamelheti, vagy több felhasználó egyszerre játszhat benne:
    • Crowdplay típusú TwitchPlayPokemon, csak több platformon és valós idejűbb
    • Offline játék az online térben. Sok felhasználó játszhat anélkül, hogy a hálózatot be kellene állítani. A Samurai Shodown most már 2 játékos által játszható a CloudRetro hálózatán

    Felhasználásra kész, nyílt forráskódú felhős játékzés WebRTC-n: p2p, multiplayer, zero latency.
    Demó verzió a többjátékos online játékhoz különböző eszközökön

    Infrastruktúra

    Követelmények és technológiai stack

    Az alábbiakban található a követelmények listája, amelyeket a projekt megkezdése előtt állítottam fel.

    1. Egy játékos
    Ez a követelmény talán nem tűnik túl fontosnak és nyilvánvalónak itt, de ez az egyik kulcsfontosságú megállapításom, hogy a felhőalapú játékok minél távolabb tarthassák magukat a hagyományos streaming szolgáltatásoktól. Ha a single-player játékra összpontosítunk, megszabadulhatunk a központosított szervertől vagy CDN-től, mert nincs szükségünk arra, hogy tömegesen streameljünk. Ahelyett, hogy folyamokat töltenénk fel egy igénybe vevő szerverre vagy csomagokat küldenénk egy központosított WebSocket szerverre, a szolgáltatói folyamok közvetlenül a felhasználóhoz jutnak el egy peer-to-peer WebRTC kapcsolaton keresztül.

    2. Alacsony késleltetésű médiastream
    Miközben a Stadiáról olvastam, gyakran találkozom a WebRTC említésével egyes cikkekben. Megértettem, hogy a WebRTC egy kiemelkedő technológia, és tökéletesen alkalmas a felhőalapú játékokhoz. A WebRTC egy projekt, amely valós idejű kommunikációt biztosít a web böngészők és mobil alkalmazások számára, egy egyszerű API-n keresztül. Ez egy peer-to-peer kapcsolatot biztosít, amely optimalizált a médiához és beépített szabványos kodekekkel rendelkezik, mint például VP8 és H264.

    Én a felhasználók maximális kényelmének biztosítását részesítettem előnyben, nem pedig a grafika magas színvonalának megőrzését. Az algoritmusban megengedettek bizonyos veszteségek. A Google Stadiánál van egy plusz lépés a kép méretének csökkentésére a szerveren, így a képkockák magasabb minőségre skálázódnak a peer-to-peer csomópontokhoz való továbbítás előtt.

    3. Földrajzi irányítással rendelkező elosztott infrastruktúra
    Függetlenül attól, hogy mennyire van optimalizálva a tömörítési algoritmus és a kód, a hálózat továbbra is döntő tényező, amely leginkább hozzájárul a késleltetéshez. Az architektúrának rendelkeznie kell egy mechanizmussal, amely illeszti a felhasználóhoz legközelebbi szervert a késleltetési idő (RTT) csökkentésére. Az architektúrának 1 koordinátorral és több streaming szerverrel kell rendelkeznie, amelyek szerte a világban helyezkednek el: Nyugat-Egyesült Államok, Kelet-Egyesült Államok, Európa, Szingapúr, Kína. Minden streaming szervernek teljesen izoláltnak kell lennie. A rendszer képes kezelni a terjesztését, amikor a szerver csatlakozik a hálózathoz vagy kilép belőle. Így nagy forgalom esetén további szerverek hozzáadása lehetővé teszi a vízszintes skálázást.

    4. Böngészőkompatibilitás
    A felhőalapú játékok legjobban akkor teljesítenek, ha a felhasználóktól a lehető legkevesebb erőforrást igényelnek. Ez azt jelenti, hogy lehetőség van a játékok böngészőben való indítására. A böngészők segítenek a játékélmény maximális kényelmének biztosításában, eltüntetve a szükségességet programok és hardverek telepítésére. A böngészők a mobil és asztali verziók közötti platformfüggetlenséget is segítik. Szerencsére a WebRTC jól támogatott a különböző böngészőkben.

    5. A játéknak és a szolgáltatásnak a jól elkülönített interfésze
    A felhőalapú játékokat platformként tekintek. Mindenki számára lehetőséget kell biztosítani a platformhoz való csatlakozásra. Jelenleg integráltam LibRetro a felhőalapú játék szolgáltatással, mert a LibRetro szép emulátor felületet kínál retro játékokhoz, például SNES, GBA, PS.

    6. Multiplayer szobák, crowd play és külső deep-linkelés a játékhoz
    A CloudRetro számos új játékformátumot támogat, mint például a CrowdPlay és az Online MultiPlayer retro játékokhoz. Ha több felhasználó ugyanazt a deep-linket nyitja meg különböző számítógépeken, ugyanazt a játékot fogják látni, és akár csatlakozhatnak is hozzá.

    Továbbá, a játékállapotokat a felhőben tárolják. Ez lehetővé teszi a felhasználók számára, hogy bármikor bármilyen más eszközről folytathassák a játékot.

    7. Horizontális skálázhatóság
    Mint minden SAAS esetében, a felhőalapú játékokat is úgy kell tervezni, hogy horizontálisan skálázhatóak legyenek. A 'koordinátor-munkás' felépítés lehetővé teszi, hogy további munkásokat adjunk hozzá a nagyobb forgalom kiszolgálásához.

    8. Nincs kötődés egyetlen felhőszolgáltatóhoz sem
    A CloudRetro infrastruktúrája különböző felhőszolgáltatóknál (Digital Ocean, Alibaba, egyedi szolgáltató) található a különböző régiók számára. A Docker konténerben való indítást aktiválom az infrastruktúrára, és bash-szkripttel állítom be a hálózati paramétereket, hogy elkerüljem az egyetlen felhőszolgáltatótól való függőséget. Ezt a NAT Traversal-nal a WebRTC-ben kombinálva rugalmasságot nyerünk a CloudRetro bármely felhőplatformra való telepítéséhez, sőt bármely felhasználó gépén is.

    Architekturális tervezés

    Munkás: (vagy a fent említett stream szerver) megszorozza a játékokat, elindítja a kódoló csővezetéket, és továbbítja a kódolt médiát a felhasználóknak. A worker példányok világszerte eloszlanak, és minden worker több felhasználói munkamenetet is kezelhet egyszerre.

    Koordinátor: feladata, hogy új felhasználót összepárosítson a legmegfelelőbb workerrel a streaminghez. A koordinátor WebSocketen keresztül kommunikál a workerekkel.

    Játékállapot-tároló: központi távoli tároló minden játékállapot számára. Ez a tároló fontos funkciókat biztosít, mint például a távoli mentés/letöltés.

    Felhasználásra kész, nyílt forráskódú felhős játékzés WebRTC-n: p2p, multiplayer, zero latency.
    CloudRetro felső szintű architektúrája

    Felhasználói forgatókönyv

    Amikor egy új felhasználó megnyitja a CloudRetro-t az alábbi 1. és 2. lépésben, a koordinátor a rendelkezésre álló workerek listájával a kezdőoldalon kérdést kap. Ezután a 3. lépésben az ügyfél HTTP ping kérés segítségével kiszámítja a késleltetéseket minden jelölt esetében. Ez a késleltetési lista ezután vissza kerül a koordinátorhoz, hogy meghatározhassa a legmegfelelőbb worker-t a felhasználó kiszolgálására. Az alábbi 4. lépésben létrejön a játék. A felhasználó és a kijelölt worker között WebRTC streaming kapcsolat áll fel.
    Felhasználásra kész, nyílt forráskódú felhős játékzés WebRTC-n: p2p, multiplayer, zero latency.
    Felhasználói forgatókönyv hozzáférés után

    Mi van a workerben

    A játék- és streamingcsővezetések elszigetelten tárolódnak a workerben, és ott információt cserélnek az interfészen keresztül. Jelenleg a kapcsolat az adatok memóriában való átadásával történik a Golang csatornákban ugyanabban a folyamatban. A következő cél a szegmentálás, azaz a játék független elindítása egy másik folyamatban.

    Felhasználásra kész, nyílt forráskódú felhős játékzés WebRTC-n: p2p, multiplayer, zero latency.
    A worker komponenseinek kölcsönhatása

    Fő alkotóelemek:

    • WebRTC: ügyfélkomponens, amely fogadja a felhasználói bemenetet és a kódolt médiát továbbítja a szerverről.
    • Játékemulátor: játék komponens. A Libretro könyvtárnak köszönhetően a rendszer képes játékot indítani ugyanabban a folyamatban, és belsőleg elfogni a média- és bemeneti folyamot.
    • A játékon belüli képkockák rögzítve lesznek és elküldve a kódolónak.
    • Kép/audio kódoló: kódoló csővezeték, amely a média képkockákat fogadja, kódolja őket a háttérben, és kódolt képeket/audio-t állít elő.

    Megvalósítás

    A CloudRetro a WebRTC-t választotta alaptechnológiájának, ezért mielőtt mélyebben belemerülnék a Golangban való megvalósítás részleteibe, szeretném bemutatni magát a WebRTC-t. Ez egy csodálatos technológia, amely jelentősen segített nekem abban, hogy csak egy tizedmásodperces késleltetést érjek el az adatok streamelésében.

    WebRTC

    A WebRTC célja a kiváló minőségű, peer-to-peer kapcsolatok biztosítása natív mobilalkalmazások és böngészők számára egyszerű API-kon keresztül.

    NAT Traversal

    A WebRTC ismert a NAT Traversal funkciójáról. A WebRTC célja a peer-to-peer kommunikáció. Célja, hogy megtalálja a legmegfelelőbb közvetlen útvonalat a NAT átjárók és tűzfalak elkerülésével a peer-to-peer kapcsolat megvalósítása érdekében, egy folyamat során, amelyet ICE. Ebben a folyamatban a WebRTC API-k a STUN szervereken keresztül megtalálják a nyilvános IP-címedet, és azt továbbítják egy relé szerverre (Ideálisan olyan hálózati API-t szeretnénk létrehozni, amely belsőleg WebRTC-t használ, de a lehető legjobban hasonlít az UDP Sockets interfészéhez, amely nem igényel kapcsolatot.), amikor a közvetlen kapcsolat nem hozható létre.

    A CloudRetro azonban nem használja ki teljes mértékben ezt a lehetőséget. A peer-to-peer kapcsolatai nem a felhasználók között, hanem a felhasználók és a felhős szerverek között léteznek. A szerver oldali modellnek kevesebb korlátozása van a közvetlen kapcsolatra, mint a hagyományos felhasználói eszközöknek. Ez lehetővé teszi a bejövő portok előnyitását vagy a nyilvános IP-címek közvetlen használatát, mivel a szerver nem NAT mögött található.

    Korábban azt akartam, hogy a projektet a Cloud Gaming játékainak terjesztő platformjává alakítsam. Az ötlet az volt, hogy a játékfejlesztők lehetőséget kapjanak a játékok és streamelt források biztosítására. A felhasználók pedig közvetlenül léptek kapcsolatba a szolgáltatókkal. Ebben a decentralizált formában a CloudRetro csupán közeget jelent a külső streamelt források felhasználókhoz való csatlakoztatásához, ezáltal a méretezhetősége is nő, amikor már nem terheli a hosting. A WebRTC NAT Traversal szerepe itt nagyon fontos a peer-to-peer kapcsolat inicializálásának megkönnyítésében külső streamelt forrásokon, ami egyszerűsíti a fejlesztő és a hálózat összekapcsolását.

    Videó tömörítés

    A videók tömörítése elengedhetetlen része a folyamatnak, ami nagymértékben hozzájárul a folyamat zökkenőmentességéhez. Bár nem szükséges ismerni a VP8/H264 videódolgozás részleteit, a koncepció megértése segít eligibilizálni a streaming videó sebességi paramétereit, és finomhangolni a váratlan viselkedést valamint a késleltetést.

    A videók tömörítése a streamelési szolgáltatások számára összetett feladat, mivel az algoritmusnak biztosítania kell, hogy a teljes kódolási idő + a hálózati átvitel ideje + a dekódolási idő a lehető legkisebb legyen. Ezenkívül a kódolási folyamatnak következetesnek és folytonosnak kell lennie. Egyes kölcsönös engedmények a kódolás során nem alkalmazhatók – például nem preferálhatjuk a hosszú kódolási időt a kisebb fájlméret és dekódolási idő érdekében, vagy használhatunk következetlen tömörítést.

    A videók tömörítésének lényege, hogy eltávolítsuk a szükségtelen információkat, miközben megőrizzük a felhasználók számára elfogadható pontossági szintet. A statikus képkeretek kódolása mellett az algoritmus a jelenlegi keretet a korábbi és következő keret alapján határozza meg, így csak a közöttük lévő különbség kerül továbbításra. Ahogy a Pacman példa is mutatja, csak a differenciális pontok kerülnek átküldésre.

    Felhasználásra kész, nyílt forráskódú felhős játékzés WebRTC-n: p2p, multiplayer, zero latency.
    Videók összehasonlítása a Pacman példáján

    Hangkodeálás

    Hasonlóképpen, a hang tömörítő algoritmus is elhagyja azokat az adatokat, amelyeket az ember nem érzékelhet. Jelenleg az Opus a legjobb teljesítményű audio kodek. Az audiováltozás RTP (Real Time Transport Protocol – valós idejű forgalomátviteli protokoll) protokollon keresztül való továbbítására lett kifejlesztve. Késése alacsonyabb, mint az mp3 és aac, míg a minősége magasabb. A késleltetés általában körülbelül 5~66,5 ms.

    Pion, WebRTC a Golangban

    Pion egy nyílt forráskódú projekt, amely a WebRTC-t áthúzza a Golangba. A klasszikus natív C++ WebRTC könyvtárak burkolása helyett a Pion egy natív Golang-alapú WebRTC megvalósítás, amely jobb teljesítményt, Go-integrációt, valamint verziókezelést biztosít a WebRTC protokollokhoz.

    A könyvtár emellett gyors adatfolyamot biztosít számos nagyszerű, beépített modullal, kevesebb, mint egy másodperces késéssel. Saját megvalósításai vannak STUN, DTLS, SCTP és hasonlók terén, és némi kísérletezés a QUIC és WebAssembly technológiával. Ez az open-source könyvtár valóban nagyszerű tanulási forrást jelent, remek dokumentációval, hálózati protokollok megvalósításaival és klassz példákkal.

    A Pion közösség, amelyet egy nagyon szenvedélyes alapító vezet, meglehetősen élénk; sok minőségi diskurzus zajlik a WebRTC-ről. Ha érdekli ez a technológia, csatlakozzon http://pion.ly/slack - sok új dolgot fog tanulni.

    CloudRetro írása Golang nyelven

    Felhasználásra kész, nyílt forráskódú felhős játékzés WebRTC-n: p2p, multiplayer, zero latency.
    Worker megvalósítása Go nyelven

    Go csatornák működés közben

    A Go csatornák szép tervezése miatt az eseményfolyam és a párhuzamosság problémái jelentősen leegyszerűsödnek. Ahogy a diagramon látható, több komponens párhuzamosan működik különböző GoRoutines-ban. Minden komponens a saját állapotát kezeli és csatornákon keresztül kommunikál. A Golang kiválasztási nyilatkozata lehetővé teszi, hogy minden egyes atomáris eseményt egy korszakonként (game tick) kezeljünk. Ez azt jelenti, hogy az ilyen tervezéshez nincs szükség zárolásra. Például, amikor a felhasználó ment, szükség van a játék állapotának teljes snapshotjára. Ennek az állapotnak folyamatosan meg kell maradnia, amíg a mentés be nem fejeződik. Minden game tick során a backend csak mentési vagy belépési műveletet kezelhet, így a folyamat szálbiztos lesz.

    func (e *gameEmulator) gameUpdate() {
    for {
    	select {
    		case <-e.saveOperation:
    			e.saveGameState()
    		case key := <-e.input:
    			e.updateGameState(key)
    		case <-e.done:
    			e.close()
    			return
    	}
        }
    }

    Fan-in / Fan-out

    Ez a Golang minta tökéletes a CrowdPlay és a Multiple Player használati esetemhez. E minta követésével minden felhasználói input egy szobában egy központi belépési csatornába integrálódik. A játék médiáit ezután minden felhasználónak a szobában kiadják. Így elérjük a játékállapot megosztását a különböző felhasználók játékmenetei között.

    Felhasználásra kész, nyílt forráskódú felhős játékzés WebRTC-n: p2p, multiplayer, zero latency.
    Szinkronizálás különböző ülések között

    A Golang hátrányai

    A Golang nem tökéletes. A csatorna lassú. A Go csatorna a blokkoláshoz képest egyszerűbb módot kínál a párhuzamos és eseményalapú gyakorlatok kezelésére, de a csatorna nem biztosít a legjobb teljesítményt. A csatorna alatt bonyolult blokkolási logika húzódik meg. Ezért néhány módosítást végeztem a megvalósításon, újra alkalmazva a blokkolásokat és atomikus értékeket a csatornák teljesítményének optimalizálása érdekében.

    Továbbá, a Golang garbage collector nem kézben tartott, ami miatt néha gyanúsan hosszú szünetek lépnek fel. Ez jelentősen megnehezíti a valós idejű streaming alkalmazások működését.

    CGO

    A projekt az open-source VP8/H264 Golang könyvtárat használja a média tömörítésére, valamint a Libretro-t a játék emulátorokhoz. Mindezek a könyvtárak egyszerűen a C könyvtár Go-ra való becsomagolásai. CGONéhány hátrányt felsorolnak a Dave Cheney ezen a poszton.A problémák, amelyekkel szembesültem:

    • a CGO-ban bekövetkező összeomlások elkapásának lehetetlensége, még a Golang RecoveryCrash segítségével is;
    • nincs lehetőség a teljesítmény szűk keresztmetszeteinek meghatározására, amikor nem tudunk részletes problémákat felfedezni a CGO-ban.

    Zárás

    Elértem a célomat – megértettem a felhőalapú játék szolgáltatásokat és létrehoztam egy platformot, amely segít régi nosztalgikus játékok online játszásában a barátaimmal. Ennek a projektnek a létrehozása Pion könyvtár és a Pion közösség támogatása nélkül lehetetlen lett volna. Rendkívül hálás vagyok a folyamatos fejlesztéséért. A WebRTC és a Pion által biztosított egyszerű API-k zökkenőmentes integrálást biztosítottak. Az első koncepcióm a megfelelő héten jelent meg, annak ellenére, hogy előre nem tudtam a peer-to-peer (P2P) kapcsolatról.

    A beépítés egyszerűsége ellenére a P2P streaming valóban nagyon összetett terület a számítástechnika világában. Olyan régi hálózati architektúrákkal kell foglalkoznia, mint az IP és a NAT, hogy peer-to-peer ülést hozzon létre. A projekt során rengeteg értékes tudást szereztem a hálózatokról és a teljesítmény optimalizálásáról, így mindenkinek ajánlom, hogy próbáljon P2P termékeket építeni WebRTC segítségével.

    A CloudRetro minden olyan felhasználási forgatókönyvet lefed, amit retro játékosként elvártam. Mindazonáltal úgy érzem, hogy a projektben még sok terület van, amit javíthatnék, például megbízhatóbb és teljesítményesebb hálózatot létrehozni, magasabb grafikai minőséget biztosítani, vagy lehetőséget adni a felhasználók közötti játékmegosztásra. Ezen dolgozom. Kérem, figyeljék a projekt és támogassák, ha tetszik önöknek.

Forrás: habr.com

Megbízható hosting vásárlása weboldalak számára, DDoS védelemmel, VPS VDS szerverek. 🔥 Megbízható webtárhely vásárlása DDoS védelemmel, VPS VDS szerverek | ProHoster