Vogliamo realizzare un piccolo servizio di prenotazione posti teatro/cinema/bus usando una connessione socket client/server. Per il momento tralasciamo considerazioni sulla concorrenza delle risorse e la contesa tra più client. Il codice lo scriviamo asciutto e decisamente semplificato senza uso di try/catch ed elementi di validazione/correzione accurati. L’esercizio è collegato a quello visto in Server/Client Ping Pong in Python.
Indice dei contenuti
Il server
Il server in una ipotetica serie di esercizi di questa difficoltà è quello che riserva sempre un po’ più di lavoro. Se procediamo in modo analogo all’esercizio visto del ping pong, possiamo implementare una classe in modo analogo dove definiamo in modo fisso/statico porta e indirizzo per il socket.
Sempre nella stessa classe possiamo anche definirci degli attributi utili a risolvere la specifica progettuale dell’esercizio. Nel nostro caso siamo di fronte alla prenotazione di posti di un teatro o simili. La rappresentazione più semplice è un vettore/lista di valori booleani che indicano se un posto è occupato, mentre il posto stesso è numerato in base all’indice del vettore stesso. Inizializziamo tutto a falso (non occupato). Qui nell’esercizio creiamo un vettore di 10 posti ed esempio.
self.posti = [False] * 10 #imposta 10 posizioni a FalsePassiamo al metodo start che viene lanciato per poter mettere in ascolto il server e scatenare tutte le azioni di ricezioni/risposta. Le prime righe di bind e listen servono per inizializzare il socket e mettersi in ascolto. Il parametro 1 di listen assolve al compito di far collegare solo un client alla volta.
self.servizio.bind((self.host, self.porta))
self.servizio.listen(1)
print("Server in ascolto sulla porta ", self.porta)A questo punto dobbiamo ciclare all’infinito, o per lo meno se non abbiamo qualche condizioni di chiusura particolare, e rimanere in ricezioni dei comandi provenienti dal client. Questa è la parte forse più complessa per lo studente, uno perché può essere fatta in molti modi, due perché i vari chatbot da cui ispirarsi, in questi casi, consigliano soluzioni molto complesse ed articolate, ultimo i messaggi dei client possono essere molto variegati a seconda dell’esercizio.
Accettiamo attraverso l’attributo servizio una connessione ed indirizzo. A questo punto la variabile connessione sarà la maniglia in stile file per ricevere lo streaming dati con la funzione recv. Scegliamo un buffer di dimensione opportune, nel nostro caso 1024 bit sono più che sufficienti per passare comandi e relativi parametri. Se l’esercizio prevedesse l’invio di lunghe frasi, probabilmente il buffer dovrebbe essere di dimensione maggiore.
Quindi ora la parte più ostica: capire quale comando è stato passato dal client. Si può fare in molti modi, analizzando parola singola, cercando nelle sottostringhe con la funzione nativa parola.find() o parola.index(). Lo stesso comando split() torna molto utile per suddividere una stringa con spazi divisori in più variabili/parole.
while True:
connessione, indirizzo = self.servizio.accept()
print("Connessione ricevuta da ", indirizzo)
messaggio = connessione.recv(1024).decode()
if messaggio != "ALL" and messaggio != "QUIT" :
comando, valore= messaggio.split(" ", 2)
else:
comando = messaggioReperiti i vari frammenti di comandi e valori, possiamo fare un switch/case che in python si chiama match dalla versione 3.10 in poi. Qui l’esercizio richiede più messaggi di ritorno dal server verso il client. Bisogna stare attenti ai parametri che abbiamo ripreso dal flusso di bit: in quanto flusso viene recepito come stringa quindi i vari parametri se numerici vanno forzati con un cast, qui ad esempio ad intero. Il parametro in questo caso è banalmente l’indice del mio vettore di posti che mi permette di controllare il valore boolean occupato con true o libero con false. Con il comando send() mandiamo un buffer stringa o variabile con stringa al client componendola con messaggio o altri valori utili.
case "GET":
i = int(valore)
if self.posti[i] == True:
valore = "OCCUPATO"
else:
valore = "LIBERO"
connessione.send(valore.encode())Menzione particolare merita il comando ALL che chiede che al client sia mandato l’intero blocco di posti con la status, ovvero una stampa del vettore dei posti.
case "ALL":
connessione.send(self.stampaPosti().encode())Il codice server completo
import socket
class ServerPosti:
def __init__(self, _host = "127.0.0.1", _porta= 5000):
self.host = _host
self.porta = _porta
self.servizio = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
self.servizio.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
self.posti = [False] * 10 #imposta 10 posizioni a False
def stampaSocket(self):
print ("host:",self.host, " porta:",self.porta)
def stampaPosti(self):
stampa=""
for i,valore in enumerate(self.posti):
if valore == True:
stato = "Occupato"
else:
stato = "Libero"
stampa = stampa + "Posto " + str(i) +"->"+ stato + " "
return stampa
def start(self):
self.servizio.bind((self.host, self.porta))
self.servizio.listen(1)
print("Server in ascolto sulla porta ", self.porta)
while True:
connessione, indirizzo = self.servizio.accept()
print("Connessione ricevuta da ", indirizzo)
messaggio = connessione.recv(1024).decode()
if messaggio != "ALL" and messaggio != "QUIT" :
comando, valore= messaggio.split(" ", 2)
else:
comando = messaggio
match comando:
case "GET":
i = int(valore)
if self.posti[i] == True:
valore = "OCCUPATO"
else:
valore = "LIBERO"
connessione.send(valore.encode())
case "SET":
i = int(valore)
self.posti[i] = True
connessione.send("OK".encode())
case "ALL":
connessione.send(self.stampaPosti().encode())
case "QUIT":
connessione.close()
exit(0)
case _:
print("Comando non valido da: ", indirizzo)
connessione.close()
if __name__ == "__main__":
server = ServerPosti()
server.start()
Il client
Il client è molto simile a quello visto nel pong (qui). E’ chiaro si deve creare un ciclo infinito che permetta l’inserimento di più comandi fino a che si inserisce il comando QUIT. Per il resto qui non c’è una grande interazione: si recepisce il comando da tastiera/input e si spedisce al server con send(). Occhio sempre a trasformare il testo in flusso di bit grezzo con il comando encode(). Mandato il comando ci mettiamo in ascolto della risposta con receipt() e decodificando il flusso grezzo di dati provenienti dal server.
import socket
class ClientPosti:
def __init__(self, _host="127.0.0.1", _porta=5000):
self.host = _host
self.porta = _porta
self.servizio = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
def start(self):
#print("Digita ? per i comandi disponibili")
print("Comandi: SET/GET/ALL/QUIT")
print("es >GET 1 >SET 5, >ALL")
comando =""
while True and comando != "QUIT":
self.servizio = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
self.servizio.connect((self.host, self.porta))
comando = input(">")
if comando == " ":
continue
self.servizio.send(comando.encode())
messaggio = self.servizio.recv(1024).decode()
print(messaggio)
self.servizio.close()
print("Connessione chiusa")
if __name__ == "__main__":
client = ClientPosti()
client.start()Ultima modifica 23 Ottobre 2025


