Erlang rusă despre soclu tcp pentru manechine

Despre socket TCP pentru manechine

O poveste foarte simplificată despre soclul TCP pentru cei care nu se află în subiect :)

Stăteam, scriind documentația internă pentru proiectul meu. Și a fost necesar, printre altele, să descriem implementarea socket-ului de la client. Eu însumi am făcut această implementare pe Java pentru un anumit client care efectuează testarea funcțională și de stres a serverului. Și am nevoie de o altă implementare pe .NET pentru aplicația Unity, care va fi un client real al serverului meu. Iar această implementare va fi scrisă de un alt dezvoltator.







Așa că am scris despre socket Java, și a dat seama că ar fi frumos să spun în primul rând, cum lucrează socket TCP. Și mi-am dat seama că o astfel de poveste poate fi publicată, pentru că nu mai este o documentație internă specifică. Ei bine, asta se răspândește :)

Cum funcționează soclul la un nivel scăzut? Acesta este un socket TCP Full Duplex, fără niciun alt add-on cum ar fi protocolul HTTP. Full Duplex sunt două țevi. O conductă de date curge de la client la server. Pe un alt flux de țevi de la server la client. Acestea curg în pachete mici, de exemplu, 1500 de octeți (în funcție de setările de rețea).

Tevile funcționează independent una de cealaltă. Ceea ce curge unul câte unul nu interferează cu ceea ce curge pe celălalt.

Și pentru a lucra cu asta, trebuie să rezolvi două probleme.

Problema extragerii datelor dintr-un soclu

Clientul trimite ceva la server, o bucată de date. Se poate potrivi într-un singur pachet. Și poate că nu se potrivește, poate fi împărțită în mai multe pachete. TCP garantează că toate aceste pachete vor ajunge și vor ajunge în ordinea dorită. Însă serverul trebuie să știe cum să colecteze din nou o parte din datele din aceste pachete.

Să presupunem în mod condiționat că clientul trimite o astfel de solicitare:

Acum nu atingem tema serializării datelor, în ce format sunt transmise. Să presupunem că avem un astfel de obiect, cumva descris în limba de programare pe care scriem partea clientului. Și acest obiect este într-un fel serializat într-o serie de octeți. Să presupunem, în formă serializată, că va arăta astfel:

Să presupunem că obiectul este mare, iar matricea octeților este mare. Într-un singur pachet nu sa urcat, a fost împărțit și a trecut prin conductă sub forma a 3 pachete:

Prin urmare, serverul citește prima piesă din conductă. Și ce ar trebui să facă el? Puteți încerca să deserializați. Dacă vom obține o eroare, va fi clar că datele nu sunt complete și trebuie să obținem mai mult. Și de fiecare dată când ceva vine de la țeavă, vom încerca să-l deserializăm. Sa dovedit - ei bine, interpretăm și trimităm mai departe pentru procesare. Nu a funcționat - așteptăm mai multe date.







Dar este rău că încercările suplimentare de deserializare vor crea sarcini inutile pe CPU. Avem nevoie de o altă opțiune.

În modulul Erlang gen_tcp oferă soluții diferite pentru această problemă. Să folosim ceea ce deja există. De exemplu, există o opțiune în care serverul presupune că fiecare cerere client are un antet. Și antetul indică lungimea datelor care formează această interogare.

Aceasta înseamnă că întreaga interogare arată astfel:

Și spart în pachete, cum ar fi:

Când serverul ajunge la 42, atunci serverul citește antetul, vede în el lungimea cererii - 42 de octeți și înțelege că este necesar să aștepte până când vor veni acești 42 de octeți. Și după aceea, datele pot fi deserializate și interpretate. De exemplu, interpretarea poate fi că serverul va invoca metoda de conectare cu argumentele "Bob" și "123". În mod similar, acesta va prelua datele și clientul atunci când le va primi de la server.

Dimensiunea antetului poate fi de 1 sau 2 sau 4 octeți. Astfel de opțiuni sunt oferite de gen_tcp. când este utilizat în modul activ. (Și în modul pasiv, noi extragem și interpretăm acest antet, deci suntem liberi să facem cum vă place).

Care este dimensiunea antetului mai bună? In 1 octet se potrivesc numărul 2 ^ 8 = 256. Apoi, cererea nu poate fi mai mare de 256 de octeți. Acest lucru este prea mic. În 2 octeți se potrivesc numărul 2 ^ 16 = 65536. Prin urmare, o cerere poate fi de până la 65.536 octeți. Acest lucru este suficient pentru majoritatea cazurilor.

Dar, de exemplu, este posibil să fie nevoie să trimiteți cereri mari serverului, astfel încât 2 octeți pe antet să fie mici. Aici am nevoie de ea și am luat antetul în 4 octeți.

Am luat-o, dar eu sunt sufocata de un broasca :) Nu vor exista cereri atat de mari. Practic, toate cererile vor fi mici, dar totuși toate vor folosi un antet de 4 octeți. Există loc pentru optimizare aici. De exemplu, puteți utiliza două antete. Primul, singur-octet, va indica lungimea celui de-al doilea. Iar al doilea, 1-4 octeți, va indica lungimea pachetului :) Sau puteți utiliza un int fără dimensiuni, ocupând 1-4 octeți, așa cum se face în serializarea AMF. Dacă doriți, puteți salva traficul.

Desigur, o astfel de optimizare mic râde numai cei care folosesc HTTP :) Pentru HTTP nu este un moft, iar fiecare cerere a trimis pachet bolnăvicios de metadate, nu este serverul corect, și pentru că risipind traficul pe scara nu este comparabilă cu soclu meu TCP elegant :)

Probleme de potrivire a cererilor și răspunsurilor

Aici clientul a făcut o cerere, iar puțin mai târziu, din altceva, a venit la el ceva. Care este răspunsul la ultima solicitare? Sau răspunsul la o interogare anterioară? Sau nu un răspuns deloc, ci un impuls activ de date asupra inițiativei serverului? Clientul trebuie să știe cum să facă ceva.

O bună opțiune este că fiecare solicitare a clientului trebuie să aibă un identificator unic. Răspunsul serverului va avea același identificator. Deci, va fi posibil să se determine la care solicitare a venit răspunsul.

În general, avem nevoie de trei opțiuni pentru interacțiunea client și server:

  • Clientul trimite o cerere către server și dorește să primească un răspuns
  • Clientul trimite o solicitare serverului și nu are nevoie de niciun răspuns
  • Serverul împinge în mod activ datele către client

(De fapt, există și cea de-a 4-a versiune, când serverul trimite în mod activ o solicitare clientului și dorește să primească un răspuns, dar nu am avut nevoie de această opțiune și nu l-am implementat).

În primul caz, adăugăm un identificator la interogare:

Și primim răspunsul:

În al doilea caz, nu adăugăm un identificator la interogare:







Articole similare

Trimiteți-le prietenilor: