Metalkia despre socket TCP pentru Dummies

Despre socket TCP pentru Dummies

O poveste foarte simplificată a socket TCP pentru cei care nu sunt în subiect :)

Am stat și am scris documentația internă pentru proiectul dumneavoastră. Și a fost necesar, printre altele, descrie punerea în aplicare a unui soclu pe partea de client. M-am făcut această implementare în Java pentru un anumit client, server de a efectua teste funcționale și de stres. O nevoie este o altă implementare pe .NET pentru aplicații unitate, și că va fi un client real al serverului meu. Și această realizare va scrie 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 am dat seama că această poveste poate fi stabilită în public, pentru că nu este o documente interne specifice. Ei bine, asta răspândit :)

Cum priza la un nivel scăzut? Este vorba despre socket TCP Full Duplex, fără nici un add-on-uri, cum ar fi HTTP. Full Duplex - cele două tuburi. O țeavă de fluxul de date de la client la server. Conform unui alt flux de țeavă de la server la client. Ele curg în ambalaje mici, cum ar fi 1500 bytes (în funcție de configurația rețelei).

Tuburile funcționează independent unul față de celălalt. Ceea ce curge prin unul, nu împiedică care curge prin cealaltă.

Și pentru a lucra cu el, este necesar să se rezolve două probleme.

Problema de extragere a datelor de la priza

Iată un client care trimite la server, orice bucată unică de date. Totul se poate încadra într-un singur pachet. Și nu se poate potrivi, pot fi împărțite în mai multe pachete. TCP garantează că toate aceste pachete ajung, și să ajungă în ordinea corectă. Dar serverul este într-un fel ar trebui să știe cum de aceste pachete din nou, pentru a colecta o bucată solidă de date.

Să ne imaginăm în mod arbitrar că clientul trimite o cerere:

În prezent, nu ating subiectul datelor serializare, acestea sunt transmise în orice format. Să presupunem că avem un obiect, cum ar fi descris în limbajul de programare, în cazul în care vom scrie porțiunea de client. Și acest obiect să fie cumva serializate o matrice octet. De exemplu, în formă serializate, se va arata astfel:

Să presupunem că un obiect mare și o matrice de octet pentru a obține mai mult. Într-un singur pachet, el nu este primit, a fost divizată și a mers pe tub sub formă de pachete de 3:

Prin urmare, serverul citește prima bucată de țeavă. Și a face cu ea? Puteți încerca să deserializati. Dacă primiți o eroare, va fi clar că datele nu sunt complete, și avem nevoie pentru a obține mai mult. Și așa de fiecare dată când ceva iese din tub, vom încerca să-l deserializati. Sa dovedit - bine, interpreta și trimite o prelucrare ulterioară. Ea nu a funcționat - de așteptare pentru mai multe date.

Dar atunci lucru rău este faptul că încercările suplimentare de a deserializati va crea o sarcină suplimentară pentru CPU. Am nevoie de o altă opțiune.

În Erlang modulul gen_tcp oferă diverse opțiuni pentru rezolvarea acestei probleme. Să folosim ceea ce este deja acolo. De exemplu, există un exemplu de realizare în care serverul presupune că fiecare client are un antet cerere. Iar titlul conține lungimea datelor care alcătuiesc cererea.

Asta este, o interogare arată astfel:

Și împărțit în pachete, cum ar fi acest lucru:

Când serverul vine la serverul de 42 citește titlul, vede ca lungimea de interogare - 42 bytes, și înțelege că trebuie să așteptați până când vin, aceste 42 de octeți. Și atunci puteți deserializati date și interpreta. De exemplu, interpretarea ar putea fi faptul că serverul de apeluri la metoda de conectare cu argumente „Bob“ și „123“. În mod similar, se va extrage, de asemenea, datele și client atunci când el le va primi de la server.

Dimensiunea antet poate fi 1 sau 2 sau 4 octeți. Astfel de variante oferă gen_tcp. Atunci când este utilizat în modul activ. (Și, într-un mod pasiv, noi extragem și să interpreteze acest antet, deci liber să faci ce vrei).

Care este dimensiunea antetului este mai bine? In 1 octet se potrivesc numărul 2 ^ 8 = 256. Apoi, cererea nu poate fi mai mare de 256 de octeți. E prea puțin. Î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 cele mai multe cazuri.

Dar, de exemplu, poate fi necesar pentru a trimite la serverul de interogări mari, astfel încât 2 octeți la titlul nu va fi suficient. Aici am nevoie de ea, și am luat un antet de 4 octeți.

Ia ceva luat, dar am fost sufocare râioasă :) Aceste cereri vor fi puțin mai mare. Practic, toate cererile sunt mici, dar toate la fel ei vor folosi un antet de 4 octeți. Există temei pentru optimizare. De exemplu, puteți utiliza cele două titluri. În primul rând, un singur octet, va indica lungimea celei de a doua. Un al doilea, 1-4 octet va indica lungimea de pachete :) Sau este posibil să se utilizeze int adimensională, ocupă octeți 1-4, așa cum face în serializare AMF. puteți salva de lățime de bandă, dacă se dorește.

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 :)

Problema cererilor potrivite și a răspunsurilor

Aici clientul a făcut cererea, și un pic mai târziu de cealaltă conductă să le-a venit ceva. Care este răspunsul la ultima interogare? Sau ca răspuns la unele interogare mai devreme? Sau nici un răspuns, iar informația de împingere activă la inițiativa serverului? Clientul trebuie să știu cumva ce să facă.

O opțiune bună - fiecare cerere client trebuie să aibă un identificator unic. Răspunsul de la server va avea același identificator. Deci, pe care le poate determina care este cererea a venit răspunsul.

În general, avem nevoie de trei versiuni ale interacțiunii client-server:

  • Clientul trimite o cerere către server și doriți să obțineți un răspuns
  • Clientul trimite o cerere către server și nu are nevoie de nici un răspuns
  • Serverul împinge în mod activ de date la client

(De fapt, există opțiunea 4, serverul trimite în mod activ o cerere către un client și doriți să obțineți răspunsul. Dar eu sunt o astfel de opțiune nu a fost nevoie și nu am dat seama).

În primul caz vom adăuga ID cerere:

Și vom obține răspunsul:

În al doilea caz, nu vom adăuga la identificatorul cerere: