This shows you the differences between two versions of the page.
pc:laboratoare:06 [2022/04/04 00:28] vlad_andrei.badoiu [UDP] |
pc:laboratoare:06 [2022/04/13 15:06] (current) vlad_andrei.badoiu [Exercitii] |
||
---|---|---|---|
Line 59: | Line 59: | ||
Nu se garanteaza ordinea primirii mesajelor si nici prevenirea pierderilor pachetelor. UDP-ul se utilizeaza | Nu se garanteaza ordinea primirii mesajelor si nici prevenirea pierderilor pachetelor. UDP-ul se utilizeaza | ||
mai ales in retelele in care exista o pierdere foarte mica de pachete si in cadrul aplicatiilor | mai ales in retelele in care exista o pierdere foarte mica de pachete si in cadrul aplicatiilor | ||
- | pentru care pierderea unui pachet nu este foarte grava (Un exemplu: aplicatiile peer-to-peer | + | pentru care pierderea unui pachet nu este foarte grava (Un exemplu: aplicatiile streaming video). |
- | din cadrul unei retele locale). | + | |
Are un overhead foarte mic, in comparatie cu celelalte protocoale de transport (Are un header de 8 bytes, | Are un overhead foarte mic, in comparatie cu celelalte protocoale de transport (Are un header de 8 bytes, | ||
Line 100: | Line 99: | ||
==== Sockets ==== | ==== Sockets ==== | ||
- | Un socket este un canal generalizat de comunicare intre procese, reprezentat in Unix print-un descriptor de fisiere*. El ofera posibilitatea de comunicare intre procese aflate pe masini diferite intr-o retea. | + | In cadrul laboratorului nu vom implementa protocolul UDP ci vom folosi implementarea existenta din kernel-ul de Linux. Acest lucru se realizeaza prin intermediul API-ului de sockets. Un socket este un canal generalizat de comunicare intre procese, reprezentat in Unix print-un descriptor de fisiere*. El ofera posibilitatea de comunicare intre procese aflate pe masini diferite intr-o retea. |
* Un file descriptor este un handle prin care un proces comunica cu resursele sistemului. | * Un file descriptor este un handle prin care un proces comunica cu resursele sistemului. | ||
Line 107: | Line 106: | ||
- | In continuare, se vor prezenta principalele functii pentru manipularea socketilor, pe cele din figura de mai jos. | + | In continuare, se vor prezenta principalele functii pentru manipularea socketilor, |
- | {{:pc:laboratoare:sockets-udp.png?400|}} | ||
== Comunicare client-server UNIX == | == Comunicare client-server UNIX == | ||
Intr-o arhitectura client-server, clientul trimite request-uri (cere resurse) catre server, iar acesta din urma trimite inapoi un raspuns (cu resursa). | Intr-o arhitectura client-server, clientul trimite request-uri (cere resurse) catre server, iar acesta din urma trimite inapoi un raspuns (cu resursa). | ||
- | Pasi urmati pentru a schimba mesaje folosind UDP la nivelul Transport sunt urmatorii: | + | Pasi urmati pentru a schimba mesaje folosind UDP la nivelul Transport folosind API-ul de sockets sunt urmatorii: |
- //Deschide un socket// unix in scopul de a permite comunicarea intre procese/statii diferite folosind descriptori de fisiere (file descriptors) cu apelul //socket()//. | - //Deschide un socket// unix in scopul de a permite comunicarea intre procese/statii diferite folosind descriptori de fisiere (file descriptors) cu apelul //socket()//. | ||
- | - //Asociaza o adresa// pentru //socketul deschis// cu apelul //bind()//. In general, folosim //bind()// atunci cand dorim sa asteptam conexiuni pe un anumit port. | + | - //Asociaza o adresa// pentru //socketul deschis// cu apelul //bind()//. In general, folosim //bind()// atunci cand dorim sa asteptam conexiuni pe un anumit port. Bind este chemat pe server pentru a specifica la ce port sa lege socket-ul. |
- //Trimite/Receptioneaza date// cu apelul //recvfrom()/sendto()//. | - //Trimite/Receptioneaza date// cu apelul //recvfrom()/sendto()//. | ||
- //Inchide socket// prin //close()//. | - //Inchide socket// prin //close()//. | ||
- | {{:pc:laboratoare:apeluri.png?300|}} | + | {{:pc:laboratoare:apeluri.png?300|}} {{ :pc:laboratoare:sockets-udp.png?300|}} |
- | + | ||
- | Optional clientul poate face bind, daca vrea sa specifice o interfata pe care pleaca pachetele sau un port sursa, dar de cele mai multe ori acest aspect cade in sarcina sistemului de operare. | + | |
== socket() == | == socket() == | ||
Line 154: | Line 150: | ||
== bind() == | == bind() == | ||
- | + | Utilizata in server pentru lega un socket de un port si eventual de un subnet. Pentru a afla mai multe informatii, putem accesa [[https://beej.us/guide/bgnet/html/#bind|5.3 bind()—What port am I on?]]. | |
- | Pentru a afla mai multe informatii, putem accesa [[https://beej.us/guide/bgnet/html/#bind|5.3 bind()—What port am I on?]]. | + | |
<code C> | <code C> | ||
Line 162: | Line 157: | ||
/*int bind(int sockfd, struct sockaddr *my_addr, int addrlen)*/ | /*int bind(int sockfd, struct sockaddr *my_addr, int addrlen)*/ | ||
- | + | ||
- | int rs = bind(sockfd, my_addr, sizeof(my_addr); | + | struct sockaddr myaddr; |
+ | memset(&myaddr, 0, sizeof(servaddr)); | ||
+ | myaddr.sin_family = AF_INET; // IPv4 | ||
+ | /* INADDR_ANY = 0.0.0.0 as uint32 */ | ||
+ | myaddr.sin_addr.s_addr = INADDR_ANY; | ||
+ | myaddr.sin_port = htons(atoi(8888)); | ||
+ | |||
+ | int rs = bind(sockfd, myaddr, sizeof(servaddr); | ||
/* in urma apelului, sockfd va avea adresa my_addr */ | /* in urma apelului, sockfd va avea adresa my_addr */ | ||
if (rs == -1) { | if (rs == -1) { | ||
Line 171: | Line 173: | ||
Explicatii: | Explicatii: | ||
- | * sockfd - Descriptorul de fisier returnat de socket() | + | * sockfd - Descriptorul de fisier returnat de socket() |
- | * my_addr - Structura sockaddr ce contine informatii despre adresa IP si port | + | * myaddr - Structura sockaddr ce contine informatii despre adresa IP si port |
- | * addrlen - lungimea lui my_addr | + | * addrlen - lungimea lui my_addr |
- | Observam ca apelul bind utilizeaza o variabila de tip **struct sockaddr**. | + | Observam ca apelul bind utilizeaza o variabila de tip **struct sockaddr_in**. |
- | + | ||
- | <code C> | + | |
- | #include <sys/socket.h> | + | |
- | + | ||
- | /* tip de date generic pentru a retine informatii despre adresa socketilor */ | + | |
- | struct sockaddr { | + | |
- | unsigned char sa_family; | + | |
- | char sa_data[14]; | + | |
- | } | + | |
- | </code> | + | |
- | + | ||
- | Explicatii: | + | |
- | * sa_family - indica formatul particular de adresa: AF_INET (protocolul IPv4), AF_INET6 (protocol IPv6) | + | |
- | * sa_data - adresa socketului | + | |
== struct sockaddr_in == | == struct sockaddr_in == | ||
Line 208: | Line 196: | ||
Explicatii: | Explicatii: | ||
- | * sin_family - indica formatul particular de adresa: AF_INET (protocolul IPv4), AF_INET6 (protocol IPv6) | + | * sin_family - indica formatul particular de adresa: AF_INET (protocolul IPv4), AF_INET6 (protocol IPv6) |
- | * sin_port - portul utilizat in NETWORK BYTE ORDER | + | * sin_port - portul utilizat in NETWORK BYTE ORDER |
- | * sin_addr - adresa IP in NETWORK BYTE ORDER, in format in_addr mentionat mai jos | + | * sin_addr - adresa IP in NETWORK BYTE ORDER, in format in_addr mentionat mai jos |
== recvfrom()/ sendto() == | == recvfrom()/ sendto() == | ||
+ | |||
+ | Functile sunt folosite pentru a primi/trimite o datagrama peste un socket. Mai multe detalii gasiti [[https://beej.us/guide/bgnet/html/#sendtorecv|aici]]. | ||
<code C> | <code C> | ||
#include <sys/types.h> | #include <sys/types.h> | ||
#include <sys/socket.h> | #include <sys/socket.h> | ||
+ | |||
+ | struct sockaddr to; | ||
+ | // Filling server information | ||
+ | memset(&to, 0, sizeof(servaddr)); | ||
+ | to.sin_family = AF_INET; | ||
+ | to.sin_port = htons(8888); | ||
+ | int rc = inet_aton("127.0.0.1", &to.sin_addr); | ||
+ | |||
int byteswrite = sendto(int sockfd, char *buff, int nbytes, int flags, struct sockaddr *to, int addrlen); | int byteswrite = sendto(int sockfd, char *buff, int nbytes, int flags, struct sockaddr *to, int addrlen); | ||
Line 222: | Line 220: | ||
/* trateaza eroare */ | /* trateaza eroare */ | ||
} | } | ||
- | + | ||
+ | /* from va fi populata de apelul recvfrom si va contine informatii despre cine a trimis datagrama catre noi */ | ||
+ | struct sockaddr from; | ||
int bytesread = recvfrom(int sockfd, char *buff, int nbytes, int flags, struct sockaddr *from, int *addrlen); | int bytesread = recvfrom(int sockfd, char *buff, int nbytes, int flags, struct sockaddr *from, int *addrlen); | ||
if (bytesread == -1) { | if (bytesread == -1) { | ||
Line 260: | Line 260: | ||
Explicatii: | Explicatii: | ||
- | * sockfd - Descriptorul de fisier returnat de socket() | + | * sockfd - Descriptorul de fisier returnat de socket() |
- | + | * how - Specifica modul de inchidere: 0 - Nu se mai citesc date. 1 - Nu se mai pot face transmiteri de date. 2 - Se intrerupe comunicatia in ambele directii. | |
- | * how - Specifica modul de inchidere: 0 - Nu se mai citesc date. 1 - Nu se mai pot face transmiteri de date. 2 - Se intrerupe comunicatia in ambele directii. | + | |
<note> Shutdown() nu inchide un descriptor de fisier, ci doar ii schimba modul de utilizare. Resursele trebuie eliberate folosind close() la final. </note> | <note> Shutdown() nu inchide un descriptor de fisier, ci doar ii schimba modul de utilizare. Resursele trebuie eliberate folosind close() la final. </note> | ||
Line 312: | Line 311: | ||
**[Task-ul 1]:** | **[Task-ul 1]:** | ||
- | Plecând de la scheletul de cod oferit, scrieți o pereche de programe (client și server) care să permită transferul unui fișier binar de la server la client. | + | Plecând de la scheletul de cod oferit, scrieți o pereche de programe (client și server) care să permită transferul unui fișier binar de la client la server. |
<note warning> | <note warning> | ||
Line 327: | Line 326: | ||
Specificații: | Specificații: | ||
- Clientul trimite un fisier binar prin UDP, iar serverul il va receptiona datele si le va scrie in fisierul lui. | - Clientul trimite un fisier binar prin UDP, iar serverul il va receptiona datele si le va scrie in fisierul lui. | ||
- | - Fișierul trimis de client se va numi **fisier.bin**, iar fișierul salvat de client se va numi **received_file.bin**. | + | - Fișierul trimis de client se va numi **fisier.bin**, iar fișierul salvat de server se va numi **received_file.bin**. |
- Clientul va transmite fișierul în calupuri de câte 1024 octeți. | - Clientul va transmite fișierul în calupuri de câte 1024 octeți. | ||
+ | |||
+ | La final, vom studia folosind **Wireshark** datagramele trimise de catre client. | ||
+ | |||
+ | Mai mult, daca sunteti in aceasi retea (e.g. pe acelasi WiFi) va puteti **grupa cate doi** pentru a trimite un fisier de la unu la celalalt. Mai exact, unul dintre voi va porni un server cu bind pe INADDR_ANY (0.0.0.0), iar clientul va putea trimite catre ip-ul celuilalt un fisierul. Felicitari, ati facut o **aplicatie simpla de transfer de fisiere**. | ||
**[Task-ul 2]:** | **[Task-ul 2]:** | ||
Dacă ați rulat task-ul 1 cu ambele componente pe aceeași mașină, probabil totul a mers bine, însă o soluție naivă (care trimite datagrame doar de la sender la receiver), ascunde 2 probleme: | Dacă ați rulat task-ul 1 cu ambele componente pe aceeași mașină, probabil totul a mers bine, însă o soluție naivă (care trimite datagrame doar de la sender la receiver), ascunde 2 probleme: | ||
- | - Dacă transmițătorul este mai rapid decât receptorul, acesta din urma poate fi inundat și să nu reușească să le proceseze la timp; | + | - Dacă transmițătorul este mai rapid decât receptorul, acesta din urma poate fi inundat și să nu reușească să proceseze la timp datagramele; |
- Două situații foarte posibile în cazul comunicării către mașini diferite (**pierderea unei datagrame** sau **inversarea ordinii datagramelor**) sunt netratate. | - Două situații foarte posibile în cazul comunicării către mașini diferite (**pierderea unei datagrame** sau **inversarea ordinii datagramelor**) sunt netratate. | ||
Line 340: | Line 343: | ||
<note warning> | <note warning> | ||
- | Acest exercitiu va fi rulat doar pe mininet. Lansati-l doar folosind comanda: <code>sudo python3 topology.py</code> | + | Acest exercitiu va fi rulat doar pe mininet. Lansați-l doar folosind comanda: <code>sudo python3 topology.py</code> |
</note> | </note> | ||
Line 398: | Line 401: | ||
<note> | <note> | ||
Observatie: Cateodata, cand se incearca sa se reporneasca un _server_, bind() nu reuseste sa asigneze, iar eroarea este "Address already in use". Asta inseamna ca un socket care a fost conectat pe acel port inca mai este agatat si //utilizeaza portul//. In aceasta situatie, fie se poate astepta deconectarea portului respectiv, fie se specifica programatic reutilizarea portului cu [[https://linux.die.net/man/3/setsockopt|setsockopt()]] | Observatie: Cateodata, cand se incearca sa se reporneasca un _server_, bind() nu reuseste sa asigneze, iar eroarea este "Address already in use". Asta inseamna ca un socket care a fost conectat pe acel port inca mai este agatat si //utilizeaza portul//. In aceasta situatie, fie se poate astepta deconectarea portului respectiv, fie se specifica programatic reutilizarea portului cu [[https://linux.die.net/man/3/setsockopt|setsockopt()]] | ||
+ | |||
+ | <code C> | ||
+ | int enable = 1; | ||
+ | if (setsockopt(sockfd, SOL_SOCKET, SO_REUSEADDR, &enable, sizeof(int)) < 0) | ||
+ | perror("setsockopt(SO_REUSEADDR) failed"); | ||
+ | </code> | ||
+ | |||
+ | </note> | ||
+ | |||
+ | <note> | ||
+ | O posibila solutie a laboratorului se gaseste [[https://ocw.cs.pub.ro/courses/_media/pc/laboratoare/lab06-rezolvare.zip|aici]] | ||
</note> | </note> |