Sobre el model de xarxa en jocs per a principiants

Sobre el model de xarxa en jocs per a principiants
Durant les últimes dues setmanes he estat treballant en el motor de xarxa per al meu joc. Abans d'això, no sabia res de les xarxes als jocs, així que vaig llegir molts articles i vaig fer molts experiments per entendre tots els conceptes i poder escriure el meu propi motor de xarxes.

En aquesta guia, m'agradaria compartir amb vosaltres els diferents conceptes que heu d'aprendre abans d'escriure el vostre propi motor de joc, així com els millors recursos i articles per aprendre-los.

En general, hi ha dos tipus principals d'arquitectures de xarxa: peer-to-peer i client-servidor. En una arquitectura peer-to-peer (p2p), les dades es transfereixen entre qualsevol parell de jugadors connectats, mentre que en una arquitectura client-servidor, les dades només es transfereixen entre els jugadors i el servidor.

Tot i que l'arquitectura peer-to-peer encara s'utilitza en alguns jocs, client-servidor és l'estàndard: és més fàcil d'implementar, requereix una amplada de canal més petita i fa que sigui més fàcil de protegir contra les trampes. Per tant, en aquesta guia, ens centrarem en l'arquitectura client-servidor.

En particular, ens interessen més els servidors autoritaris: en aquests sistemes, el servidor sempre té la raó. Per exemple, si el jugador creu que està a (10, 5) i el servidor li diu que està a (5, 3), aleshores el client hauria de substituir la seva posició per la que el servidor està informant, no al revés. L'ús de servidors autoritzats facilita el reconeixement dels tramposos.

Hi ha tres components principals en els sistemes de xarxa de jocs:

  • Protocol de transport: com es transfereixen les dades entre els clients i el servidor.
  • Protocol d'aplicació: què es transmet dels clients al servidor i del servidor als clients, i en quin format.
  • Lògica d'aplicació: com s'utilitzen les dades transmeses per actualitzar l'estat dels clients i del servidor.

És molt important entendre el paper de cada part i les dificultats associades a ella.

Protocol de transport

El primer pas és triar un protocol per transportar dades entre el servidor i els clients. Hi ha dos protocols d'Internet per a això: TCP и UDP. Però podeu crear el vostre propi protocol de transport basat en un d'ells o utilitzar una biblioteca que els utilitzi.

Comparació de TCP i UDP

Tant TCP com UDP es basen en IP. La IP permet transmetre un paquet des d'una font a un receptor, però no garanteix que el paquet enviat arribarà tard o d'hora al receptor, que hi arribarà almenys una vegada i que la seqüència de paquets arribarà a l'ordre correcte. A més, un paquet només pot contenir una mida de dades limitada, donada pel valor MTU.

UDP és només una capa fina a la part superior de la IP. Per tant, té les mateixes limitacions. En canvi, TCP té moltes característiques. Proporciona una connexió ordenada fiable entre dos nodes amb comprovació d'errors. Per tant, TCP és molt convenient i s'utilitza en molts altres protocols, per exemple, en HTTP, FTP и SMTP. Però totes aquestes característiques tenen un preu: retard.

Per entendre per què aquestes funcions poden causar latència, hem d'entendre com funciona TCP. Quan l'amfitrió emissor transmet un paquet a l'amfitrió receptor, espera rebre un reconeixement (ACK). Si al cap d'un temps determinat no el rep (perquè s'ha perdut el paquet o la confirmació, o per algun altre motiu), llavors torna a enviar el paquet. A més, TCP garanteix que els paquets es reben en l'ordre correcte, de manera que fins que no es rebi un paquet perdut, tots els altres paquets no es poden processar, encara que ja hagin estat rebuts pel node receptor.

Però com probablement entengueu, la latència en els jocs multijugador és molt important, especialment en gèneres tan actius com els FPS. És per això que molts jocs utilitzen UDP amb el seu propi protocol.

Un protocol natiu basat en UDP pot ser més eficient que TCP per diverses raons. Per exemple, pot marcar alguns paquets com a de confiança i altres com a no fiables. Per tant, no li importa si el paquet poc fiable ha arribat al destinatari. O pot processar múltiples fluxos de dades de manera que un paquet perdut en un flux no retardi altres fluxos. Per exemple, pot haver-hi un fil per a l'entrada del jugador i un altre fil per als missatges de xat. Si es perd un missatge de xat que no és dades urgents, no alentirà l'entrada que és urgent. O un protocol propietari podria implementar la fiabilitat de manera diferent que TCP per ser més eficient en un entorn de videojocs.

Aleshores, si TCP fa mal, construirem el nostre propi protocol de transport basat en UDP?

Tot és una mica més complicat. Tot i que el TCP és gairebé subòptim per als sistemes de xarxa de jocs, pot funcionar força bé per al vostre joc específic i estalviar-vos un temps valuós. Per exemple, la latència pot no ser un problema per a un joc per torns o un joc que només es pot jugar a xarxes LAN, on la latència i la pèrdua de paquets són molt menors que a Internet.

Molts jocs d'èxit, inclosos World of Warcraft, Minecraft i Terraria, utilitzen TCP. Tanmateix, la majoria dels FPS utilitzen els seus propis protocols basats en UDP, així que en parlarem més a continuació.

Si trieu utilitzar TCP, assegureu-vos que estigui desactivat algorisme de Nagle, perquè emmagatzema els paquets abans d'enviar-los, la qual cosa significa que augmenta el retard.

Per obtenir més informació sobre les diferències entre UDP i TCP en el context dels jocs multijugador, consulteu l'article de Glenn Fiedler UDP vs. TCP.

Protocol propietari

Voleu crear el vostre propi protocol de transport però no saps per on començar? Estàs d'enhorabona, perquè Glenn Fiedler va escriure dos articles sorprenents sobre això. Hi trobareu moltes idees intel·ligents.

Primer article Xarxes per a programadors de jocs 2008, més fàcil que el segon Construcció d'un protocol de xarxa de jocs 2016. Us recomano que comenceu amb el més gran.

Tingueu en compte que Glenn Fiedler és un gran defensor de l'ús del vostre propi protocol basat en UDP. I després de llegir els seus articles, probablement adoptareu la seva opinió que TCP té greus inconvenients en els videojocs i voldreu implementar el vostre propi protocol.

Però si sou nou a les xarxes, feu-vos un favor i utilitzeu TCP o una biblioteca. Per implementar amb èxit el vostre propi protocol de transport, heu d'aprendre molt abans.

Biblioteques en xarxa

Si necessiteu alguna cosa més eficient que TCP, però no voleu molestar-vos a implementar el vostre propi protocol i entrar en molts detalls, podeu utilitzar la biblioteca de xarxa. N'hi ha molts:

No els he provat tots, però prefereixo ENet perquè és fàcil d'utilitzar i fiable. A més, té una documentació clara i un tutorial per a principiants.

Conclusió del protocol de transport

En resum, hi ha dos protocols de transport principals: TCP i UDP. TCP té moltes característiques útils: fiabilitat, preservació de l'ordre dels paquets, detecció d'errors. UDP no té tot això, però TCP, per la seva pròpia naturalesa, té una latència elevada que és inacceptable per a alguns jocs. És a dir, per garantir una baixa latència, pots crear el teu propi protocol basat en UDP o utilitzar una biblioteca que implementi el protocol de transport en UDP i que estigui adaptada per a videojocs multijugador.

L'elecció entre TCP, UDP i la biblioteca depèn de diversos factors. En primer lloc, a partir de les necessitats del joc: necessita una latència baixa? En segon lloc, des dels requisits del protocol d'aplicació: necessita un protocol fiable? Com veurem a la part següent, és possible crear un protocol d'aplicació per al qual un protocol poc fiable és prou adequat. Finalment, també heu de tenir en compte l'experiència del desenvolupador del motor de xarxa.

Tinc dos consells:

  • Abstraure el protocol de transport tant com sigui possible de la resta de l'aplicació perquè es pugui substituir fàcilment sense reescriure tot el codi.
  • No optimitzis en excés. Si no sou un expert en xarxes i no esteu segur de si necessiteu el vostre propi protocol de transport basat en UDP, podeu començar amb TCP o una biblioteca que proporcioni fiabilitat i després provar i mesurar el rendiment. Si teniu problemes i esteu segur que es tracta d'un protocol de transport, pot ser que sigui el moment de crear el vostre propi protocol de transport.

Al final d'aquesta part, us recomano que llegiu Introducció a la programació de jocs multijugador Brian Hook, que cobreix molts dels temes tractats aquí.

Protocol d'aplicació

Ara que podem intercanviar dades entre els clients i el servidor, hem de decidir quines dades transferir i en quin format.

L'esquema clàssic és que els clients envien entrades o accions al servidor, i el servidor envia l'estat actual del joc als clients.

El servidor no envia l'estat complet, sinó l'estat filtrat amb les entitats que estan a prop del reproductor. Ho fa per tres motius. En primer lloc, l'estat total pot ser massa gran per transmetre a una freqüència alta. En segon lloc, els clients estan interessats principalment en les dades visuals i d'àudio, perquè la major part de la lògica del joc es simula al servidor del joc. En tercer lloc, en alguns jocs el jugador no necessita saber certes dades, com ara la posició de l'enemic a l'altre costat del mapa, perquè en cas contrari pot olorar paquets i saber exactament on moure's per matar-lo.

Serialització

El primer pas és convertir les dades que volem enviar (entrada o estat del joc) en un format adequat per a la transmissió. Aquest procés s'anomena serialització.

Immediatament em ve al cap la idea d'utilitzar un format llegible per humans, com ara JSON o XML. Però això serà completament ineficient i ocuparà la major part del canal per res.

En canvi, es recomana utilitzar el format binari, que és molt més compacte. És a dir, els paquets només contindran uns quants bytes. Aquí hem de tenir en compte el problema ordre de bytes, que pot ser diferent en diferents ordinadors.

Per serialitzar dades, podeu utilitzar una biblioteca, per exemple:

Només assegureu-vos que la biblioteca creï arxius portàtils i tingui cura de l'endian.

Una solució alternativa seria implementar-la tu mateix, no és tan difícil, sobretot si fas servir un enfocament centrat en dades al teu codi. A més, us permetrà realitzar optimitzacions que no sempre són possibles quan feu servir la biblioteca.

Glenn Fiedler ha escrit dos articles sobre la serialització: Paquets de lectura i escriptura и Estratègies de serialització.

Compressió

La quantitat de dades transferides entre els clients i el servidor està limitada per l'ample de banda del canal. La compressió de dades us permetrà transferir més dades a cada instantània, augmentar la freqüència d'actualització o simplement reduir els requisits d'ample de banda.

Embalatge de bits

La primera tècnica és l'empaquetament de bits. Consisteix a utilitzar exactament el nombre de bits que cal per descriure el valor desitjat. Per exemple, si teniu una enumeració que pot tenir 16 valors diferents, en comptes d'un byte sencer (8 bits), podeu utilitzar només 4 bits.

Glenn Fiedler explica com implementar-ho a la segona part de l'article. Paquets de lectura i escriptura.

L'empaquetament de bits funciona especialment bé amb la discretització, que serà el tema de la següent secció.

Mostreig

Mostreig és una tècnica de compressió amb pèrdues que utilitza només un subconjunt de valors possibles per codificar un valor. La manera més senzilla d'implementar la discretització és arrodonint nombres de coma flotant.

Glenn Fiedler (de nou!) mostra com aplicar la discretització a la pràctica al seu article Compressió instantània.

Algorismes de compressió

La següent tècnica seran algorismes de compressió sense pèrdues.

Aquests són, al meu entendre, els tres algorismes més interessants que heu de conèixer:

  • Codificació Huffman amb codi precalculat, que és extremadament ràpid i pot produir bons resultats. S'utilitzava per comprimir paquets al motor de xarxa Quake3.
  • zlib és un algorisme de compressió de propòsit general que mai augmenta la quantitat de dades. Com pots veure aquí, s'ha utilitzat en una varietat d'aplicacions. Per actualitzar estats, pot ser redundant. Però pot ser útil si necessiteu enviar actius, textos llargs o terreny als clients des del servidor.
  • Còpia de longituds de tirada és probablement l'algoritme de compressió més senzill, però és molt eficient per a certs tipus de dades i es pot utilitzar com a pas de preprocessament abans de zlib. És especialment indicat per comprimir terrenys formats per rajoles o voxels en els quals es repeteixen molts elements veïns.

compressió delta

La tècnica de compressió final és la compressió delta. Rau en el fet que només es transmeten les diferències entre l'estat actual del joc i l'últim estat rebut pel client.

Es va utilitzar per primera vegada al motor de xarxa Quake3. Aquí teniu dos articles que expliquen com utilitzar-lo:

Glenn Fiedler també ho va fer servir a la segona part del seu article. Compressió instantània.

Xifrat

A més, és possible que hàgiu de xifrar la transmissió d'informació entre els clients i el servidor. Hi ha diverses raons per a això:

  • Privadesa/confidencialitat: els missatges només els pot llegir el destinatari i cap altre sniffer de la xarxa els podrà llegir.
  • autenticació: una persona que vol fer el paper de jugador ha de conèixer la seva clau.
  • prevenció de trampes: serà molt més difícil per als jugadors maliciosos crear els seus propis paquets de trampes, hauran de replicar l'esquema de xifratge i trobar la clau (que canvia a cada connexió).

Recomano fermament utilitzar una biblioteca per a això. Suggereixo utilitzar libsodi, perquè és especialment senzill i té grans tutorials. Particularment interessant és el tutorial sobre intercanvi de claus, que us permet generar noves claus a cada nova connexió.

Protocol d'aplicació: Conclusió

Això conclou el protocol d'aplicació. Crec que la compressió és completament opcional i la decisió d'utilitzar-la només depèn del joc i de l'ample de banda requerit. El xifratge, al meu entendre, és obligatori, però en el primer prototip es pot prescindir d'ell.

Lògica d'aplicació

Ara podem actualitzar l'estat del client, però podem tenir problemes de latència. El jugador, després de fer una entrada, ha d'esperar una actualització de l'estat del joc del servidor per veure quin efecte ha tingut en el món.

A més, entre dues actualitzacions d'estat, el món és completament estàtic. Si la taxa d'actualització de l'estat és baixa, els moviments seran molt bruscos.

Hi ha diverses tècniques per mitigar l'impacte d'aquest problema, i les tractaré a la secció següent.

Tècniques de suavització retardada

Totes les tècniques descrites en aquesta secció es comenten amb detall a la sèrie. Multijugador de ritme ràpid Gabriel Gambetta. Recomano molt la lectura d'aquesta excel·lent sèrie d'articles. També inclou una demostració interactiva per veure com funcionen aquestes tècniques a la pràctica.

La primera tècnica és aplicar el resultat d'entrada directament sense esperar una resposta del servidor. Es diu predicció del costat del client. Tanmateix, quan el client rep una actualització del servidor, ha de verificar que la seva predicció era correcta. Si no és així, només cal que canviï el seu estat segons el que va rebre del servidor, perquè el servidor és autoritari. Aquesta tècnica es va utilitzar per primera vegada a Quake. Podeu llegir-ne més informació a l'article. Revisió del codi de Quake Engine Fabien Sanglars [traducció sobre Habré].

El segon conjunt de tècniques s'utilitza per suavitzar el moviment d'altres entitats entre dues actualitzacions d'estat. Hi ha dues maneres de resoldre aquest problema: la interpolació i l'extrapolació. En el cas de la interpolació, es prenen els dos últims estats i es mostra la transició d'un a l'altre. El seu inconvenient és que provoca una petita fracció del retard, perquè el client sempre veu el que va passar en el passat. L'extrapolació consisteix a predir on haurien de ser ara les entitats en funció de l'últim estat rebut pel client. El seu desavantatge és que si l'entitat canvia completament la direcció del moviment, hi haurà un gran error entre la previsió i la posició real.

L'última tècnica més avançada, útil només en FPS, és compensació de retard. Quan s'utilitza la compensació de retard, el servidor té en compte els retards del client quan dispara a l'objectiu. Per exemple, si un jugador va fer un tret al cap a la pantalla, però en realitat el seu objectiu es trobava en una ubicació diferent a causa del retard, seria injust negar-li el dret a matar a causa del retard. Així, el servidor rebobina el temps fins a quan el jugador va disparar per simular el que el jugador va veure a la pantalla i comprovar si hi ha una col·lisió entre el seu tir i l'objectiu.

Glenn Fiedler (com sempre!) va escriure un article el 2004 Física de xarxa (2004), en què va posar les bases per a la sincronització de simulacions físiques entre el servidor i el client. El 2014 va escriure una nova sèrie d'articles física de xarxes, en què va descriure altres tècniques per sincronitzar simulacions físiques.

També hi ha dos articles a la wiki de Valve, Xarxa multijugador font и Mètodes de compensació de latència en el disseny i l'optimització de protocols de client/servidor dins del joc tractar la compensació per retard.

Prevenció de trampes

Hi ha dues tècniques principals de prevenció de trampes.

Primer, dificultant que els tramposos enviïn paquets maliciosos. Com s'ha esmentat anteriorment, una bona manera d'implementar-lo és el xifratge.

En segon lloc, el servidor autoritzat només hauria de rebre ordres/entrada/accions. El client no hauria de poder canviar l'estat al servidor sinó enviant l'entrada. Aleshores, el servidor, cada vegada que rep una entrada, ha de comprovar-ne la validesa abans d'aplicar-la.

Lògica d'aplicació: Conclusió

Us recomano que implementeu una manera de simular una alta latència i baixes taxes de refresc perquè pugueu provar el comportament del vostre joc en males condicions, fins i tot quan el client i el servidor s'executen a la mateixa màquina. Això simplifica molt la implementació de tècniques de suavització de retard.

Altres recursos útils

Si voleu explorar altres recursos de models de xarxa, podeu trobar-los aquí:

Font: www.habr.com

Afegeix comentari