In questa lezione impari a
- spiegare indirizzi IP, porte, TCP e UDP
- scrivere un client e un server TCP con i socket
- usare i socket UDP
- gestire più client con i thread
Come due computer si parlano davvero: dai concetti base di rete, alla scrittura di un client e un server che si scambiano messaggi, fino a una piccola chat funzionante.
gennaio – marzo · ~10 settimane · 30 ore · 5 moduli + progetto
2.1 Richiami di rete e modello client-server
Durata indicativa: 2 settimane
Prima di scrivere codice, servono tre concetti minimi per capire chi parla con chi in rete.
- Indirizzo IP — identifica quale computer nella rete (es.
192.168.1.10). È come l'indirizzo civico di un palazzo. - Porta — identifica quale programma, dentro quel computer, deve ricevere i dati (es. porta
80per un sito web,5000per la vostra app Flask). È come il numero dell'appartamento dentro il palazzo. - Modello client-server — il server è il programma che sta in ascolto, pronto a rispondere; il client è chi si collega e fa richieste. Un browser è un client, il sito che visitate gira su un server.
TCP vs UDP: affidabile o veloce?
Quando due programmi si scambiano dati in rete, possono farlo in due modi diversi:
- TCP — prima di mandare dati, stabilisce una connessione stabile, e garantisce che tutto arrivi, nell'ordine giusto (se un pezzo si perde, viene rispedito). Più lento, ma affidabile. Usato per: pagine web, email, trasferimento file.
- UDP — spedisce i dati senza stabilire nulla in anticipo e senza controllare che siano arrivati. Più veloce, ma può perdere pezzi per strada. Usato per: streaming video, chiamate vocali, giochi online — dove un pacchetto perso ogni tanto è meno grave di un ritardo.
Un buon modo per ricordarli: TCP è come una raccomandata con ricevuta di ritorno (lenta ma sicura), UDP è come urlare un'informazione nella stanza — arriva subito, ma se qualcuno non sente non ve ne accorgete.
Esercizio pratico.
- Sul PC di laboratorio, aprite un terminale ed eseguite
netstat -ano(Windows) oss -tulpn(Linux): osservate le connessioni attive e le porte in ascolto - Aprite Wireshark, avviate una cattura sulla vostra interfaccia di rete, poi visitate un sito qualsiasi dal browser: individuate i pacchetti TCP relativi a quella connessione
- Provate a riconoscere, tra le connessioni elencate da
netstat/ss, la porta80o443(i siti web) e confrontatela con quanto visto in Wireshark
Cosa consegnare: uno screenshot dell'output del terminale con almeno 3 connessioni evidenziate e spiegate a parole (chi è il client, chi il server, che porta usa).
2.2 Socket TCP
Durata indicativa: 2 settimane
Un socket è il "punto di collegamento" che un programma usa per parlare in rete — pensatelo come una presa telefonica: da una parte il server la mette in ascolto, dall'altra il client ci si collega.
Il server
import socket
server = socket.socket(socket.AF_INET, socket.SOCK_STREAM) # TCP
server.bind(("0.0.0.0", 5050)) # ascolta su tutte le interfacce, porta 5050
server.listen(1) # accetta 1 connessione alla volta
print("Server in ascolto sulla porta 5050...")
connessione, indirizzo = server.accept()
print(f"Client connesso da {indirizzo}")
while True:
dati = connessione.recv(1024) # riceve fino a 1024 byte
if not dati:
break
print(f"Ricevuto: {dati.decode()}")
connessione.sendall(dati) # rimanda indietro lo stesso messaggio (echo)
connessione.close()
Il client
import socket
client = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
client.connect(("127.0.0.1", 5050)) # indirizzo e porta del server
while True:
messaggio = input("Scrivi un messaggio (o 'esci'): ")
if messaggio == "esci":
break
client.sendall(messaggio.encode())
risposta = client.recv(1024)
print(f"Il server ha risposto: {risposta.decode()}")
client.close()
encode()edecode()servono perché i socket viaggiano in byte, non in testo:encode()trasforma una stringa in byte prima di spedirla,decode()fa il percorso inverso quando la si riceve.
Esercizio pratico.
- Scrivete il server e il client come sopra (server di "echo": rimanda indietro ciò che riceve) e provateli su due terminali diversi dello stesso PC
- Modificate il server in modo che, invece di rimandare lo stesso messaggio, si comporti come una calcolatrice remota: il client manda una stringa tipo
"3+5", il server la calcola e rimanda il risultato
Cosa consegnare: il codice di server e client della calcolatrice remota, con una breve demo (anche solo a voce) di 2-3 operazioni funzionanti.
2.3 Socket UDP
Durata indicativa: 2 settimane
Con UDP non c'è una connessione stabile da aprire: si manda un pacchetto direttamente all'indirizzo e porta di destinazione, senza sapere se qualcuno lo riceverà davvero.
Server UDP
import socket
server = socket.socket(socket.AF_INET, socket.SOCK_DGRAM) # UDP
server.bind(("0.0.0.0", 5051))
print("Server UDP in ascolto sulla porta 5051...")
while True:
dati, indirizzo_client = server.recvfrom(1024)
print(f"Ricevuto da {indirizzo_client}: {dati.decode()}")
server.sendto(dati, indirizzo_client) # echo
Client UDP
import socket
client = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
while True:
messaggio = input("Scrivi un messaggio (o 'esci'): ")
if messaggio == "esci":
break
client.sendto(messaggio.encode(), ("127.0.0.1", 5051))
risposta, _ = client.recvfrom(1024)
print(f"Il server ha risposto: {risposta.decode()}")
Notate le differenze nel codice: niente
connect()néaccept()— con UDP si usa direttamentesendto()erecvfrom(), specificando ogni volta l'indirizzo di destinazione, perché non esiste una connessione "aperta" da ricordare.
Esercizio pratico.
- Riscrivete il server e il client di echo del punto 2.2 in versione UDP, come sopra
- Elencate, a parole, almeno 3 differenze concrete che notate nel codice rispetto alla versione TCP
- Bonus: provate a chiudere il server e mandare comunque un messaggio dal client. Cosa succede rispetto a quando lo fareste con TCP?
Cosa consegnare: codice UDP funzionante + le differenze annotate.
2.4 Server multi-client
Durata indicativa: 2 settimane
I server visti finora hanno un limite grosso: accept() gestisce un solo client alla volta. Se un secondo client prova a collegarsi mentre il server sta ancora parlando con il primo, deve aspettare in coda.
La soluzione si collega a quanto visto nel Periodo 1: ogni volta che arriva un nuovo client, il server apre un thread dedicato a lui, e torna subito pronto ad accettarne altri.
import socket
import threading
def gestisci_client(connessione, indirizzo):
print(f"Client connesso: {indirizzo}")
while True:
dati = connessione.recv(1024)
if not dati:
break
print(f"[{indirizzo}] {dati.decode()}")
connessione.sendall(dati)
connessione.close()
print(f"Client disconnesso: {indirizzo}")
server = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
server.bind(("0.0.0.0", 5050))
server.listen()
print("Server multi-client in ascolto...")
while True:
connessione, indirizzo = server.accept()
# un thread dedicato per ogni client, il ciclo torna subito ad ascoltare
t = threading.Thread(target=gestisci_client, args=(connessione, indirizzo))
t.start()
Il ciclo principale fa solo una cosa: accettare nuovi client e "delegare" la conversazione a un thread. Così può accettarne un altro subito dopo, senza aspettare che il primo finisca — esattamente il concetto di reattività visto nel Periodo 1.
Esercizio pratico.
- Scrivete il server multi-thread come sopra e collegatevi con 2-3 client contemporaneamente (da terminali o PC diversi del laboratorio)
- Trasformatelo in una chat testuale: invece di rimandare l'eco al solo mittente, il server deve inoltrare ogni messaggio ricevuto a tutti gli altri client connessi (suggerimento: mantenete una lista condivisa delle connessioni attive, e attenzione a proteggerla con un
Lockse più thread la modificano insieme — vi ricorda qualcosa del Periodo 1?)
Cosa consegnare: server e client della chat, con una demo tra almeno 2 postazioni del laboratorio.
2.5 Progetto del periodo
Durata indicativa: 2 settimane
Esercizio.
Obiettivo
Realizzare un'applicazione client-server completa, funzionante tra postazioni diverse della rete scolastica, che metta insieme socket e multi-threading.
Scegliete un tema (o proponetene uno voi, va approvato dal docente)
- Chat testuale multi-utente — evoluzione diretta dell'esercizio del punto 2.4, magari con nickname per ogni utente
- Gioco semplice a turni — es. tris (tris a due giocatori collegati da PC diversi) o "indovina il numero" multiplayer
- Sistema di messaggi — un client invia un messaggio a un utente specifico (identificato da un nome), il server lo consegna solo a lui
Requisiti minimi
- Server in grado di gestire più client contemporaneamente (con thread)
- Comunicazione funzionante tra almeno due postazioni fisiche diverse del laboratorio, non solo sullo stesso PC
- Gestione corretta della disconnessione di un client (il server non deve bloccarsi se un client si chiude)
- Codice commentato nei punti chiave
Cosa consegnare: codice sorgente funzionante (su GitHub o cartella condivisa) + demo live tra due postazioni diverse della rete scolastica.
academy-pc.it · materiale didattico — Modulo Reti e Programmazione con Socket, TPSIT quarta