A kezdőknek szánt játékok hálózati modelljéről

A kezdőknek szánt játékok hálózati modelljéről
Az elmúlt két hétben a játékom hálózati motorján dolgoztam. Azelőtt semmit sem tudtam a játékokban a hálózatépítésről, ezért rengeteg cikket olvastam és sok kísérletet végeztem, hogy megértsem az összes fogalmat, és meg tudjam írni a saját hálózati motoromat.

Ebben az útmutatóban szeretném megosztani veled azokat a különféle fogalmakat, amelyeket meg kell tanulnod, mielőtt saját játékmotort írnál, valamint szeretném megosztani veled a tanuláshoz szükséges legjobb forrásokat és cikkeket.

Általánosságban elmondható, hogy a hálózati architektúráknak két fő típusa van: a peer-to-peer és a kliens-szerver. A peer-to-peer (p2p) architektúrában az adatok átvitele tetszőleges pár csatlakoztatott lejátszó között, míg a kliens-szerver architektúrában csak a lejátszók és a szerver között történik az adatátvitel.

Bár egyes játékokban még mindig a peer-to-peer architektúrát használják, a kliens-szerver a szabvány: könnyebben megvalósítható, kisebb csatornaszélességet igényel, és könnyebben védekezhet a csalás ellen. Ezért ebben az útmutatóban a kliens-szerver architektúrára fogunk összpontosítani.

Különösen a tekintélyelvű szerverek érdekelnek bennünket: az ilyen rendszerekben mindig a szervernek van igaza. Például, ha a játékos úgy gondolja, hogy a (10, 5) ponton van, és a szerver azt mondja neki, hogy (5, 3) van, akkor a kliensnek le kell cserélnie a pozícióját arra, amelyet a szerver jelent, nem pedig fordítva. A hiteles szerverek használata megkönnyíti a csalók felismerését.

A játékhálózati rendszereknek három fő összetevője van:

  • Szállítási protokoll: hogyan történik az adatok átvitele a kliensek és a szerver között.
  • Alkalmazási protokoll: mit és milyen formátumban továbbítanak a kliensek a szerverre és a szerverről a kliensekre.
  • Alkalmazási logika: hogyan használják fel a továbbított adatokat a kliensek és a szerver állapotának frissítésére.

Nagyon fontos megérteni az egyes részek szerepét és a hozzájuk kapcsolódó nehézségeket.

Szállítási protokoll

Az első lépés egy protokoll kiválasztása a szerver és a kliensek közötti adatátvitelhez. Ehhez két internetes protokoll létezik: TCP и UDP. De létrehozhat saját szállítási protokollt az egyik alapján, vagy használhat egy könyvtárat, amely ezeket használja.

A TCP és az UDP összehasonlítása

A TCP és az UDP is ezen alapul IP. Az IP lehetővé teszi egy csomag továbbítását a forrásból a vevő felé, de nem garantálja, hogy az elküldött csomag előbb-utóbb eljut a vevőhöz, hogy legalább egyszer eljut hozzá, és a csomagok sorozata megérkezik a helyes sorrend. Ezenkívül egy csomag csak korlátozott adatméretet tartalmazhat, amelyet az érték adja MTU.

Az UDP csak egy vékony réteg az IP-n. Ezért ugyanazok a korlátai vannak. Ezzel szemben a TCP számos funkcióval rendelkezik. Megbízható rendezett kapcsolatot biztosít két csomópont között hibaellenőrzéssel. Ezért a TCP nagyon kényelmes, és sok más protokollban is használják, például in HTTP, FTP и SMTP. De ezeknek a funkcióknak ára van: késleltetés.

Annak megértéséhez, hogy ezek a funkciók miért okozhatnak késést, meg kell értenünk a TCP működését. Amikor a küldő gazdagép egy csomagot továbbít a fogadó gazdagépnek, nyugtázást (ACK) vár. Ha egy bizonyos idő elteltével nem kapja meg (mert a csomag vagy a visszaigazolás elveszett, vagy más okból), akkor újra elküldi a csomagot. Sőt, a TCP garantálja, hogy a csomagok a megfelelő sorrendben érkeznek, tehát amíg egy elveszett csomag nem érkezik, az összes többi csomagot nem lehet feldolgozni, még akkor sem, ha azokat a fogadó csomópont már megkapta.

De amint valószínűleg megérti, a többjátékos játékokban a késleltetés nagyon fontos, különösen az olyan aktív műfajokban, mint az FPS. Ezért sok játék használja az UDP-t saját protokollal.

Az UDP-n alapuló natív protokoll több okból is hatékonyabb lehet, mint a TCP. Például egyes csomagokat megbízhatóként, másokat pedig nem megbízhatóként jelölhet meg. Ezért nem érdekli, hogy a megbízhatatlan csomag eljutott-e a címzetthez. Vagy több adatfolyamot is képes feldolgozni, így az egyik adatfolyamban elveszett csomag nem lassítja le a többi adatfolyamot. Például lehet egy szál a játékos beviteléhez és egy másik szál a csevegőüzenetekhez. Ha egy nem sürgős csevegési üzenet elveszik, akkor az nem lassítja le a sürgős bevitelt. Vagy egy szabadalmaztatott protokoll másképp valósítja meg a megbízhatóságot, mint a TCP, hogy hatékonyabb legyen a videojáték-környezetben.

Tehát, ha a TCP szar, akkor meg fogjuk építeni a saját UDP-n alapuló szállítási protokollunkat?

Minden egy kicsit bonyolultabb. Annak ellenére, hogy a TCP szinte nem optimális a játékhálózati rendszerek számára, meglehetősen jól működhet az adott játékban, és értékes időt takaríthat meg. Például előfordulhat, hogy a késleltetés nem jelent problémát egy körökre osztott játéknál vagy olyan játéknál, amelyet csak LAN-hálózaton lehet játszani, ahol a késleltetés és a csomagvesztés sokkal kisebb, mint az interneten.

Számos sikeres játék, köztük a World of Warcraft, a Minecraft és a Terraria, TCP-t használ. A legtöbb FPS azonban saját UDP-alapú protokollt használ, ezért az alábbiakban többet fogunk beszélni róluk.

Ha a TCP használatát választja, győződjön meg arról, hogy az le van tiltva Nagle algoritmusa, mert puffereli a csomagokat küldés előtt, ami azt jelenti, hogy növeli a késleltetést.

Ha többet szeretne megtudni az UDP és a TCP közötti különbségekről a többjátékos játékok kontextusában, olvassa el Glenn Fiedler cikkét UDP vs. TCP.

Saját protokoll

Tehát saját szállítási protokollt szeretne létrehozni, de nem tudja, hol kezdje? Szerencséje van, mert Glenn Fiedler két csodálatos cikket írt róla. Nagyon sok okos ötletet találsz bennük.

Első cikk Hálózatépítés játékprogramozóknak 2008, könnyebb, mint a második Játékhálózati protokoll készítése 2016. Azt javaslom, hogy kezdje a régebbivel.

Ne feledje, hogy Glenn Fiedler nagy támogatója az UDP-n alapuló saját protokoll használatának. És miután elolvasta a cikkeit, valószínűleg elfogadja azt a véleményét, hogy a TCP-nek komoly hátrányai vannak a videojátékokban, és saját protokollját szeretné megvalósítani.

De ha még nem ismeri a hálózatépítést, tegyen magának egy szívességet, és használjon TCP-t vagy könyvtárat. Saját szállítási protokolljának sikeres megvalósításához sokat kell tanulnia előtte.

Hálózati könyvtárak

Ha valami hatékonyabbra van szükséged, mint a TCP, de nem akarsz vesződni a saját protokoll megvalósításával és sok részletezéssel, használhatod a netes könyvtárat. Nagyon sok van belőlük:

Nem próbáltam ki mindegyiket, de az ENetet jobban szeretem, mert könnyen használható és megbízható. Ezen kívül világos dokumentációval és kezdőknek szóló oktatóanyaggal rendelkezik.

A szállítási jegyzőkönyv következtetései

Összefoglalva, két fő szállítási protokoll létezik: a TCP és az UDP. A TCP számos hasznos funkcióval rendelkezik: megbízhatóság, csomagsorrend megőrzése, hibaészlelés. Az UDP nem rendelkezik mindezzel, de a TCP természeténél fogva magas késleltetéssel rendelkezik, ami elfogadhatatlan egyes játékoknál. Vagyis az alacsony késleltetés érdekében létrehozhat saját UDP-alapú protokollt, vagy használhat olyan könyvtárat, amely az UDP-n való szállítási protokollt valósítja meg, és többjátékos videojátékokhoz van igazítva.

A TCP, UDP és a könyvtár közötti választás több tényezőtől függ. Először is, a játék igényeiből: kell-e alacsony késleltetés? Másodszor, az alkalmazási protokoll követelményeiből: kell-e megbízható protokoll? Ahogy a következő részben látni fogjuk, lehetséges olyan alkalmazási protokollt létrehozni, amelyre egy megbízhatatlan protokoll meglehetősen alkalmas. Végül figyelembe kell vennie a hálózati motor fejlesztőjének tapasztalatait is.

Két tippem van:

  • A lehető legnagyobb mértékben vonja ki a szállítási protokollt az alkalmazás többi részéből, hogy könnyen lecserélhető legyen az összes kód átírása nélkül.
  • Ne optimalizálja túl. Ha Ön nem hálózati szakértő, és nem biztos abban, hogy szüksége van-e saját UDP-alapú szállítási protokolljára, kezdje a TCP-vel vagy egy megbízhatóságot biztosító könyvtárral, majd tesztelje és mérje a teljesítményt. Ha problémái vannak, és biztos benne, hogy ez egy szállítási protokoll, akkor itt az ideje, hogy létrehozza saját szállítási protokollját.

A rész végén azt javaslom, hogy olvassa el Bevezetés a többjátékos játékprogramozásba Brian Hook, amely számos itt tárgyalt témát lefed.

Alkalmazási protokoll

Most, hogy adatokat cserélhetünk a kliensek és a szerver között, el kell döntenünk, hogy milyen adatokat és milyen formátumban továbbítunk.

A klasszikus séma szerint a kliensek bemenetet vagy műveleteket küldenek a szervernek, a szerver pedig az aktuális játékállapotot küldi el a klienseknek.

A szerver nem a teljes, hanem a szűrt állapotot küldi a lejátszó közelében lévő entitásokkal. Ezt három okból teszi. Először is, a teljes állapot túl nagy lehet ahhoz, hogy magas frekvencián továbbítson. Másodszor, a klienseket elsősorban a vizuális és audio adatok érdeklik, mivel a játék logikájának nagy részét a játékszerveren szimulálják. Harmadszor, bizonyos játékokban a játékosnak nem kell tudnia bizonyos adatokat, például az ellenség pozícióját a térkép másik oldalán, mert különben megszagolhatja a csomagokat, és pontosan tudja, hová kell mozognia, hogy megölje.

Sorozatosítás

Első lépésként a küldeni kívánt adatokat (bemeneti vagy játékállapot) átvitelre alkalmas formátumba kell konvertálni. Ezt a folyamatot ún sorozatosítás.

Azonnal eszembe jut az ötlet, hogy használjunk ember által olvasható formátumot, például JSON-t vagy XML-t. De ez teljesen hatástalan lesz, és a csatorna nagy részét a semmiért veszi el.

Ehelyett javasolt a bináris formátum használata, amely sokkal kompaktabb. Vagyis a csomagok csak néhány bájtot tartalmaznak. Itt figyelembe kell vennünk a problémát bájt sorrend, amelyek a különböző számítógépeken eltérőek lehetnek.

Az adatok sorba rendezéséhez használhat egy könyvtárat, például:

Csak győződjön meg arról, hogy a könyvtár hordozható archívumokat hoz létre, és gondoskodik a véglegességről.

Alternatív megoldás az lenne, ha saját kezűleg implementálnád, ez nem olyan nehéz, főleg ha adatközpontú megközelítést használsz a kódodban. Ezenkívül lehetővé teszi olyan optimalizálások végrehajtását, amelyek nem mindig lehetségesek a könyvtár használata során.

Glenn Fiedler két cikket írt a sorozatosításról: Csomagok olvasása és írása и Sorozatosítási stratégiák.

Tömörítés

A kliensek és a szerver között továbbított adatmennyiséget a csatorna sávszélessége korlátozza. Az adattömörítés lehetővé teszi több adat átvitelét minden pillanatfelvételen, növeli a frissítési gyakoriságot, vagy egyszerűen csökkenti a sávszélesség-igényt.

Bit csomagolás

Az első technika a bitpakolás. Ez abból áll, hogy pontosan annyi bitet használunk, amennyi a kívánt érték leírásához szükséges. Például, ha van egy enum, amely 16 különböző értéket tartalmazhat, akkor egy teljes bájt (8 bit) helyett csak 4 bitet használhat.

Glenn Fiedler a cikk második részében elmagyarázza, hogyan kell ezt megvalósítani. Csomagok olvasása és írása.

A bitcsomagolás különösen jól működik a diszkretizálással, ami a következő rész témája lesz.

Mintavétel

Mintavétel egy veszteséges tömörítési technika, amely a lehetséges értékeknek csak egy részét használja egy érték kódolásához. A diszkretizálás megvalósításának legegyszerűbb módja a lebegőpontos számok kerekítése.

Glenn Fiedler (ismét!) bemutatja cikkében, hogyan lehet a diszkretizálást a gyakorlatban alkalmazni Pillanatkép tömörítése.

Tömörítési algoritmusok

A következő technika a veszteségmentes tömörítési algoritmusok.

Véleményem szerint itt van a három legérdekesebb algoritmus, amelyet tudnia kell:

  • Huffman kódolás előre kiszámított kóddal, ami rendkívül gyors és jó eredményeket produkál. Csomagok tömörítésére használták a Quake3 hálózati motorban.
  • zlib egy általános célú tömörítési algoritmus, amely soha nem növeli az adatmennyiséget. Hogyan láthatod itt, sokféle alkalmazásban használták. Az állapotok frissítése esetén ez redundáns lehet. De jól jöhet, ha eszközöket, hosszú szövegeket vagy terepet kell küldenie a klienseknek a szerverről.
  • Futáshosszok másolása valószínűleg a legegyszerűbb tömörítési algoritmus, de bizonyos típusú adatok esetén nagyon hatékony, és a zlib előtti előfeldolgozási lépésként is használható. Különösen alkalmas olyan csempékből vagy voxelekből álló terep tömörítésére, amelyben sok szomszédos elem ismétlődik.

delta tömörítés

A végső tömörítési technika a delta tömörítés. Ez abban rejlik, hogy csak az aktuális játékállapot és a kliens által utoljára kapott állapot közötti különbségek kerülnek továbbításra.

Először a Quake3 hálózati motorban használták. Íme két cikk, amelyek elmagyarázzák, hogyan kell használni:

Glenn Fiedler is felhasználta cikkének második részében. Pillanatkép tömörítése.

Titkosítás

Ezenkívül előfordulhat, hogy titkosítania kell az ügyfelek és a szerver közötti információátvitelt. Ennek több oka is van:

  • Adatvédelem/Titkosság: Az üzeneteket csak a címzett tudja elolvasni, és semmilyen más hálózati szippantó nem fogja tudni elolvasni azokat.
  • hitelesítés: annak, aki a játékos szerepét akarja betölteni, ismernie kell a kulcsát.
  • csalás megelőzése: a rosszindulatú játékosoknak sokkal nehezebb lesz saját csalócsomagokat létrehozniuk, le kell replikálniuk a titkosítási sémát és meg kell találniuk a kulcsot (ami minden kapcsolatnál változik).

Erősen ajánlom ehhez a könyvtár használatát. Javaslom a használatát libsodium, mert különösen egyszerű, és remek oktatóanyagokkal rendelkezik. Különösen érdekes a bemutató kulcscsere, amely lehetővé teszi új kulcsok létrehozását minden új kapcsolatnál.

Alkalmazási jegyzőkönyv: Konklúzió

Ezzel az alkalmazási protokoll lezárul. Úgy gondolom, hogy a tömörítés teljesen opcionális, és a használatáról szóló döntés csak a játéktól és a szükséges sávszélességtől függ. A titkosítás véleményem szerint kötelező, de az első prototípusban meg lehet nélkülözni.

Alkalmazási logika

Mostantól frissíteni tudjuk az ügyfél állapotát, de késleltetési problémákba ütközhetünk. A játékosnak a bevitel után meg kell várnia a játékállapot-frissítést a szervertől, hogy lássa, milyen hatással volt a világra.

Ráadásul két állapotfrissítés között a világ teljesen statikus. Ha az állapotfrissítési sebesség alacsony, akkor a mozgások nagyon szaggatottak lesznek.

Számos módszer létezik a probléma hatásának enyhítésére, és ezeket a következő részben ismertetem.

Késleltetett simítási technikák

Az ebben a részben leírt összes technikát a sorozat részletesen tárgyalja. Gyors tempójú többjátékos Gabriel Gambetta. Nagyon ajánlom ennek a kiváló cikksorozatnak az elolvasását. Tartalmaz egy interaktív bemutatót is, amely megmutatja, hogyan működnek ezek a technikák a gyakorlatban.

Az első technika az, hogy közvetlenül alkalmazza a bemeneti eredményt anélkül, hogy megvárná a kiszolgáló válaszát. Ez az úgynevezett ügyféloldali előrejelzés. Amikor azonban az ügyfél frissítést kap a kiszolgálótól, ellenőriznie kell, hogy az előrejelzés helyes volt-e. Ha ez nem így van, akkor csak az állapotát kell megváltoztatnia aszerint, hogy mit kapott a szervertől, mert a szerver tekintélyelvű. Ezt a technikát először a Quake-ban használták. Erről bővebben a cikkben olvashat. Quake Engine kód felülvizsgálata Fabien Sanglars [fordítás Habréról].

A technikák második csoportja más entitások mozgásának simítására szolgál két állapotfrissítés között. A probléma megoldásának két módja van: interpoláció és extrapoláció. Interpoláció esetén az utolsó két állapotot veszi, és az egyikből a másikba való átmenetet mutatja. Hátránya, hogy a késedelem kis töredékét okozza, mert az ügyfél mindig látja, hogy mi történt a múltban. Az extrapoláció arról szól, hogy az ügyfél által utoljára kapott állapot alapján megjósolja, hol legyenek az entitások. Hátránya, hogy ha az entitás teljesen megváltoztatja a mozgás irányát, akkor nagy hiba lesz az előrejelzés és a valós pozíció között.

Az utolsó, legfejlettebb technika, amely csak FPS-ben használható, az késés kompenzáció. A késleltetés kompenzáció használatakor a szerver figyelembe veszi a kliens késleltetését, amikor a célponton tüzel. Például, ha egy játékos fejlövést hajtott végre a képernyőjén, de valójában a célpontja a késés miatt más helyen volt, akkor igazságtalan lenne megtagadni a játékostól az ölési jogot a késés miatt. Így a szerver visszatekeri az időt arra az időpontra, amikor a játékos tüzelt, hogy szimulálja, amit a játékos látott a képernyőjén, és ellenőrizze, nincs-e ütközés a lövése és a célpont között.

Glenn Fiedler (mint mindig!) 2004-ben írt egy cikket Hálózati fizika (2004), amelyben lefektette a fizikai szimulációk szinkronizálását a szerver és a kliens között. 2014-ben új cikksorozatot írt hálózati fizika, amelyben más technikákat írt le a fizikai szimulációk szinkronizálására.

A Valve wikijén két cikk is található, Forrás Multiplayer Networking и Késéskompenzációs módszerek a kliens/szerver játékon belüli protokolltervezésben és -optimalizálásban késedelmi kártérítéssel foglalkozik.

Csalás megelőzése

A csalás megelőzésére két fő módszer létezik.

Először is, megnehezíti a csalók számára a rosszindulatú csomagok küldését. Mint fentebb említettük, ennek megvalósításának jó módja a titkosítás.

Másodszor, a mérvadó kiszolgáló csak parancsokat/bevitelt/műveleteket kaphat. A kliens csak bemenet elküldésével tudja megváltoztatni a szerver állapotát. Ezután a szervernek minden alkalommal, amikor bemenetet kap, ellenőriznie kell annak érvényességét, mielőtt alkalmazná.

Alkalmazási logika: Következtetés

Azt javaslom, hogy valósítson meg egy módszert a magas késleltetés és az alacsony frissítési gyakoriság szimulálására, hogy tesztelje a játék viselkedését rossz körülmények között, még akkor is, ha a kliens és a szerver ugyanazon a gépen fut. Ez nagymértékben leegyszerűsíti a késleltetési simítási technikák megvalósítását.

Egyéb hasznos források

Ha más hálózati modell-forrásokat szeretne felfedezni, itt találja meg őket:

Forrás: will.com

Hozzászólás