7.1 Che cosa sono i sockets?
Diamo un'occhiata più da vicino. Prima abbiamo imparato a lavorare con
request
, poi con http.client
, poi con i proxy. E ora? Ora daremo un'occhiata sotto il cofano di tutte queste librerie…
Socket (letteralmente — presa) è un punto in rete attraverso il quale i dati vengono inviati e ricevuti. Puoi immaginare un socket come un punto finale di un canale di comunicazione bidirezionale tra due programmi, che lavorano su una o su diverse macchine.
I sockets supportano vari protocolli di rete, ma quelli più utilizzati sono due:
-
TCP
(Transmission Control Protocol): Protocollo affidabile che assicura la connessione, verifica l'integrità dei dati e la loro corretta sequenza. -
UDP
(User Datagram Protocol): Protocollo non basato sulla connessione, che non garantisce la consegna dei dati, ma è più veloce ed efficiente per certi tipi di applicazioni.
Per identificare un socket si usano indirizzo IP (che identifica il dispositivo nella rete) e porta (che identifica un'applicazione o servizio specifico sul dispositivo).
Importante!
L'indirizzo IP e la porta identificano univocamente
il programma nella rete. È come l'indirizzo di casa e il numero dell'appartamento. L'indirizzo di casa (indirizzo IP) è l'indirizzo del tuo computer nella
rete, mentre la porta è il numero dell'appartamento che il programma usa per
ricevere e inviare messaggi.
Puoi scoprire di più su cosa sono l'indirizzo IP e la porta nelle lezioni dedicate alla struttura della rete.
I principali passaggi del lavoro con i sockets in un programma:
- Creazione del socket: Il programma crea un socket, specificando il tipo di protocollo (TCP o UDP).
- Associazione all'indirizzo: Il socket si associa con l'indirizzo IP e il numero di porta per essere disponibile per le connessioni o per l'invio/ricezione dei dati.
-
Ascolto e stabilimento della connessione (per TCP):
- Ascolto: Il socket sul lato server viene messo in modalità ascolto, in attesa di connessioni in entrata.
- Stabilimento della connessione: Il client avvia la connessione con il server. Il server accetta la connessione, creando un nuovo socket per interagire con il client.
- Scambio dati: I dati vengono trasmessi tra client e server. Nel caso di TCP, i dati vengono trasmessi in modo affidabile.
- Chiusura della connessione: Dopo il completamento dello scambio dati, la connessione viene chiusa.
Vantaggi dell'uso dei sockets:
- Flessibilità: I sockets consentono alle applicazioni di scambiarsi dati indipendentemente dalla loro posizione e piattaforma.
- Prestazioni: I sockets offrono un modo rapido ed efficiente di trasmettere i dati, specialmente nel caso dell'uso di UDP.
- Affidabilità (nel caso di TCP): Il protocollo TCP offre una consegna affidabile dei dati con verifica dell'integrità e recupero dei pacchetti persi.
7.2 Creazione di un server socket
Il lavoro con i sockets in Python si gestisce tramite il modulo integrato socket
, che
fornisce un'interfaccia
per la programmazione di rete a basso livello.
Puoi creare un socket-server
— applicazione/oggetto, che riceverà
richieste dai client e
rispondere loro. E anche un socket-client
— applicazione/oggetto, che invierà richieste
al socket-server
e
riceverà risposte.
Per creare un socket-server
, bisogna fare tre cose:
- Creare un oggetto socket-server.
- Associare (con la funzione
bind
) all'indirizzo IP e alla porta. - Attivare la modalità di ascolto (
listen
) per i messaggi in entrata.
Il codice può apparire così:
import socket
# Creazione del socket
server_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
# Associazione del socket all'indirizzo e alla porta
server_socket.bind(('localhost', 12345))
# Ascolto delle connessioni in entrata
server_socket.listen(5)
print("Il server è in attesa di connessioni...")
Qui socket.AF_INET
indica che stiamo usando IPv4 per il protocollo di rete,
e socket.SOCK_STREAM
indica che stiamo usando TCP. Questi parametri sono i più
comunemente usati per creare applicazioni di rete.
Dopo che è arrivato un messaggio in entrata, bisogna fare altre quattro operazioni:
- Stabilire
(accept)
una connessione con il client. - Ricevere
(receive)
una richiesta (dati) dal client. - Inviare
(send)
una risposta al client — anche in questo caso dei dati. - Chiudere
(close)
la connessione.
Il codice può apparire così:
# Accettazione di una nuova connessione
client_socket, client_address = server_socket.accept()
print(f"Connessione stabilita con {client_address}")
# Ricezione dati dal client
data = client_socket.recv(1024)
print(f"Ricevuto: {data.decode('utf-8')}")
# Invio dati al client
client_socket.sendall(b'Ciao, client!')
# Chiusura della connessione con il client
client_socket.close()
Il metodo sendall()
viene usato al posto di send()
perché garantisce che
tutti i dati vengano inviati. Il metodo send()
potrebbe inviare solo una parte dei dati,
se il buffer si riempie.
Il numero 1024 in recv(1024)
indica il numero massimo di byte che si possono
ricevere in una sola volta. Aiuta a controllare il volume dei dati elaborati in un'unica operazione.
Il codice dell'ultimo esempio di solito viene eseguito in un ciclo infinito — il server elabora la richiesta, poi attende una nuova, poi la elabora, e così via continuamente.
Se vuoi eseguirlo da te o semplicemente vedere un esempio completo, te lo mostro qui:
import socket
# Creazione del socket
server_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
# Associazione del socket all'indirizzo e alla porta
server_socket.bind(('localhost', 12345))
# Ascolto delle connessioni in entrata
server_socket.listen(5)
print("Il server è in attesa di connessioni...")
while True:
# Accettazione di una nuova connessione
client_socket, client_address = server_socket.accept()
print(f"Connessione stabilita con {client_address}")
# Ricezione dati dal client
data = client_socket.recv(1024)
print(f"Ricevuto: {data.decode('utf-8')}")
# Invio dati al client
client_socket.sendall(b'Ciao, client!')
# Chiusura della connessione con il client
client_socket.close()
7.3 Creazione di un client socket
Abbiamo creato un server socket, ora scriviamo il nostro client socket che si collegherà al server e riceverà dati in risposta.
Per fare questo, bisogna fare cinque cose:
- Creare un oggetto
client socket
. - Stabilire una connessione
(connect)
con l'indirizzo IP e la porta del nostrosocket-server
. - Inviare
(send)
un messaggio al server. - Ricevere
(receive)
dati dal server. - Chiudere
(close)
la connessione.
In realtà è più semplice di quanto sembri. Ecco come apparirà il codice:
import socket
# Creazione del socket
client_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
# Connessione al server
client_socket.connect(('localhost', 12345))
# Invio dati al server
client_socket.sendall(b'Ciao, server!')
# Ricezione dati dal server
data = client_socket.recv(1024)
print(f"Ricevuto dal server: {data.decode('utf-8')}")
# Chiusura del socket
client_socket.close()
Se dall'altra parte non c'è un socket-server
avviato o la connessione è interrotta, verrà sollevata un'eccezione di tipo
socket.error
. Quindi, non dimenticare di gestire le eccezioni.
Qui finiamo il lavoro con i sockets per oggi.
In qualsiasi lavoro con la rete ti incontrerai di nuovo e ancora con host, porte, indirizzi IP, stabilimento delle connessioni, ascolto delle richieste e cose del genere. Quindi capire come funziona a fondo ti faciliterà molto la vita e ti aiuterà a unire conoscenze sparse in un'immagine unica.
GO TO FULL VERSION