Berkeley Sockets este o interfață de programare a aplicațiilor (API) care este o bibliotecă pentru dezvoltarea aplicațiilor în limbajul C cu suport pentru comunicarea între procese (IPC), adesea folosită în rețelele de calculatoare .
Socket-urile Berkeley (cunoscute și sub numele de API socket BSD ) au apărut pentru prima dată ca API în sistemul de operare 4.1BSD Unix (lansat în 1982) [1] . Cu toate acestea, abia în 1989 UC Berkeley a putut începe să lanseze versiuni ale sistemului de operare și ale bibliotecii de rețea fără restricțiile de licențiere AT&T ale Unix cu drepturi de autor.
API-ul Berkeley Sockets a format standardul de abstractizare de facto pentru socketurile de rețea. Majoritatea celorlalte limbaje de programare folosesc o interfață similară cu API-ul C.
API -ul Transport Layer Interface (TLI) bazat pe STREAM este o alternativă la API-ul socket. Cu toate acestea, API-ul Berkeley Sockets este foarte dominat în popularitate și număr de implementări.
Interfața socket Berkeley este un API care permite comunicarea între computere sau între procese de pe același computer. Această tehnologie poate funcționa cu multe dispozitive I/O și drivere diferite , deși suportul lor depinde de implementarea sistemului de operare . Această implementare a interfeței stă la baza TCP/IP , datorită căruia este considerată una dintre tehnologiile fundamentale pe care se bazează Internetul . Tehnologia socket a fost dezvoltată pentru prima dată la UC Berkeley pentru a fi utilizată pe sisteme UNIX . Toate sistemele de operare moderne au o anumită implementare a interfeței socket Berkeley, deoarece aceasta a devenit interfața standard pentru conectarea la Internet.
Programatorii pot accesa interfața socket la trei niveluri diferite, dintre care cel mai puternic și fundamental este nivelul socket-urilor brute . Un număr destul de mic de aplicații trebuie să limiteze controlul asupra conexiunilor de ieșire pe care le implementează, astfel încât suportul pentru socket brut a fost destinat să fie disponibil numai pe computerele utilizate pentru dezvoltare bazate pe tehnologii legate de Internet. Ulterior, majoritatea sistemelor de operare au implementat suport pentru ele, inclusiv Windows .
Biblioteca de software Berkeley Sockets include multe fișiere de antet asociate.
<sys/socket.h> Funcții de bază socket BSD și structuri de date. <netinet/in.h> Familiile de adresă/protocol PF_INET și PF_INET6. Folosite pe scară largă pe Internet, acestea includ adrese IP, precum și numere de porturi TCP și UDP. <sys/un.h> Familia de adrese PF_UNIX/PF_LOCAL. Folosit pentru comunicarea locală între programe care rulează pe același computer. Nu se aplică rețelelor de calculatoare. <arpa/inet.h> Funcții pentru lucrul cu adrese IP numerice. <netdb.h> Funcții pentru conversia numelor de protocol și a numelor de gazdă în adrese numerice. Datele locale sunt folosite similar cu DNS.socket()creează un punct final de conexiune și returnează un handle la . socket()are trei argumente:
Funcția revine −1în caz de eroare. În caz contrar, returnează un număr întreg care reprezintă mânerul atribuit.
Prototip #include <sys/types.h> #include <sys/socket.h> int socket ( domeniu int , tip int , protocol int );Funcțiile gethostbyname()și gethostbyaddr()returnează un pointer către un obiect de tip struct hostent care descrie o gazdă Internet prin nume sau, respectiv, prin adresă. Această structură conține fie informații primite de la serverul de nume, fie câmpuri arbitrare dintr-o linie din /etc/hosts. Dacă serverul de nume local nu rulează, atunci aceste rutine caută în /etc/hosts. Funcțiile preiau următoarele argumente:
Funcțiile returnează un pointer NULL în caz de eroare. În acest caz, un număr întreg suplimentar h_errno poate fi verificat pentru a detecta o eroare sau o gazdă invalidă sau necunoscută. În caz contrar, este returnat o struct hostent * validă .
Prototipuri struct hostent * gethostbyname ( const char * nume ); struct hostent * gethostbyaddr ( const void * addr , int len , int tip );connect() Stabilește o conexiune la server. Returnează un număr întreg care reprezintă codul de eroare: 0 indică succesul și -1 indică o eroare.
Unele tipuri de prize sunt fără conexiune, în special prize UDP. Pentru ei, conexiunea capătă o semnificație specială: destinația implicită pentru trimiterea și primirea datelor este atribuită adresei transmise, permițând astfel de funcții să fie utilizate ca send()la recv()prizele fără conexiune.
Un server ocupat poate respinge încercarea de conectare, așa că unele tipuri de programe trebuie configurate pentru a reîncerca conexiunea.
Prototip #include <sys/types.h> #include <sys/socket.h> int connect ( int sockfd , const struct sockaddr * serv_addr , socklen_t addrlen );bind()leagă un socket la o anumită adresă. Când un socket este creat cu socket(), acesta este asociat cu o anumită familie de adrese, dar nu cu o anumită adresă. Înainte ca un socket să poată accepta conexiuni de intrare, acesta trebuie să fie legat de o adresă. bind()are trei argumente:
Returnează 0 la succes și -1 la eroare.
Prototip #include <sys/types.h> #include <sys/socket.h> int bind ( int sockfd , const struct sockaddr * my_addr , socklen_t addrlen );listen()pregătește soclul legat să accepte conexiuni de intrare (numite „ascultare”). Această funcție se aplică numai tipurilor de prize SOCK_STREAMși SOCK_SEQPACKET. Ia doua argumente:
Odată ce o conexiune este acceptată, aceasta este scoasă din coadă. La succes, se returnează 0; în caz de eroare, se returnează -1.
Prototip #include <sys/socket.h> int listen ( int sockfd , int backlog );accept()folosit pentru a accepta o solicitare de conectare de la o gazdă la distanță. Acceptă următoarele argumente:
Funcția returnează descriptorul de socket asociat conexiunii acceptate sau -1 în caz de eroare.
Prototip #include <sys/types.h> #include <sys/socket.h> int accept ( int sockfd , struct sockaddr * cliaddr , socklen_t * addrlen );După crearea unui socket, puteți seta parametri suplimentari pentru acesta. Iată câteva dintre ele:
Prizele Berkeley pot funcționa în unul dintre două moduri: blocare sau neblocare. Un socket de blocare nu returnează controlul până când nu a trimis (sau a primit) toate datele specificate pentru operație. Acest lucru este valabil numai pentru sistemele Linux. Pe alte sisteme, cum ar fi FreeBSD, este firesc ca un socket de blocare să nu trimită toate datele (dar puteți seta flag-ul send() sau recv() MSG_WAITALL). Aplicația ar trebui să verifice valoarea returnată pentru a ține evidența câți octeți au fost trimiși/primiți și să retrimite informațiile neprocesate în mod corespunzător [2] . Acest lucru poate duce la probleme dacă socket-ul continuă să asculte: programul se poate bloca deoarece socket-ul așteaptă date care s-ar putea să nu ajungă niciodată.
Un socket este de obicei specificat ca blocant sau neblocant folosind funcțiile fcntl()sau ioctl().
Pentru a transfera date, puteți utiliza funcțiile standard pentru citirea/scrierea fișierelor readși write, dar există funcții speciale pentru transferul de date prin socluri:
Trebuie remarcat faptul că atunci când utilizați protocolul TCP (socket-uri de tip SOCK_STREAM), există șansa de a primi mai puține date decât au fost transmise, deoarece nu toate datele au fost încă primite, așa că trebuie fie să așteptați până când funcția recvreturnează 0 octeți, sau setați un steag MSG_WAITALLpentru funcția recv, care o va forța să aștepte până la sfârșitul transferului. Pentru alte tipuri de socluri, steag-ul MSG_WAITALLnu schimbă nimic (de exemplu, în UDP, întregul pachet = întregul mesaj). Vezi și Prize blocante și neblocante.
Sistemul nu eliberează resursele alocate de apel socket()până când apelul are loc close(). Acest lucru este important mai ales dacă apelul connect()a eșuat și poate fi reîncercat. Fiecare apel socket()trebuie să aibă un apel corespunzător close()în toate căile de execuție posibile. Fișierul antet <unistd.h> trebuie adăugat pentru a suporta funcția de închidere.
Rezultatul executării unui apel de sistem close()este doar apelarea interfeței pentru a închide soclu-ul, nu pentru a închide soclu-ul în sine. Aceasta este o comandă pentru kernel pentru a închide socket-ul. Uneori, pe partea de server, priza poate intra în modul de repaus TIME_WAITtimp de până la 4 minute. [unu]
TCP implementează conceptul de conexiune. Procesul creează un socket TCP apelând o funcție socket()cu parametrii PF_INETsau PF_INET6, precum și SOCK_STREAM(Socket Stream) și IPPROTO_TCP.
Crearea unui server TCP simplu constă din următorii pași:
Crearea unui client TCP este după cum urmează:
UDP se bazează pe un protocol fără conexiune, adică un protocol care nu garantează livrarea informațiilor. Pachetele UDP pot ajunge necorespunzător, pot fi duplicate și pot ajunge de mai multe ori sau chiar să nu ajungă deloc la destinație. Datorită acestor garanții minime, UDP este semnificativ inferior TCP. Nicio stabilire a conexiunii înseamnă că nu există fluxuri sau conexiuni între două gazde, deoarece datele ajung în datagrame ( Datagram Socket ).
Spațiul de adrese UDP, zona numerelor de port UDP (TSAP în terminologia ISO), este complet separat de porturile TCP.
Codul poate crea un server UDP pe portul 7654 astfel:
int sock = socket ( PF_INET , SOCK_DGRAM , IPPROTO_UDP ); struct sockaddr_insa ; _ int legat ; ssiize_t recsize ; socklen_t * address_len = NULL ; sa . sin_addr . s_addr = htonl ( INADDR_ANY ); sa . sin_port = htons ( 7654 ); bound = bind ( sock , ( struct sockaddr * ) & sa , sizeof ( struct sockaddr ) ); dacă ( legat < 0 ) fprintf ( stderr , "bind(): eroare %s \n " , strerror ( errno ) );bind() leagă un socket la o pereche adresă/port.
în timp ce ( 1 ) { printf ( "test recv.... \n " ); recsize = recvfrom ( sock , ( void * ) Hz , 100 , 0 , ( struct sockaddr * ) & sa , address_len ); dacă ( redimensionare < 0 ) fprintf ( stderr , "Eroare %s \n " , strerror ( errno ) ); printf ( "recsize: %d \n " , recsize ); somn ( 1 ); printf ( "datagrama: %s \n " , hz ); }O astfel de buclă infinită primește toate datagramele UDP care sosesc pe portul 7654 folosind recvfrom() . Funcția folosește parametrii:
O demonstrație simplă a trimiterii unui pachet UDP care conține „Salut!” la adresa 127.0.0.1, portul 7654, arată cam așa:
#include <stdio.h> #include <errno.h> #include <șir.h> #include <sys/socket.h> #include <sys/types.h> #include <netinet/in.h> #include <unistd.h> /* pentru a apela close() pe un socket */ int main ( void ) { int ciorap ; struct sockaddr_insa ; _ int bytes_sent ; const char * buffer = "Bună ziua!" ; int buffer_length ; buffer_length = strlen ( buffer ) + 1 ; sock = socket ( PF_INET , SOCK_DGRAM , IPPROTO_UDP ); if ( ciorap == -1 ) { printf ( "Eroare la crearea soclului" ); returnează 0 ; } sa . sin_family = PF_INET ; sa . sin_addr . s_addr = htonl ( 0x7F000001 ); sa . sin_port = htons ( 7654 ); octeți trimiși = trimite la ( ciorap , tampon , strlen ( buffer ) + 1 , 0 , ( struct sockaddr * ) & sa , sizeof ( struct sockaddr_in ) ); dacă ( octeți_trimiși < 0 ) printf ( "Eroare la trimiterea pachetului: %s \n " , strerror ( errno ) ); închidere ( ciorap ); returnează 0 ; }Definiția „de jure” a interfeței socket conținută în standardul POSIX , mai bine cunoscută ca: