Il Modulo Socket - Introduzione

Il questa lezione parleremo dei socket! Considerando la vastità dell'argomento in questione, questo e gli altri miei tutorial sui socket non pretendono di essere una guida esaustiva al riguardo, ma cercano di fornire una gentile introduzione all'applicazione di questa tecnologia nel mondo di Python, in modo che possiate iniziare ad integrarli nel vostro codice. Per maggiori informazioni potete dare uno sguardo alla documentazione ufficiale e a questo link.

Utilizzeremo il modulo Socket della standard library. Iniziamo!

>>> import socket

Che cos'è un socket? Partiamo da un esempio.

Quando dal vostro browser digitate www.google.it, il browser sta creando un socket: con esso si connette al server Google che sta in attesa sulla porta 80 riservata per le connessioni HTTP, e quando la connessione al server è finalmente stabilita, allora il socket può essere usato per effettuare la richiesta della pagina presente all'indirizzo che avete digitato.

Quindi, i socket sono delle astrazioni software che rappresentano le estremità di questo canale di comunicazione virtuale tra dispositivi, normalmente server e client.


Come creare un oggetto socket

Per creare un socket utilizziamo la funzione socket() inclusa nel modulo socket, che ha la seguente sintassi:

# creazione oggetto socket
>>> s = socket.socket(socket_family, socket_type)

Tra i parametri passabili alla funzione, a socket_family corrisponde la tipologia di indirizzo con cui vogliamo lavorare, di default abbiamo AF_INET che sta per gli indirizzi IPv4, con cui lavoreremo in questo Tutorial.

A socket_type corrisponde invece la tipologia di socket che vogliamo creare, di default abbiamo SOCK_STREAM per socket di tipo TCP, che utilizzeremo in questo tutorial. La funzione può essere personalizzata ulteriormente con altri parametri, ma questo va al di là dello scopo di questa lezione e quindi gli tralasceremo per ora.


Come creare un client socket

La funzione connect()

Una volta che abbiamo creato il nostro oggetto socket, allora possiamo utilizzare le appropriate funzioni per creare il nostro server o il nostro client. In questa prima lezione creeremo un client socket e ci connetteremo con esso a google.it per scaricare la homepage del sito, in maniera molto simile a come lo farebbe il browser.

La funzione molto importante con cui utilizziamo il client socket è la funzione connect(), a cui passiamo come parametro una tupla con i dati del servizio a cui ci vogliamo connettere, l'indirizzo e la porta:

# connessione a Google
>>> s.connect(("www.google.it", 80))

Tecnicamente a connect() sarebbe meglio passare una variabile server (host) e una variabile porta (port) per fare un lavoro più pulito. Proviamo a passare il nostro oggetto socket alla funzione print e vediamo che informazioni ci restituisce:

>>> print(s)

# output
< socket.socket fd=3, family=AddressFamily.AF_INET, type=SocketKind.SOCK_STREAM, proto=0, laddr=('192.168.1.6', 36668), raddr=('216.58.210.227', 80) >

La funzione send()

Come vedete da raddr (che interpreto come Remote Address, quindi indirizzo remoto) siamo felicemente connessi a Google! Ora che la connessione è stabilita possiamo usare una delle funzioni generiche (usate quindi sia per il server che per il client) per inviare una richiesta a Google per la sua homepage.

# Funzioni Generiche:

s.recv()   ==> Riceve messaggi TCP
s.send()   ==> Trasmette messaggi TCP
s.close()   ==> Chiude il Socket
socket.gethostname()   ==> Restituisce l'hostname della macchina su cui sta girando l'Interprete di Python
socket.gethostbyname()   ==> Restituisce l'IP associato al nome passato

In questo caso mi riferisco alla funzione send(), che accetta richieste di tipo bytes-object:

# richiesta GET homepage Google.it
>>> richiesta = "GET / HTTP/1.1\nHost: www.google.it\n\n"

# usiamo la funzione encode() per convertire la richiesta da stringa in 
# bytes-object, in modo da poterla poi inviare al server:
>>> s.send(richiesta.encode())

La funzione recv()

Ora che abbiamo inviato una richiesta per la homepage, possiamo ragionevolmente pensare di ricevere indietro il codice che la compone. Usiamo quindi la funzione recv():

# alla funzione recv() passiamo un valore corrispondente al buffer,
# ovvero alla quantità di dati che vogliamo scaricare per volta
>>> risposta = s.recv(2048)

Dal momento che la risposta viene passata in pezzi, per leggere tutto il contenuto del buffer facciamo:

# finché c'è contenuto, len(risposta) sarà maggiore di zero.

>>> while len(risposta) > 0:
    print(risposta)
    risposta = s.recv(2048)

# output codice HTML homepage #

Ed ecco che otteniamo il contenuto della pagina!

Quando abbiamo fatto, chiudiamo il nostro socket con la funzione close():

>>> s.close()

>>> print(s)
< socket.socket [closed] fd=-1, family=AddressFamily.AF_INET, type=SocketKind.SOCK_STREAM, proto=0 >

Nella prossima lezione vedremo come creare un semplice Server TCP con la funzione socket(), e un client con cui lo faremo comunicare!