Prize Berkeley

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ță socket Berkeley

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 .

Fișiere antet

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.

Structuri

struct sockaddr_in stSockAddr ; ... bind ( SocketFD ,( const struct sockaddr * ) & stSockAddr , sizeof ( struct sockaddr_in ));
  • sockaddr_in
  • sockaddr_in6
  • in_addr
  • in6_addr

Funcții

socket()

socket()creează un punct final de conexiune și returnează un handle la . socket()are trei argumente:

  • domain , care specifică familia de protocol a socketului creat. Acest parametru specifică convențiile de denumire și formatul adresei. De exemplu:
    • PF_INETpentru protocolul de rețea IPv4 sau
    • PF_INET6pentru IPv6 .
    • PF_UNIXpentru socket-uri locale (folosind un fișier).
  • tip (tip) unul dintre:
    • SOCK_STREAMserviciu fiabil orientat pe flux (TCP) (serviciu) sau soclu de flux
    • SOCK_DGRAMserviciu de datagramă (UDP) sau soclu de datagramă
    • SOCK_SEQPACKETserviciu de pachete seriale de încredere
    • SOCK_RAW Un socket brut  este un protocol brut deasupra stratului de rețea.
  • protocol specifică protocolul de transport de utilizat. Cele mai frecvente sunt IPPROTO_TCP, IPPROTO_SCTP, IPPROTO_UDP, IPPROTO_DCCP. Aceste protocoale sunt specificate în <netinet/in.h>. Valoarea „ 0” poate fi utilizată pentru a selecta un protocol implicit din familia specificată ( domain) și tip ( type).

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

gethostbyname() și gethostbyaddr()

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:

  • name , care specifică numele gazdei. De exemplu: www.wikipedia.org
  • addr , care definește un pointer către o structură in_addr care conține adresa gazdei.
  • len , care specifică lungimea în octeți a addr .
  • type , care specifică tipul domeniului adresei gazdei. De exemplu: PF_INET

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

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

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:

  • sockfd - un mâner reprezentând priza când este legată
  • serv_addr este un pointer către o structură sockaddrreprezentând adresa la care ne legăm.
  • addrlen este un câmp socklen_treprezentând lungimea structurii sockaddr.

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

ascultă()

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:

  • sockfd este un descriptor de socket valid.
  • backlog este un număr întreg care indică numărul de conexiuni stabilite care pot fi procesate la un moment dat. Sistemul de operare îl setează de obicei la valoarea maximă.

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

accept()folosit pentru a accepta o solicitare de conectare de la o gazdă la distanță. Acceptă următoarele argumente:

  • sockfd — descriptorul prizei de ascultare pentru a accepta conexiunea.
  • cliaddr — un pointer către structură sockaddr, pentru a primi informații despre adresa clientului.
  • addrlen — pointer la socklen_t, definind dimensiunea structurii care conține adresa clientului și transmisă la accept(). Când accept()returnează o valoare, socklen_tindică câți octeți ai structurii cliaddrsunt utilizați în prezent.

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

Opțiuni suplimentare pentru prize

După crearea unui socket, puteți seta parametri suplimentari pentru acesta. Iată câteva dintre ele:

  • TCP_NODELAYdezactivează algoritmul lui Nagle ;
  • SO_KEEPALIVEinclude verificări periodice pentru „semne de viață”, dacă sunt acceptate de sistemul de operare.

Prize blocante și neblocante

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().

Transfer de date

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:

  • trimite
  • recv
  • Trimite catre
  • recvfrom
  • trimitemsg
  • recvmsg

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.

Eliberarea resurselor

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]

Un exemplu de client și server care utilizează TCP

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.

Server

Crearea unui server TCP simplu constă din următorii pași:

  • Crearea de socket-uri TCP prin apelarea socket().
  • Legarea unei prize la un port de ascultare apelând la bind(). Înainte de bind()a apela , programatorul trebuie să declare structura sockaddr_in, să o șterge (cu memset()), apoi sin_family( PF_INETsau PF_INET6) și să completeze câmpurile sin_port(portul de ascultare, specificați ca o secvență de octeți ). Conversia short intîn endianness se poate face cu un apel de funcție htons()(prescurtare de la gazdă la rețea).
  • Pregătiți o priză pentru a asculta conexiuni (creați o priză de ascultare) apelând listen().
  • Acceptarea conexiunilor primite printr-un apel accept(). Aceasta blochează socket-ul până când este primită o conexiune de intrare, după care returnează un descriptor de socket pentru conexiunea primită. Mânerul original rămâne un mâner care poate fi ascultat și accept()poate fi apelat din nou pe acel soclu în orice moment (atâta timp cât este deschis).
  • O conexiune la o gazdă la distanță, care poate fi creată cu send()și recv()sau write()și read().
  • Închiderea finală a fiecărei prize deschise care nu mai este necesară se face cu close(). Trebuie remarcat faptul că, dacă au existat apeluri fork(), atunci fiecare proces trebuie să închidă socket-urile cunoscute de el (kernel-ul ține evidența numărului de procese care au un mâner deschis) și, în plus, două procese nu trebuie să folosească același socket. in acelasi timp.
