Serverul și clientul își trimit continuu mesaje: „jucătorul s-a mișcat”, „a scris în chat”, „a primit un item”. Fiecare mesaj are o formă fixă, stabilită dinainte, ca un formular tipizat. Dacă serverul completează formularul cu o rubrică în plus, iar clientul îl citește pe cel vechi, totul se decalează: clientul citește numere greșite, se blochează sau este deconectat. Lecția îți explică, fără matematică, de ce se întâmplă asta și cum faci schimbări fără să rupi jocul.
packet.h și în client în Packet.h. Numărul fiecărui mesaj și ordinea și mărimea rubricilor trebuie să fie identice pe ambele părți. Dacă schimbi una, compilezi și distribui ambele în același timp și testezi pe serverul de test.Nivel: Avansat. Ce trebuie să știi înainte: ce sunt game, db, client și sursa, compilarea game și compilarea clientului. Ajută și lecția despre diferențele client–server la iteme noi.
Cuvinte pe care le vei întâlni
| Cuvânt | Ce înseamnă |
|---|---|
| Protocol | Regulile comune după care serverul și clientul își trimit mesaje. |
| Pachet (packet) | Un mesaj trimis prin rețea. |
| Header | Primul octet al pachetului: un număr care spune ce fel de mesaj e (chat, mișcare, item). |
| Struct | Descrierea în cod a unui pachet: ce rubrici are, în ce ordine și cât de mare e fiecare. |
| Octet (byte) | Cea mai mică unitate de date. BYTE = 1 octet, WORD = 2 octeți, DWORD = 4 octeți. |
| CG / GC | Direcția: CG = de la client la game, GC = de la game la client. |
Cum arată un pachet, pe exemplul chatului
În sursa serverului verificată, în game/src/packet.h:
typedef struct command_chat
{
BYTE header;
WORD size;
BYTE type;
} TPacketCGChat;În sursa clientului, în source/UserInterface/Packet.h:
typedef struct command_chat
{
BYTE header;
WORD length;
BYTE type;
} TPacketCGChat;Observi ceva? A doua rubrică se numește size pe server și length în client. Nu contează. Numele există doar pentru programatori. Prin rețea circulă doar octeții: 1 octet de header, 2 octeți de lungime, 1 octet de tip, apoi textul. Ce contează e să fie aceeași ordine și aceeași mărime.
Numărul mesajului e și el identic pe ambele părți. Pe server, HEADER_CG_CHAT = 3 și HEADER_GC_CHAT = 4; în client, la fel.
De ce un câmp în plus strică totul
Imaginează-ți un formular de bancă cu rubricile: nume (20 de căsuțe), sumă (10 căsuțe). Dacă banca tipărește un formular nou cu o rubrică „telefon” între ele, dar casierul îl citește după șablonul vechi, el va citi telefonul ca sumă. Exact asta se întâmplă cu pachetele.
În sursă, la începutul listei de pachete apare #pragma pack(1). Asta îi spune compilatorului: „nu lăsa spații goale între rubrici”. Fără ea, compilatorul ar putea adăuga octeți de umplutură, iar serverul și clientul (compilate cu unelte diferite, unul pe FreeBSD, altul în Visual Studio) ar putea ajunge la mărimi diferite pentru același struct. De aceea nu muți definiții de pachete în afara zonei #pragma pack(1) ... #pragma pack().
Ce se strică, concret
| Ce vezi | Cauză probabilă |
|---|---|
| Clientul se deconectează imediat după login | Un pachet trimis la intrare are altă mărime pe o parte |
| Clientul se blochează la o acțiune anume | Pachetul acelei acțiuni diferă |
| Valori absurde în joc (nivel 0, yang uriaș, nume ciudate) | Rubrici citite decalat |
syserr cu mesaje despre pachete necunoscute sau mărimi greșite | Header necunoscut sau lungime greșită |
Mesajele exacte din syserr diferă de la pachet la pachet.
Pasul 1: Înainte să schimbi un pachet
Pune-ți întâi întrebarea: chiar trebuie? Multe lucruri se pot face cu pachete existente, de exemplu un mesaj de chat (vezi prima modificare în sursă) sau un quest.
Dacă trebuie, găsește struct-ul pe ambele părți:
grep -n "struct command_chat" /usr/home/sursa/game/src/packet.hPe Windows, în Visual Studio, folosește Edit → Find and Replace → Find in Files cu același text în sursa clientului.
Pasul 2: Schimbă simetric
- Notează struct-ul vechi: fiecare rubrică, tipul ei, mărimea în octeți.
- Adaugă noua rubrică în aceeași poziție pe server și în client, cu același tip.
- Verifică și codul care completează și citește pachetul: dacă trimite o mărime fixă, trebuie actualizată.
- Dacă pachetul trece și prin
dbsau între core-uri, verifică și acolo:dbșigameau structurile comune în folderulcommon/, iar core-urile își trimit și ele mesaje între ele.
Pasul 3: Compilează și distribuie împreună
- Compilezi
game(șidb, dacă ai atinscommon/) și clientul. - Le testezi împreună pe serverul de test: login, chat, inventar, comerț, schimbare de canal, relogare, mesaje lungi.
- La lansare, clienții vechi nu vor mai merge corect. Pui serverul nou și clientul nou în aceeași mentenanță, iar clientul ajunge la jucători prin update cu autopatcher.
Regula de siguranță: serverul nu are încredere în client
Clientul poate fi modificat de oricine. Orice valoare primită de la client (o cantitate, o poziție, un ID de item) trebuie verificată pe server: e în limite? are jucătorul voie? există itemul? Un pachet nou fără verificări e o ușă deschisă pentru trișori. Detalii în protecția împotriva accesului neautorizat.
Greșeli des întâlnite
- Schimbi doar serverul și uiți clientul (sau invers).
- Pui rubrica nouă în altă poziție pe cele două părți.
- Folosești tipuri diferite (
WORDpe o parte,DWORDpe cealaltă). - Lași jucătorii cu clientul vechi după update.
- Te bazezi pe verificările din client. Ele pot fi ocolite.
Întrebări frecvente
Am schimbat doar o iconiță în client. Afectează protocolul?
Nu. Iconițele, textele și interfața din pack-uri nu schimbă pachetele.
Pot adăuga un pachet complet nou în loc să modific unul existent?
Da, și e adesea mai sigur. Îi dai un header nefolosit, același pe ambele părți, și îl înregistrezi acolo unde serverul și clientul își țin lista de pachete cunoscute. Testează-l temeinic.
De ce după update merge pe un canal și pe altul nu?
Probabil un core rulează alt binar game. Verifică cu sha256 fișierul folosit de fiecare core (vezi deploy).
Ce urmează
Mergi la versionare și release pentru game, db și client. Toate lecțiile sunt în lista tutorialelor Metin2.
Server Metin2 gata de joc
Găzduire Metin2 în România, cu instalare de serverfiles, bază de date MySQL și protecție Anti-DDoS inclusă.