Programarea prizei este o modalitate de a conecta două noduri dintr-o rețea pentru a comunica între ele. Un socket (nod) ascultă pe un anumit port la un IP, în timp ce celălalt soclu ajunge la celălalt pentru a forma o conexiune. Serverul formează soclul de ascultător în timp ce clientul ajunge la server.
Programarea prin socket este utilizată pe scară largă în aplicațiile de mesagerie instantanee în flux binar și colaborări de documente, platforme de streaming online etc.
Exemplu
În acest program C schimbăm un mesaj de salut între server și client pentru a demonstra modelul client/server.
server.c
C#include #include #include #include #include #include #define PORT 8080 int main(int argc char const* argv[]) { int server_fd new_socket; ssize_t valread; struct sockaddr_in address; int opt = 1; socklen_t addrlen = sizeof(address); char buffer[1024] = { 0 }; char* hello = 'Hello from server'; // Creating socket file descriptor if ((server_fd = socket(AF_INET SOCK_STREAM 0)) < 0) { perror('socket failed'); exit(EXIT_FAILURE); } // Forcefully attaching socket to the port 8080 if (setsockopt(server_fd SOL_SOCKET SO_REUSEADDR | SO_REUSEPORT &opt sizeof(opt))) { perror('setsockopt'); exit(EXIT_FAILURE); } address.sin_family = AF_INET; address.sin_addr.s_addr = INADDR_ANY; address.sin_port = htons(PORT); // Forcefully attaching socket to the port 8080 if (bind(server_fd (struct sockaddr*)&address sizeof(address)) < 0) { perror('bind failed'); exit(EXIT_FAILURE); } if (listen(server_fd 3) < 0) { perror('listen'); exit(EXIT_FAILURE); } if ((new_socket = accept(server_fd (struct sockaddr*)&address &addrlen)) < 0) { perror('accept'); exit(EXIT_FAILURE); } // subtract 1 for the null // terminator at the end valread = read(new_socket buffer 1024 - 1); printf('%sn' buffer); send(new_socket hello strlen(hello) 0); printf('Hello message sentn'); // closing the connected socket close(new_socket); // closing the listening socket close(server_fd); return 0; }
client.c
C#include #include #include #include #include #define PORT 8080 int main(int argc char const* argv[]) { int status valread client_fd; struct sockaddr_in serv_addr; char* hello = 'Hello from client'; char buffer[1024] = { 0 }; if ((client_fd = socket(AF_INET SOCK_STREAM 0)) < 0) { printf('n Socket creation error n'); return -1; } serv_addr.sin_family = AF_INET; serv_addr.sin_port = htons(PORT); // Convert IPv4 and IPv6 addresses from text to binary // form if (inet_pton(AF_INET '127.0.0.1' &serv_addr.sin_addr) <= 0) { printf( 'nInvalid address/ Address not supported n'); return -1; } if ((status = connect(client_fd (struct sockaddr*)&serv_addr sizeof(serv_addr))) < 0) { printf('nConnection Failed n'); return -1; } // subtract 1 for the null // terminator at the end send(client_fd hello strlen(hello) 0); printf('Hello message sentn'); valread = read(client_fd buffer 1024 - 1); printf('%sn' buffer); // closing the connected socket close(client_fd); return 0; }
Compilarea
gcc client.c -o clientgcc server.c -o server
Ieșire
Client:Hello message sentHello from serverServer:Hello from clientHello message sentComponentele programării socketului
1. Prize
Prize sunt una dintre componentele de bază utilizate de program pentru a accesa rețeaua pentru a comunica cu alte procese/noduri prin rețea. Este pur și simplu o combinație între o adresă IP și un număr de port care acționează ca punct final pentru comunicare.
Exemplu: 192.168.1.1:8080 unde cele două părți separate de două puncte reprezintă Adresă IP (192.168.1.1) iar cel numărul portului (8080).
Tipuri de prize:
- Socket TCP (Socket de flux): Oferă o comunicare fiabilă bazată pe conexiune (de ex. Protocolul TCP ).
- Socket UDP (Socket Datagramă): Oferă o comunicare fără conexiune mai rapidă, dar nesigură (de ex. Protocolul UDP ).
2. Model Client-Server
The model client-server se referă la arhitectura utilizată în programarea socket-ului în care un client și un server interacționează unul cu celălalt pentru a schimba informații sau servicii. Această arhitectură permite clientului să trimită cereri de servicii și serverului să proceseze și să trimită răspuns la acele solicitări de servicii.
Diagrama de stare pentru modelul server și client
Diagrama de stare pentru modelul de server și client al SocketProgramarea socketului în C este o modalitate puternică de a gestiona comunicarea în rețea.
Crearea unui proces pe partea de server
Serverul este creat folosind următorii pași:
java int la char
1. Crearea prizei
Acest pas implică crearea socket-ului folosind funcția socket().
Parametri:
- sockfd: descriptor de socket un număr întreg (ca un mâner de fișier)
- domeniu: întreg specifică domeniul de comunicare. Folosim AF_LOCAL așa cum este definit în standardul POSIX pentru comunicarea între procesele de pe aceeași gazdă. Pentru comunicarea între procese pe diferite gazde conectate prin IPV4 folosim AF_INET și AF_I NET 6 pentru procesele conectate prin IPV6.
- tip: tipul de comunicare
SOCK_STREAM: TCP (orientat pe conexiune de încredere)
SOCK_DGRAM: UDP (nefiabil fără conexiune) - protocol: Valoarea protocolului pentru Internet Protocol(IP), care este 0. Acesta este același număr care apare în câmpul de protocol din antetul IP al unui pachet. (man protocoale pentru mai multe detalii)
sockfd = socket(domain type protocol)
2. Setați socket opt
Acest lucru ajută la manipularea opțiunilor pentru socket la care se face referire de descriptorul de fișier sockfd. Acest lucru este complet opțional, dar ajută la reutilizarea adresei și a portului. Previne erori precum: adresa deja utilizată.
Csetsockopt(sockfd level optname optval socklen_t optlen);
3. Leagă
După crearea socket-ului, funcția bind() leagă socket-ul la adresa și numărul portului specificate în addr (structură de date personalizată). În codul exemplu, legăm serverul la gazda locală, prin urmare folosim INADDR_ANY pentru a specifica adresa IP.
C++bind(sockfd sockaddr *addr socklen_t addrlen);
Parametri:
- sockfd : descriptor de fișier socket creat folosind funcția socket().
- adresă : pointer către o struct sockaddr care conține adresa IP și numărul portului pentru a lega socket-ul.
- adresa : lungimea structurii addr.
4. Ascultă
În acest pas, serverul folosește funcția listen() care pune socket-ul serverului într-un mod pasiv unde așteaptă ca clientul să se apropie de server pentru a face o conexiune. Backlog-ul definește lungimea maximă la care poate crește coada de conexiuni în așteptare pentru sockfd. Dacă o solicitare de conectare sosește când coada este plină, clientul poate primi o eroare cu indicația ECONNREFUSED.
Clisten(sockfd backlog);
Parametrii :
- sockfd : descriptor de fișier socket creat folosind funcția socket().
- restante : număr care reprezintă dimensiunea cozii care deține conexiunile în așteptare în timp ce serverul așteaptă să accepte o conexiune.
5. Acceptați
În acest pas, serverul extrage prima cerere de conexiune din coada de conexiuni în așteptare pentru soclul de ascultare sockfd creează un nou soclu conectat folosind accepta() funcția și returnează un nou descriptor de fișier care se referă la acel socket. În acest moment se stabilește conexiunea între client și server și sunt gata să transfere date.
Cnew_socket= accept(sockfd sockaddr *addr socklen_t *addrlen);
Parametri:
- sockfd : descriptor de fișier socket returnat de socket() și bind().
- adresă : pointer către o struct sockaddr care va păstra adresa IP și numărul de port al clientului.
- adresa : pointer către o variabilă care specifică lungimea structurii adresei.
6. Trimitere/Primire
În acest pas, serverul poate trimite sau primi date de la client.
Trimite(): pentru a trimite date către client
Csend(sockfd *buf len flags);
Parametri:
- sockfd : descriptor de fișier socket returnat de funcția socket().
- buf : pointer către bufferul care conține datele de trimis.
- numai : numărul de octeți de date care trebuie trimiși.
- steaguri : întreg care specifică diferite opțiuni pentru modul în care sunt trimise datele, de obicei, 0 este utilizat pentru comportamentul implicit.
Primire(): pentru a primi datele de la client.
Crecv( sockfd *buf len flags);
Parametri:
- sockfd : descriptor de fișier socket returnat de funcția socket().
- buf : pointer către buffer-ul care conține datele de stocat.
- numai : numărul de octeți de date care trebuie trimiși.
- steaguri : întreg care specifică diferite opțiuni pentru modul în care sunt trimise datele, de obicei, 0 este utilizat pentru comportamentul implicit.
6. Închideți
După ce schimbul de informații este complet, serverul închide socket-ul folosind funcția close() și eliberează resursele sistemului.
Cclose(fd);
Parametri:
- fd: descriptor de fișier al soclului.
Crearea procesului la nivelul clientului
Urmați pașii de mai jos pentru a crea un proces la nivelul clientului:
1. Conexiune priză
Acest pas implică crearea socket-ului care se face în același mod ca cel al creării socket-ului serverului
2. Conectați-vă
Apelul de sistem connect() conectează socket-ul la care se face referire de descriptorul de fișier sockfd la adresa specificată de addr. Adresa și portul serverului sunt specificate în addr.
C++connect(sockfd sockaddr *addr socklen_t addrlen);
Parametrii
- sockfd : descriptor de fișier socket returnat de funcția socket().
- adresă : pointer către struct sockaddr care conține adresa IP și numărul portului serverului.
- adresa : dimensiunea adresei.
3. Trimitere/Primire
În acest pas, clientul poate trimite sau primi date de la server, ceea ce se face folosind funcțiile send() și recieve() similar cu modul în care serverul trimite/primește date de la client.
4. Închideți
Odată ce schimbul de informații este complet, clientul trebuie să închidă socket-ul creat și să elibereze resursele de sistem folosind funcția close() în același mod ca și serverul.
Probleme obișnuite și soluțiile lor în programarea socketului
- Eșecuri de conectare: Pentru a evita erorile de conectare, ar trebui să ne asigurăm că clientul încearcă să se conecteze la cel corect adresa IP și portul .
- Erori de legare la port: Aceste erori apar atunci când un port este deja utilizat de o altă aplicație în acest scenariu, legarea la acel port va eșua. Încercați să utilizați un alt port sau închideți aplicația anterioară folosind portul.
- Prize de blocare: În mod implicit, prizele se blochează. Aceasta înseamnă că apelurile precum accept() sau recv() vor aștepta nelimitat dacă nu există nicio conexiune la client sau date. Puteți seta priza în modul non-blocare dacă este necesar.