# compatibile Windows 11 # compatibile Ubuntu 24.10 # compatibile python 3.12.7 import socket import time import logging import sys import threading # Importante: necessario per i thread class UDPClient: """ Un client UDP per comunicazioni con un server UDP, con ricezione in un thread separato. """ def __init__(self, server_host, server_port, client_port, buffer_size=1024, timeout=5): """ Inizializza l'oggetto UDPClient. Args: server_host (str): L'indirizzo IP o nome host del server. server_port (int): La porta del server. client_port (int): La porta su cui il client ascolta. buffer_size (int, optional): La dimensione del buffer. Default: 1024. timeout (int, optional): Timeout per le operazioni di socket. Default: 5. """ self.server_host = server_host self.server_port = server_port self.client_port = client_port self.buffer_size = buffer_size self.timeout = timeout self.socket = None self.active = False self.logger = self._setup_logger() self._receive_thread = None # Thread per la ricezione self._stop_receive_thread = False # Flag per fermare il thread di ricezione self.on_message = None #callback per la ricezione self.on_error = None # Callback per gli errori def _setup_logger(self): """Configura il logger.""" logger = logging.getLogger('UDPClient') logger.setLevel(logging.DEBUG) console_handler = logging.StreamHandler(sys.stdout) console_handler.setLevel(logging.DEBUG) formatter = logging.Formatter('%(asctime)s - %(name)s - %(levelname)s - %(message)s') console_handler.setFormatter(formatter) logger.addHandler(console_handler) return logger def start(self): """Avvia il client UDP e il thread di ricezione.""" if self.active: self.logger.warning("Il client è già attivo.") return try: self.socket = socket.socket(socket.AF_INET, socket.SOCK_DGRAM) self.socket.settimeout(self.timeout) self.socket.bind(('0.0.0.0', self.client_port)) # Bind a un indirizzo/porta self.active = True self.logger.info(f"Client UDP avviato su {self.socket.getsockname()}") # Avvia il thread di ricezione self._stop_receive_thread = False self._receive_thread = threading.Thread(target=self._receive_loop, daemon=True) self._receive_thread.start() except socket.error as e: self._handle_error(f"Errore socket durante l'avvio: {e}") self.close() except Exception as e: self._handle_error(f"Errore generico durante l'avvio: {e}") self.close() def _receive_loop(self): """Ciclo di ricezione eseguito nel thread separato.""" while not self._stop_receive_thread: try: data, addr = self.socket.recvfrom(self.buffer_size) if data and self.on_message: #Chiamata alla callback self.on_message(self, data.decode('utf-8', 'replace'), addr) except socket.timeout: # Timeout del socket (normale con socket non bloccanti/timeout) continue # Continua il ciclo except socket.error as e: if e.errno != 10035: # Ignora WSAEWOULDBLOCK (Windows) if self.on_error: self.on_error(self, str(e)) continue except Exception as e: if self.on_error: self.on_error(self, str(e)) break #esci in caso di errore time.sleep(0.01) # Piccola pausa per evitare di usare il 100% della CPU def send(self, message): """Invia un messaggio UDP al server.""" if not self.active: self._handle_error("Il client non è attivo, impossibile inviare il messaggio") return address = (self.server_host, self.server_port) try: if isinstance(message, str): message = message.encode('utf-8') self.socket.sendto(message, address) self.logger.debug(f"Inviato messaggio a {address}: {message[:50]}...") except (socket.error, ValueError) as e: self._handle_error(f"Errore durante l'invio a {address}: {e}") except Exception as e: self._handle_error(f"Errore generico durante l'invio a {address}: {e}") def close(self): """Chiude il client, ferma il thread di ricezione e rilascia le risorse.""" if not self.active: return self.active = False self._stop_receive_thread = True # Ferma il thread di ricezione if self._receive_thread: self._receive_thread.join() # Attendi che il thread termini try: if self.socket: self.socket.close() except socket.error as e: self._handle_error(f"Errore socket durante la chiusura: {e}") finally: self.logger.info("Client UDP arrestato.") self.socket = None self._receive_thread = None #resetta il thread def _handle_error(self, message): """Gestisce gli errori.""" if self.on_error: self.on_error(self, message) #callback else: self.logger.error(message) #messaggio di log # --- Funzioni di callback --- def handle_message(client, message, address): print(f"Ricevuto da {address}: {message}") def handle_error(client, message): print(f"Errore: {message}") def test_udp_server(server_host, server_port, client_port): """Funzione per testare il client UDP (ora con ricezione asincrona).""" client = UDPClient(server_host, server_port, client_port) client.on_message = handle_message #imposta la callback client.on_error = handle_error client.start() test_messages = [ "Messaggio di prova 1\r\n", "Messaggio di prova 2\r\n", "Messaggio di prova 3\r\n", "Fine" ] try: test = '' test = input("Premi RETURN per continuare") for message in test_messages: if message == "Fine": print("Client: Test terminato come richiesto.") break print(f"Client: Invio messaggio: {message.strip()}") client.send(message) time.sleep(5) # Attendi tra gli invii (ma la ricezione è asincrona) except KeyboardInterrupt: print("Client: Test interrotto dall'utente.") finally: client.close() if __name__ == "__main__": SERVER_HOST = 'localhost' SERVER_PORT = 5000 CLIENT_PORT = 5001 test_udp_server(SERVER_HOST, SERVER_PORT, CLIENT_PORT)