Programmazione delle socket

Una applicazione distribuita in rete permette a delle applicazioni situate su diversi computer, ma entrambi connessi ad internet, di comunicare.
Ci sono due approcci per creare applicazioni distribuite in rete: il primo è quello di utilizzare gli standard del RFC, in tal modo anche se gli sviluppatori delle applicazioni create sono diversi, avendo eseguito i passi standard per la programmazione delle socket, i programmi sono comunque in grado di interagire. L'altro approccio è sviluppare l'app senza tenere conto delle regole del RFC, in questo caso il programmatore deve sviluppare il client e il server in modo che essi possano comunicare tra di loro. Altri sviluppatori indipendenti non posso fare interagire le loro app con quella, poiché essa non implementa un protocollo di pubblico dominio.

Per lo sviluppo di questa semplice app distribuita utilizzeremo python.
Faremo gli esempi con entrambi i protocolli del livello di trasporto, in questa fase utilizzeremo UDP.
Come abbiamo già detto, le applicazioni sono su sistemi diversi che comunicano attraverso le socket ed è come se fossero delle case, la cui porta rappresenta la socket. Dal lato interno della porta viene gestito il lato applicativo, dal lato esterno troviamo il protocollo di trasporto, che non possiamo programmare. L’app che invia il pacchetto UDP deve attaccare (attach) l’indirizzo cui è diretto il pacchetto al pacchetto stesso. In questo modo internet potrà instradare il pacchetto verso la destinazione. Sulla macchina che riceve il pacchetto possono essere in esecuzione più processi, bisogna indirizzare il pacchetto verso la socket del processo ricevente, per questo si specifica un numero di porta. Il lavoro che svolgerà la nostra app è il seguente:

  • Il client legge una riga di caratteri dalla tastiera e la invia tramite la socket al server
  • Il server riceve i dati e converte i caratteri in maiuscole
  • Il server invia i caratteri modificati
  • Il client li riceve e li mostra sullo schermo
    Il processo client si chiama udpclient.py
    La porta su cui operano le socket è 12000 (sia per il server che per il client, è arbitraria, l'abbiamo scelta noi).

Codice per il client (eseguito con la versione 3 di python):
...

UDP
...
Client UDP
...
from socket import * # importate libreria per le socket

serverName = 'localhost'
serverPort = 12000

clientSocket = socket(AF_INET, SOCK_DGRAM)
# creazione del socket lato client, memorizzandone il riferimento in una variabile;
# AF_INET indica il tipo di dati trattati (indica la famiglia di indirizzi IPv4);
# SOCK_DGRAM indica che la socket è di tipo UDP;

message = input('Frase da convertire in maiuscolo:') # prende input da tastiera
clientSocket.sendto(message.encode(), (serverName, serverPort))
# invia il messaggio nella socket creata sopra;
# il messaggio viene prima convertito in byte (message.encode()) per poterla
# inviare nella socket;
# la funzione sendto attacca serverName e serverPort al messaggio converito in byte;
# viene attaccato anche l'IP del mittente, ma questo viene fatto in automatico.

modifiedMessage, serverAddress = clientSocket.recvfrom(2048)
# la funzione recvfrom(2048) chiamata sulla socket, fa in modo di ricevere i messaggi
# inviati da qualche server su quella socket (ascolta per dei pacchetti in arrivo);
# i valori di ritorno di questa funzione (dati contenuti nella socket e mittente)
# vengono piazzati nelle variabili modifiedMessage e serverAddress;
# 2048 è la grandezza del buffer.

print(modifiedMessage.decode())
# stampa il messaggio ricevuto (modificato dal 'server')

clientSocket.close()
Server UDP
...
from socket import * # importate libreria per le socket

serverPort = 12000
serverSocket = socket(AF_INET, SOCK_DGRAM)

serverSocket.bind(('', serverPort))
# associa alla socket il numero di porta serverPort;
# nel server il numero di porta viene esplicitamente collegato alla socket;

print('Ascolto nella socket per pacchetti in arrivato da altri client')

while 1:
	message, clientAddress = serverSocket.recvfrom(2048)
	# con recvfrom riceve i messaggi nel buffer di dimensione 2048
	# e li piazza nelle variabili message e clientAddress
	print('Ricevuto un pacchetto')
	print(message)
	printf('Lo converto in maiuscolo')
	modifiedMessage = message.decode().upper()
	# il messaggio viene decodificato, gli viene chiamata la funzione upper sopra
	# per renderlo maiuscolo;
	# il tutto viene salvato in modifiedMessage
	
	serverSocket.sendto(modifiedMessage.encode(), clientAddress)
	# il messaggio modificato viene convertito in byte e viene passato a sendto
	# l'IP del client

TCP
...

TCP è un protocollo orientato alla connessione rispetto a UDP. Ciò vuol dire che prima di iniziare a scambiarsi i dati, client e server devono "presentarsi", attraverso un'operazione che viene detta three-way handshake (handshake = "stretta di mano"). La connessione TCP, dopo l'handshake, risulterà attaccata ai due estremi con i rispettivi host della comunicazione: client e server. Una connessione TCP è identificata dall'indirizzo del client (formato da IP e numero di porta) e l'indirizzo del server (IP e numero di porta).
A questo punto quando un lato vuole inviare dei dati all'altro deve solo mettere i dati nella connessione TCP attraverso la sua socket. In UDP il server doveva attaccare l'indirizzo del client a cui voleva inviare i dati.

Client TCP
...
from socket import * # importate libreria per le socket

serverName = 'localhost'
serverPort = 12000

clientSocket = socket(AF_INET, SOCK_STREAM)
# SOCK_STREAM indica che la socket è di tipo TCP

clientSocket.connect((serverName, serverPort))
# il client si connette con il server specificato (handshake)

message = input('Frase da convertire in maiuscolo:') # prende input da tastiera
clientSocket.send(message.encode())
# non è necessario specificare a chi, poiché il client è già connesso con il server

modifiedMessage = clientSocket.recv(1024)
# è noto da chi si riceve il messaggio (poiché la connessione è stata stabilita)
# quindi non server prelevare dal pacchetto l'indirizzo del server

print(modifiedMessage.decode())
# stampa il messaggio ricevuto (modificato dal 'server')

clientSocket.close()
Server TCP
...
from socket import * # importate libreria per le socket

serverPort = 12000
serverSocket = socket(AF_INET, SOCK_STREAM)
serverSocket.bind(('', serverPort))
serverSocket.listen(1) # la socket si mette in ascolto

print('Ascolto nella socket per pacchetti in arrivato da altri client')

while 1:
	connectionSocket, addr = serverSocket.accept()
	# la funzione accept() blocca il corpo del while su quella riga, fino a che
	# non giunge un pacchetto al server (la funzione è bloccante);
	# quando in client contatta il server viene creata una connessione
	# connectionSocket dedicata all'host che l'ha contattato (handshake);
	# l'handshake avviene su serverSocket
	
	print('Ricevuto un pacchetto')
	print(message)
	printf('Lo converto in maiuscolo')
	modifiedMessage = message.decode().upper()
	serverSocket.send(modifiedMessage.encode())
	# non serve specificare a chi viene inviato il pacchetto