/* Cod server în limbaj C */ #include <sys/types.h> #include <sys/socket.h> #include <netinet/in.h> #include <arpa/inet.h> #include <stdio.h> #include <stdlib.h> #include <șir.h> #include <unistd.h> #define portul 1100 int main ( void ) { struct sockaddr_in stSockAddr ; int i32SocketFD = socket ( PF_INET , SOCK_STREAM , IPPROTO_TCP ); if ( i32SocketFD == -1 ) { perror ( "eroare la crearea soclului" ); ieșire ( EXIT_FAILURE ); } memset ( & stSockAddr , 0 , sizeof ( stSockAddr )); stSockAddr . sin_family = PF_INET ; stSockAddr . sin_port = htons ( port ); stSockAddr . sin_addr . s_addr = htonl ( INADDR_ANY ); if ( bind ( i32SocketFD , ( struct sockaddr * ) & stSockAddr , sizeof ( stSockAddr )) == -1 ) { perror ( "Eroare: legături" ); închidere ( i32SocketFD ); ieșire ( EXIT_FAILURE ); } if ( ascultă ( i32SocketFD , 10 ) == -1 ) { perror ( "Eroare: ascultare" ); închidere ( i32SocketFD ); ieșire ( EXIT_FAILURE ); } pentru (;;) { int i32ConnectFD = accept ( i32SocketFD , 0 , 0 ); if ( i32ConnectFD < 0 ) { perror ( "Eroare: accept" ); închidere ( i32SocketFD ); ieșire ( EXIT_FAILURE ); } /* efectuează operații de citire și scriere ... */ oprire ( i32ConnectFD , SHUT_RDWR ); închidere ( i32ConnectFD ); } returnează 0 ; }

Client

Crearea unui client TCP este după cum urmează:

  • Crearea unui socket TCP apelând socket().
  • Conectați-vă la un server utilizând connect(), trecând o structură sockaddr_incu sau sin_familyspecificat , pentru a specifica portul de ascultare (în ordinea octeților) și pentru a specifica adresa IPv4 sau IPv6 a serverului pe care să ascultați (tot în ordinea octeților).PF_INETPF_INET6sin_portsin_addr
  • Interacțiunea cu serverul folosind send()și recv()sau write()și read().
  • Opriți conexiunea și resetați informațiile la apel close(). De asemenea, dacă au existat apeluri fork(), fiecare proces trebuie să închidă ( close()) socket-ul.
/* Cod client C */ #include <sys/types.h> #include <sys/socket.h> #include <netinet/in.h> #include <arpa/inet.h> #include <stdio.h> #include <stdlib.h> #include <șir.h> #include <unistd.h> int main ( void ) { struct sockaddr_in stSockAddr ; int i32Res ; int i32SocketFD = socket ( PF_INET , SOCK_STREAM , IPPROTO_TCP ); if ( i32SocketFD == -1 ) { perror ( "Eroare: Imposibil de creat socket" ); returnează EXIT_FAILURE ; } memset ( & stSockAddr , 0 , sizeof ( stSockAddr )); stSockAddr . sin_family = PF_INET ; stSockAddr . sin_port = htons ( 1100 ); i32Res = inet_pton ( PF_INET , "192.168.1.3" , & stSockAddr . sin_addr ); if ( i32Res < 0 ) { perror ( "Eroare: primul parametru nu este o adresă validă" ); închidere ( i32SocketFD ); returnează EXIT_FAILURE ; } else if ( ! i32Res ) { perror ( "Eroare: al doilea parametru nu conține o adresă IP validă" ); închidere ( i32SocketFD ); returnează EXIT_FAILURE ; } if ( connect ( i32SocketFD , ( struct sockaddr * ) & stSockAddr , sizeof ( stSockAddr )) == -1 ) { perror ( "Eroare: conexiuni" ); închidere ( i32SocketFD ); returnează EXIT_FAILURE ; } /* efectuează operații de citire și scriere ... */ oprire ( i32SocketFD , SHUT_RDWR ); închidere ( i32SocketFD ); returnează 0 ; }

Un exemplu de client și server care utilizează UDP

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.

Server

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:

  • priză,
  • pointer către buffer de date,
  • dimensiunea memoriei tampon,
  • steaguri (în mod similar atunci când citiți sau alte funcții de primire a prizei),
  • structura adresei expeditorului,
  • lungimea structurii adresei expeditorului.

Client

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 ; }

Vezi și

Note

  1. Uresh Vahalia. Interne UNIX: noile frontiere. - Upper Saddle River, New Jersey 07458: Prentice Hall PTR, 2003. - 844 p. — ISBN 0-13-101908-2 .
  2. Ghidul lui Beej pentru programarea în rețea . Consultat la 12 decembrie 2008. Arhivat din original la 10 aprilie 2011.

Link -uri

Definiția „de jure” a interfeței socket conținută în standardul POSIX , mai bine cunoscută ca: