
Az elmúlt két héten a játékomba szánt hálózati motoron dolgoztam. Előtte egyáltalán nem tudtam semmit a játékok hálózati technológiáiról, így sok cikket olvastam és számos kísérletet végeztem, hogy megérthessem az összes koncepciót, és képes legyek megírni a saját hálózati motoromat.
Ebben az útmutatóban szeretném megosztani veletek a különböző koncepciókat, amelyeket tudomásul kell vennetek, mielőtt saját játékmotorot írnátok, valamint a legjobb forrásokat és cikkeket e koncepciók tanulmányozásához.
Összességében két alapvető típusú hálózati architektúra létezik: peer-to-peer és kliens-szerver. A peer-to-peer (p2p) architektúrában az adatok bármely csatlakoztatott játékos pár között cserélődnek, míg a kliens-szerver architektúrában az adatok csak a játékosok és a szerver között terjednek.
Bár a peer-to-peer architektúra napjainkban is használatban van néhány játékban, a kliens-szerver architektúra az alapértelmezett: könnyebb megvalósítani, kisebb sávszélességet igényel, és megkönnyíti a csalás elleni védelmet. Ezért ebben az útmutatóban a kliens-szerver architektúrára összpontosítunk.
Különösen az autoritatív szerverek érdekelnek minket: ilyen rendszerekben a szerver mindig igaz. Például, ha egy játékos azt hiszi, hogy a (10, 5) koordinátákon van, de a szerver azt mondja neki, hogy a (5, 3) pontban van, akkor a kliensnek a szerver által megadott pozícióra kell váltania, nem pedig fordítva. Az autoritatív szerverek használata megkönnyíti a csalók észlelését.
A játékok hálózati rendszereiben három alapvető összetevő található:
- Szállítási protokoll: hogyan kerülnek az adatok a kliensektől a szerverhez és viszont.
- Alkalmazási protokoll: mi kerül át a kliensektől a szerverhez és a szervertől a kliensekhez, és milyen formátumban.
- Alkalmazási logika: hogyan használják fel az átvitt adatokat a kliensek és a szerver állapotának frissítésére.
Nagyon fontos megérteni minden egyes rész szerepét és a hozzájuk kapcsolódó nehézségeket.
Szállítási protokoll
Az első lépés a megfelelő protokoll kiválasztása az adatok szerver és kliensek közötti szállítására. Két internetes protokoll létezik erre: és . De létrehozhatsz egy saját szállítási protokollt az egyik alapján, vagy alkalmazhatsz egy olyan könyvtárat, amelyik ezeket használja.
TCP és UDP összehasonlítása
Mind a TCP, mind az UDP alapját képezi . Az IP lehetővé teszi a csomag átadását a forrástól a címzettig, de nem garantálja, hogy az elküldött csomag előbb-utóbb eljut a címzetthez, hogy legalább egyszer elérje őt, és hogy a csomagok sorrendje helyesen érkezik meg. Sőt, a csomagok csak korlátozott méretű adatokat tartalmazhatnak, amit a méret határoz meg. .
Az UDP csupán egy vékony réteg az IP felett. Ezért ugyanazokkal a korlátokkal rendelkezik. Ezzel ellentétben a TCP sok jellemzővel bír. Megbízható, rendezett kapcsolatot biztosít két csomópont között hibakereséssel. Ennek következtében a TCP nagyon kényelmes, és számos más protokollban használják, például a , és . De ezek a funkciók áruként jelentkeznek: .
Ahhoz, hogy megértsük, miért okozhatják ezek a funkciók a késlekedést, meg kell vizsgálnunk, hogyan működik a TCP. Amikor a küldő csomópont csomagot küld a fogadó csomópontnak, várja a megerősítést (ACK). Ha egy bizonyos idő elteltével ezt nem kapja meg (mert a csomag vagy a megerősítés elveszett, vagy más okok miatt), akkor újból elküldi a csomagot. Továbbá, a TCP garantálja, hogy a csomagok a helyes sorrendben érkeznek meg, ezért amíg az elveszett csomagot nem kapják meg, az összes többi csomag nem dolgozható fel, még akkor sem, ha azokat már fogadta a fogadó csomópont.
De ahogy valószínűleg megérti, a késlekedés a multiplayer játékokban nagyon fontos, különösen olyan aktív műfajokban, mint a FPS. Ezért sok játék használ UDP-t saját protokollal.
A UDP-alapú saját protokoll különböző okokból hatékonyabb lehet a TCP-nél. Például megjelölheti egyes csomagokat megbízhatóként, míg másokat megbízhatatlanoként. Ezért nem érdekli, hogy a megbízhatatlan csomag eljutott-e a címzetthez. Vagy több adatfolyamot is kezelhet, hogy az egy adatfolyamban elveszett csomag ne lassítsa a többi folyamot. Például létezhet egy áram a játékos bemenetére és egy másik áram a chat üzenetekhez. Ha egy chat üzenet, amely nem sürgős adat, elveszik, akkor nem lassítja a bemenet aktiválását, amely sürgős. Vagy a saját protokoll más módon valósíthatja meg a megbízhatóságot, mint a TCP, hogy hatékonyabb legyen a videojátékok körülményei között.
Tehát, ha a TCP ennyire gyenge, akkor saját szállítási protokollt fogunk készíteni az UDP alapjain?
Minden kicsit bonyolultabb. Még ha a TCP szinte aluloptimalizált is a játékok hálózati rendszereihez, akkor még mindig jól működhet az ön játékában, és megtakaríthatja értékes időt. Például a késleltetés nem is lehet probléma a lépésről lépésre haladó játékokban vagy olyan játékokban, amelyeket csak LAN hálózatokban lehet játszani, ahol a késleltetés és a csomagvesztés sokkal kisebb, mint az interneten.
Sok sikeres játék, köztük a World of Warcraft, Minecraft és Terraria is használja a TCP-t. Azonban a legtöbb FPS saját UDP-alapú protokollokat alkalmaz, ezért alább részletesebben is beszélünk róluk.
Ha úgy döntesz, hogy a TCP-t használod, győződj meg róla, hogy a ki van kapcsolva, mert ez bájtnyi csomagokat pufferel az elküldés előtt, így növeli a késleltetést.
A UDP és TCP közötti eltérések részletesebb megismeréséhez elolvashatod Glenn Fidler cikkét a .
Saját protokoll
Tehát, szeretnél saját szállítási protokollt létrehozni, de nem tudod, hol kezdj? Szerencséd van, ugyanis Glenn Fidler írt erről két fantasztikus cikket. Ezekben rengeteg okos gondolatot találsz.
Az első cikk, 2008-as, egyszerűbb, mint a második, 2016-os. Ajánlom, hogy a régebbivel kezdj.
Tartsd észben, hogy Glenn Fidler nagy támogatója a saját UDP-alapú protokoll használatának. És miután elolvastad a cikkét, valószínűleg át fogod venni a véleményét arról, hogy a TCP-nak komoly hátrányai vannak a videojátékokban, és szeretnél saját protokollt megvalósítani.
De ha újdonsült vagy a hálózati munkában, akkor tedd magadnak a szívességet, és használj TCP-t vagy egy könyvtárat. A saját szállítási protokoll sikeres megvalósításához először sok mindent meg kell tanulnod.
Hálózati könyvtárak
Ha valami hatékonyabbat keresel a TCP-nél, de nem akarsz belemenni a saját protokollod megvalósításába és a sok részletbe, akkor használhatsz egy hálózati könyvtárat. Nagyon sok van belőlük:
- Glenn Fidler-től
- , ami már nincs tovább támogatva, de a forkja, még mindig aktívnak tűnik.
- — ez a könyvtár a többjátékos FPS-hez készült.
- a Valve cégtől
Nem próbáltam mindet, de az ENet-et részesítem előnyben, mert könnyen használható és megbízható. Ráadásul világos dokumentációval és kezdőknek szóló útmutatóval rendelkezik.
Szállítási protokoll: összegzés
Összegzésképpen: két alapvető szállítási protokoll létezik: TCP és UDP. A TCP számos hasznos tulajdonsággal bír: megbízhatóság, csomagok sorrendjének megőrzése, hibafelismerés. Az UDP nem rendelkezik ezekkel, viszont a TCP természeténél fogva megnövekedett késleltetéseket okoz, amelyek egyes játékok számára elfogadhatatlanok. Tehát a késleltetések minimalizálásához létrehozhatunk egy saját protokollt UDP alapokon, vagy használhatunk egy könyvtárat, amely megvalósítja a szállítási protokollt UDP-n és a többjátékos videójátékokhoz van adaptálva.
A választás a TCP, UDP és a könyvtár között több tényezőtől függ. Először is, a játék igényeitől: szüksége van-e alacsony késleltetésre? Másodszor, az alkalmazás protokolljának követelményeitől: szüksége van-e megbízható protokollra? Ahogy a következő részben látni fogjuk, létrehozhatunk egy alkalmazás protokollt, amelyhez egy megbízhatatlan protokoll is megfelelő. Végül figyelembe kell venni a hálózati motor fejlesztőjének tapasztalatát is.
Két tanácsom van:
- A szállítási protokollt maximálisan absztrahálja az alkalmazás többi részétől, hogy könnyen lecserélhető legyen anélkül, hogy az egész kódot át kellene írni.
- Ne végezzen korai optimalizálást. Ha nem vagy hálózati szakember és nem biztos abban, hogy szüksége van-e egy saját UDP alapú szállítási protokollra, akkor kezdd a TCP-val, vagy használj olyan könyvtárakat, amelyek megbízhatóságot biztosítanak, majd teszteld és mérd a teljesítményt. Ha problémák merülnek fel, és biztos vagy benne, hogy a szállítási protokoll az oka, lehet, hogy eljött az ideje a saját szállítási protokoll létrehozásának.
Ez a rész végén javaslom, hogy olvasd el Brian Hook könyvét, amely számos itt tárgyalt témát feldolgoz.
Alkalmazás protokoll
Most, hogy képesek vagyunk adatokat cserélni a kliensek és a szerver között, el kell dönteni, hogy pontosan milyen adatokat küldjünk és milyen formátumban.
A klasszikus séma az, hogy a kliensek a szervernek küldik a bemenetüket vagy cselekvéseiket, míg a szerver a klienseknek küldi a jelenlegi játékállapotot.
A szerver nem teljes, hanem szűrt állapotot küld azokról az entitásokról, amelyek a játékos közelében vannak. Ezt három okból teszi. Először is, a teljes állapot túl nagy lehet a gyakori továbbításhoz. Másodszor, az ügyfelek elsősorban a vizuális és auditív adatokat érdeklik, mivel a játék logikájának nagy része a játék szerverén kerül szimulálásra. Harmadszor, bizonyos játékokban a játékosok nem tudhatnak bizonyos adatokat, például az ellenség helyét a térkép másik végén, mert különben kiszimatolhatják a csomagokat, és pontosan tudják, merre kell menniük, hogy megöljék őket.
Szerializáció
Az első lépés az adatok átalakítása, amelyeket küldeni szeretnénk (bemenet vagy játékmenet állapota) egy olyan formátumba, amely alkalmas a továbbításra. Ezt a folyamatot nevezik .
Először is, eszünkbe jut a gépileg olvasható formátumok, mint például a JSON vagy az XML. De ez teljesen hatástalan lenne, és feleslegesen elfoglalná a sávszélesség jelentős részét.
Ehelyett ajánlott bináris formátumot használni, ami sokkal tömörebb. Tehát a csomagok csak néhány bájtot fognak tartalmazni. Itt figyelembe kell venni a , amely különböző számítógépeken eltérő lehet.
Az adatok szerializálásához használhatunk könyvtárat, mint például:
- a Google-tól
- a Sandstorm cégtől
- Shane Grant és Randolph Vorhis írásában
Csak győződjön meg róla, hogy a könyvtár hordozható archívumokat készít, és figyel a bájtrendelésre.
Alternatív megoldás lehet egy saját megvalósítás készítése, ami nem különösebben bonyolult, különösen, ha a kódban adatvezérelt megközelítést alkalmaz. Ezen kívül lehetőséget ad az olyan optimalizálások végrehajtására, amelyek nem mindig lehetségesek könyvtár használatakor.
Glenn Fidler két cikket írt a szerializációról: és .
Tömörítés
Az ügyfelek és a szerver közötti adatátvitel mértéke a csatorna sávszélességéhez van kötve. Az adatok tömörítése lehetővé teszi, hogy minden snappshotban több adatot továbbítsunk, növeljük a frissítési gyakoriságot, vagy egyszerűen csökkentsük a csatornára vonatkozó követelményeket.
Bitcsomagolás
Az első technika a bitcsomagolás. Ez annyit jelent, hogy pontosan annyi bitet használunk, amennyi szükséges a kívánt mennyiség leírásához. Például, ha egy felsorolásunk van, amely 16 különböző értéket vehet fel, akkor egy egész byte (8 bit) helyett csak 4 bitet használhatunk.
Glenn Fiddler elmagyarázza, hogyan kell ezt megvalósítani, a cikk második részében. .
A bitcsomagolás különösen jól működik a diszkretizálással, amely a következő rész témája lesz.
Diszkretizálás
— ez egy veszteséges tömörítési technika, amely a mennyiség kódolásához csak a lehetséges értékek egy részhalmazát használja. A diszkretizálás legegyszerűbben a lebegőpontos számok kerekítésével valósítható meg.
Glenn Fiddler (ismét!) bemutatja, hogyan alkalmazzuk a diszkretizálást a gyakorlatban, a cikkében. .
Tömörítő algoritmusok
A következő technika a veszteségmentes tömörítő algoritmusok lesz.
Szerintem itt van három legérdekesebb algoritmus, amelyet érdemes ismerni:
- előre számított kóddal, amely hihetetlenül gyors és jó eredményeket érhet el. Ezt a Quake3 hálózati motorjának csomagjaiban használták tömörítésre.
- — egy általános célú tömörítési algoritmus, amely soha nem növeli meg az adatok méretét. Ahogy látható, sok alkalmazási területen használták. Az állapotok frissítéséhez túlzó lehet, de hasznos lehet, ha a szerverről ügyfeleknek eszközöket, hosszú szövegeket vagy domborzatot kell küldeni.
- — ez valószínűleg a legegyszerűbb tömörítési algoritmus, de nagyon hatékony lehet bizonyos típusú adatok esetén, és használható a zlib előfeldolgozási lépéseként. Kifejezetten alkalmas a csempékből vagy voxelekből álló domborzat tömörítésére, ahol sok szomszédos elem ismétlődik.
Delta tömörítés
Az utolsó tömörítési módszer a delta tömörítés. Azon alapul, hogy csak a különbségeket továbbítják a jelenlegi játékállapot és az utolsó, az ügyféltől kapott állapot között.
Első alkalommal a Quake3 hálózati motorjában alkalmazták. Itt van két cikk, amelyek elmagyarázzák a használatának módját:
- Brian Hook
- Fabien Sanglard [ cikk a Habr-on, lásd a 'Hálózati modell' szakaszt]
Glenn Fiddler ezt a cikkének második részében is felhasználta. .
Titkosítás
Ezen kívül szükség lehet az információk titkosítására a kliensek és a szerver között. Ennek több oka is van:
- privát szféra / bizalmasság: az üzeneteket csak a címzett olvashatja, és senki más, aki hálózati lehallgatást végez, nem tudja azokat elolvasni.
- hitelesítés: annak, aki a játékos szerepét szeretné betölteni, ismernie kell a kulcsát.
- csalások megelőzése: a rosszindulatú játékosok számára sokkal nehezebb lesz saját csomagokat létrehozni a csalásokhoz, mivel reprodukálniuk kell a titkosítási sémát és meg kell találniuk a kulcsot (ami minden egyes kapcsolatkor megváltozik).
Határozottan javaslom, hogy használj erre egy könyvtárat. Javaslom az , mert különösen egyszerű, és nagyszerű oktatóanyagokkal rendelkezik. Különösen érdekes az oktatóanyag a , amely lehetővé teszi új kulcsok generálását minden új kapcsolatnál.
Alkalmazás protokoll: lezárás
Ezen a ponton befejezhetjük az alkalmazás protokollal. Úgy vélem, hogy a tömörítés teljesen nem kötelező, és az annak használatáról hozott 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 elhagyható.
Alkalmazási logika
Most már képesek vagyunk frissíteni az állapotot az ügyfélnél, de problémákba ütközhetünk a késleltetések miatt. A játékos, miután bevitte az adatokat, várnia kell a játék állapotának frissítésére a szervertől, hogy lássa, milyen hatással volt a világra.
Továbbá, két állapotfrissítés között a világ teljesen statikus. Ha az állapotfrissítések gyakorisága alacsony, a mozgások nagyon zaklatottá válnak.
Számos technika létezik, amelyek csökkentik ennek a problémának a hatását, amelyeket a következő részben ismertetni fogok.
Késleltetés simító technikák
Az ebben a részben leírt technikák részletesen át vannak tekintve a Gabriel Gambetta sorozatában. Határozottan ajánlom ennek a nagyszerű sorozatnak a elolvasását. Az oktatóanyagban interaktív bemutató is található, amely lehetővé teszi, hogy lássuk, hogyan működnek ezek a technikák a gyakorlatban.
Az első technika az, hogy közvetlenül alkalmazzuk a bevitt eredményeket, nem várva a szerver válaszára. Ezt klienési előrejelzésnek. Azonban amikor az ügyfél frissítést kap a szervertől, biztosnak kell lennie abban, hogy a jóslata helyes volt. Ha ez nem így van, egyszerűen módosítania kell az állapotát a szervertől kapott információk szerint, mert a szerver autoritás. Ezt a technikát először a Quake-ben használták. További részleteket egy cikkben olvashat. Fabien Sanglard [ a Habr-n.
A második technikákészlet az egyes entitások mozgásának simítására szolgál két állapotfrissítés között. Két módszer létezik a feladat megoldására: interpoláció és extrapoláció. Az interpoláció esetében az utolsó két állapotot használják, és bemutatják az átmenetet az egyikből a másikba. Ennek hátránya, hogy kis késleltetést okoz, mivel az ügyfél mindig a múlt eseményeit látja. Az extrapoláció az entitásoknak a legutolsó állapot alapján történő előrejelzését jelenti, amelyet az ügyfél kapott. Ennek hátránya, hogy ha egy entitás teljesen megváltoztatja a mozgás irányát, akkor nagy eltérés keletkezik a jóslat és a valós helyzet között.
Az utolsó, legfejlettebb technika, amely csak az FPS játékokban hasznos — ez a késleltetés kompenzálása. Késleltetés kompenzálása esetén a szerver figyelembe veszi az ügyfél késedelmeit, amikor célra lő. Például, ha a játékos fejre lövése a képernyőjén látható, de valójában a célpontja késedelem miatt máshol volt, akkor nem lenne fair megtagadni a játékost a gyilkossági jogtól a késlekedés miatt. Ezért a szerver visszatekeri az időt arra a pontra, amikor a játékos lőtt, hogy szimulálja, mit látott a játékos a képernyőjén, és ellenőrzi a találkozást a lövése és a célpont között.
Glenn Fiedler (mint mindig!) 2004-ben írt egy cikket , amely megalapozta a fizikai szimulációk szinkronizációját a szerver és az ügyfél között. 2014-ben új cikkeket írt , amelyekben további technikákat írt a fizikai szimuláció szinkronizálásáról.
A Valve cég wiki-jén található két cikk is, és , amelyek a késedelmek kompenzálásával foglalkoznak.
Csalások megelőzése
Két fő módszer létezik a csalások megelőzésére.
Első: a rosszindulatú csomagok kibocsátásának nehezítése csalóktól. Ahogy fentebb említettük, a megvalósítás jó módja a titkosítás.
Második: az autoriter szerver csak parancsokat/bemeneteket/műveleteket fogadhat. Az ügyfélnek nem szabad lehetősége legyen a szerver állapotának megváltoztatására, csak a bemenetek küldésével. Így a szervernek minden alkalommal a bemenet fogadása után ellenőriznie kell annak érvényességét a végrehajtás előtt.
Alkalmazás logikája: összefoglalás
Javaslom, hogy valósíts meg egy módszert, amely nagy késleltetéseket és alacsony frissítési gyakoriságokat szimulál, hogy tesztelhesd a játékod viselkedését rossz körülmények között, még akkor is, ha az ügyfél és a szerver egy számítógépen fut. Ez jelentősen leegyszerűsíti a késleltetés simítási technikáinak megvalósítását.
Other useful resources
Ha más, a hálózati modellekre vonatkozó forrásokat szeretnél tanulmányozni, itt találhatók:
- — érdemes elolvasni az egész blogját, rengeteg nagyszerű cikk található benne. összegyűjtve a hálózati technológiákkal kapcsolatos összes cikk.
- M. Fatih MAR szerzőtől — ez egy részletes lista a videójátékok hálózati motorjairól szóló cikkekről és videókról.
- A sok hasznos linket is tartalmaz.
Forrás: habr.com
