This is an old revision of the document!
Pentru a putea discuta despre comunicarea într-o rețea, este important să cunoaștem următoarele noțiuni:
Adresa fizică, numită și adresa MAC este un număr pe 48 de biți care identifică unic o placă de rețea. Acest număr este inscripționat în placa de rețea de către producător, deci, în general nu poate fi schimbat. Adresa MAC este folosită pentru transmisia de date la nivelul de access la rețea din stiva TCP/IP.
O adresă MAC este reprezentată prin gruparea celor 48 de biți în câte 6 octeți și scrierea fiecărui octet in baza 16 (ex: 00:A0:C9:14:C8:29 sau 00A0-C914-C829).
O adresă IP identifică o stație într-o rețea și se folosește pentru transmisia de date la nivelul rețea din stiva TCP/IP.
Practic, IP-ul unei stații este un număr, pe 32 de biți în cazul protocolului IPv4 sau pe 128 de biți în cazul protocolului IPv6. Uzual, adresele IP sunt scrise sub forma restransă. În cazul IPv4, adresa IP este scrisă sub formă de 4 numere în baza zecimală, cu valori între 0 și 255, separate prin . (ex: 192.168.0.14), iar în cazul IPv6, adresa IP este scrisă sub formă de 8 grupuri numere în baza hexazecimală, cu valori cuprinse între 0000 și ffff, separate prin : (ex: 2001:0db8:85a3:0000:0000:8a2e:0370:7334).
Portul este un identificator folosit pentru transmisia de date la nivelul aplicație.
Pentru a asigura că două aplicații comunică prin rețea trebuie să specificăm adresa IP a stațiilor care comunică, dar și portul. În timp ce adresa IP asigură că pachetele ajung la destinație, portul asigură că pachetul primit este folosit de aplicația potrivită.
De exemplu, două calculatoare pot să comunice atât pentru a face schimb de email-uri, dar și pentru a realiza o video-conferință. Astfel, cele două calculatoare fac schimb pe mesaje pentru ambele aplicații (email și video-conferință). În timp ce adresele IP sunt folosite pentru a ne asigura că toate pachetele ajung unde trebuie, aplicațiile de email și de video-conferintă vor avea atribuit câte un port diferit, pe baza căruia mesajele vor fi distribuite aplicației potrivite.
Pentru a putea realiza transmisia de mesaje pe rețea, vom folosit structuri de tip socket.
Un socket permite transmisia de mesaje între aplicații de pe aceeasi mașină sau de pe mașini fizice diferite, într-o manieră similară cu cea folosind file descriptori.
În Python, vom folosi modulul socket pentru operațiile privind comunicarea prin rețea. Printre operațiile pe care le putem realiza folosind modulul socket
se numără:
Funcția gethostname returnează adresa IPv4 a stației locale.
socket.gethostname()
Funcția getaddrinfo întoarce o listă cu 5 elemente care conțin informații despre o adresă și un port specificat. Lista returnată conține următoarele informații: (family, type, proto, canonname, sockaddr)
, iar sockaddr
e o altă listă ce conține (address, port)
.
socket.getaddrinfo(host, port, family=0, type=0, proto=0, flags=0)
Funcția gethostbyname returnează adresa IPv4 a unui hostname
.
socket.gethostbyname(hostname)
Folosind sockeți putem realiza două tipuri de transmisii de date: UDP sau TCP.
Pentru oricare din cele două tipuri de transmisii, trebuie să creăm un obiect nou de tip socket
folosind funcția socket (family=AF_INET, type=SOCK_STREAM, proto=0, fileno=None).
În funcție de parametrii pasați constructorului, socket-ul este configurat pentru un anumit tip de transfer de date.
AF_INET
, pentru IPv6 tipul este AF_INET6
); în cazul nostru, vom folosi AF_INET (socket.AF_INET, în Python);SOCK_STREAM
(pentru transferul TCP) și SOCK_DGRAM
(pentru transferul UDP);file descriptor
, iar dacă acest parametru este pasat, toate celelalte 3 valori sunt stabilite automat pe baza acestuia.import socket s = socket.socket()
Pentru transferul de tip TCP, se folosește paradigma client-server, în care un proces așteaptă conexiuni (server), în timp ce alte procese se pot conecta la acesta (client). Folosind TCP datele transmise ajung cu siguranță la destinație sau se primește o eroare.
Pentru a porni un server folosind modului socket
, trebuie să efectăm următoarele operații:
socket
, care va fi folosită pentru transmisia de mesaje; adresa este adresa programului care s-a conectat.
bind
este 0.0.0.0
, socket-ul acceptă conexiuni de pe toate plăcile de rețea.
import socket s = socket.socket() s.bind(('0.0.0.0', 8000)) s.listen(0) conn, addr = serv.accept()
Pentru a iniția o conexiune din partea serverului, folosind modului socket
, trebuie să apelăm funcția connect.
Pentru a încheia conexiunea cu serverul, vom folosi funcția close.
import socket s = socket.socket() s.connect(('localhost', 8000)) # send/receive data s.close()
localhost
sau 127.0.0.1
.
Pentru a trimite date folosind modulul socket
putem folosi funcțiile send și sendto.
socket.send(bytes[, flags]) socket.sendto(bytes, address) socket.sendto(bytes, flags, address)
Funcția send
trimite date doar dacă socket-ul este conectat la un alt socket, deci poate fi folosită doar pentru o transmisie de tip TCP.
Funcția sendto
trimite date la o adresă pasată ca parametru, fără a necesita o conexiune stabilită între cele două obiecte socket, deci poate fi folosită pentru o transmisie de tip UDP.
address
este un tupul de tipul (adress, port), ex: (“0.0.0.0”, 8000).
Pentru a primi date folosind modulul socket
putem folosi funcțiile recv și recvfrom.
socket.recv(bufsize[, flags]) socket.recvfrom(bufsize[, flags])
Funcția recv
citește date până la dimensiunea maximă, specificată ca parametru (bufzise
) și le returnează sub forma unui obiect de tip bytes
. Această funcție este potrivită pentru transmiterea de tip TCP, unde avem o conexiune constantă cu un alt socket.
Funcția recvfrom
citește date similar cu funcția recv
, dar întoarce două obiecte: mesajul și adresa sursei. Astfel, putem folosi adresa returnată pentru a trimite mesaje înapoi. Aceasta funcție este potrivită pentru transmiterea de tip UDP, unde nu avem o conexiune cu un alt socket.
address
returnat este un tupul de tipul (adress, port), ex: (“0.0.0.0”, 8000).
În aceste exemple vom crea un proces care așteaptă continuu mesaje si răspunde la acestea (server) și un alt proces care trimite un mesaj și asteaptă un răspuns (client).
import socket buffersize = 2048 # create socket s = socket.socket(family=socket.AF_INET, type=socket.SOCK_DGRAM) # bind socket to address and port s.bind (("0.0.0.0", 8000)) # listen for data forever and reply with "Message received" while True: data, addr = s.recvfrom(buffersize) print ("Data from {} is {}".format (addr, data)) msg = str.encode ("Message received") s.sendto (msg, addr)
import socket buffersize = 2048 # create socket s = socket.socket(family=socket.AF_INET, type=socket.SOCK_DGRAM) # create bytes object msg = str.encode("Hello") # send message s.sendto (msg, ("localhost", 8000)) # read message data, addr = s.recvfrom (buffersize) print ("Data from {} is {}".format (addr, data))
import socket buffersize = 2048 # create socket s = socket.socket (family=socket.AF_INET, type=socket.SOCK_STREAM) # bind socket to address and port s.bind (("0.0.0.0", 8000)) # wait for connections s.listen (0) # accept connections forever while True: conn, addr = s.accept () print ("Connected to {}".format (addr)) # listen for data and reply with "Message received" while True: data = conn.recv(buffersize) # client finished sending message if not data: break print ("Received {}".format (data)) msg = str.encode ("Message received") conn.send (msg) # close connection conn.close ()
import socket buffersize = 2048 # create socket s = socket.socket(family=socket.AF_INET, type=socket.SOCK_STREAM) # connect to server s.connect (("localhost", 8000)) # create bytes object msg = str.encode("Hello") # send message s.send (msg) # read message data, addr = s.recv (buffersize) # close connection s.close () print ("Data from {} is {}".format (addr, data))
În rezolvarea laboratorului folosiți repository-ul de github. Pentru a descărca repository-ul, rulati comanda git clone https://github.com/UPB-FILS/sde.git in terminal.
google.com
.Netcat este un utilitar în linie de comandă care permite efectuarea de operații pe rețea. În acest exercițiu îl vom folosi pentru a schimba mesaje cu programele python din exemplele de mai sus.
netcat -z -v localhost 8000
. Observați răspunsul primit.echo test | netcat -u localhost 8000
. Observați comportamentul rezultat (opțiunea -u specifică modul de transmisie UDP). netcat
.
Creați două programe care comunică în mod UDP prin socket astfel încât unul din programe să primească trei numere separate prin ;
și să răspundă cu media aritmetică a acestora.
Simulați un server TCP care primește comenzi bash și răspunde cu rezultatul acestora. Creați un client care trimite comenzi și afișează rezultatul primit de la server. Hint: folositi un pipe pentru a stoca output-ul comenzii lansate.
Modificați serverul creat la punctul anterior pentru a suporta mai multe conexiuni simultane. Folosiți threaduri în implementare. Hint: programul principal acceptă conexiunile, iar odată stabilită conexiunea, porniți un nou thread care primește ca parametru conexiunea.