// dotnet new console -o client_UDP_crypto_protocollo_TLV_multicast_relay_simm_csharp_launch (per creare un nuovo progetto) // dotnet build (compilazione in debug, dove si trova .csproj) // dotnet build -c Release (compilazione in release, dove si trova .csproj) using System; using System.IO; using System.Net; using System.Net.Sockets; using System.Text; using System.Threading; using System.Threading.Tasks; using System.Diagnostics; public class PythonClientLauncher { // Connessione TCP verso il Client Python private static TcpClient? _tcpControlClient = null; private static NetworkStream? _tcpStream = null; private static StreamReader? _tcpReader = null; private static StreamWriter? _tcpWriter = null; // Gestione del processo Client Python private static Process? _pythonClientProcess = null; private const string PythonClientExecutablePath = @"client_UDP_crypto_protocollo_TLV_multicast_relay_simm_csharp.exe"; // Nome dell'eseguibile Python del client // Gestione arresto private static CancellationTokenSource _cts = new CancellationTokenSource(); public static async Task Main(string[] args) { Console.Title = "C# Launcher for Python UDP/TCP Client"; // Titolo finestra console Console.WriteLine("--- C# Launcher for Python UDP/TCP Client ---"); // Registra l'handler per Ctrl+C Console.CancelKeyPress += Console_CancelKeyPress; // --- Lettura Parametri per il Client Python --- Console.Write("Inserisci l'indirizzo del gruppo multicast del server UDP [224.1.1.1]: "); string multicast_group = Console.ReadLine(); if (string.IsNullOrWhiteSpace(multicast_group)) multicast_group = "224.1.1.1"; Console.Write("Inserisci la porta multicast del server UDP [5007]: "); int multicast_port; string multicast_port_input = Console.ReadLine(); if (string.IsNullOrWhiteSpace(multicast_port_input) || !int.TryParse(multicast_port_input, out multicast_port) || multicast_port <= 0 || multicast_port > 65535) { multicast_port = 5007; Console.WriteLine($"Usando default: {multicast_port}"); } Console.Write("Inserisci la porta TCP locale per il controllo del Client Python [5009]: "); int python_client_tcp_port; string python_client_tcp_port_input = Console.ReadLine(); if (string.IsNullOrWhiteSpace(python_client_tcp_port_input) || !int.TryParse(python_client_tcp_port_input, out python_client_tcp_port) || python_client_tcp_port <= 0 || python_client_tcp_port > 65535) { python_client_tcp_port = 5009; // Default del client python Console.WriteLine($"Usando default: {python_client_tcp_port}"); } // --- Avvio Processo Client Python --- if (!File.Exists(PythonClientExecutablePath)) { WriteLineError($"Errore: Eseguibile Client Python non trovato in '{Path.GetFullPath(PythonClientExecutablePath)}'"); ExitApplication(); return; } // Costruisci gli argomenti per il client Python string arguments = $"--multicast-group {multicast_group} --multicast-port {multicast_port} --tcp-control-port {python_client_tcp_port}"; Console.WriteLine($"\nAvvio client Python: \"{PythonClientExecutablePath}\" {arguments}"); string? workingDir = Path.GetDirectoryName(PythonClientExecutablePath); ProcessStartInfo startInfo = new ProcessStartInfo { FileName = PythonClientExecutablePath, Arguments = arguments, WorkingDirectory = workingDir ?? ".", UseShellExecute = true, // Manteniamo per visibilità console Python (se non usa --noconsole) // UseShellExecute = false, // Alternativa (nasconde console Python ma richiede gestione output C#) // RedirectStandardOutput = !UseShellExecute, // RedirectStandardError = !UseShellExecute, // CreateNoWindow = !UseShellExecute, }; try { _pythonClientProcess = Process.Start(startInfo); if (_pythonClientProcess == null) throw new InvalidOperationException("Fallimento avvio processo client Python."); Console.WriteLine($"Processo client Python avviato (PID: {_pythonClientProcess.Id}). Attesa inizializzazione e avvio server TCP interno..."); await Task.Delay(3500); // Aumenta leggermente l'attesa per dare tempo al client Python di avviare il suo server TCP } catch (Exception ex) { WriteLineError($"Errore avvio processo client Python: {ex.Message}"); ExitApplication(); return; } // --- Connessione al Server TCP del Client Python --- // --- Connessione al Server TCP del Client Python --- string pythonClientTcpHost = "127.0.0.1"; // Il server TCP del client Python ascolta su localhost try { _tcpControlClient = new TcpClient(); // Correzione: Dichiarare e usare direttamente "127.0.0.1" Console.WriteLine($"\nTentativo di connessione al server TCP di controllo del client Python ({pythonClientTcpHost}:{python_client_tcp_port})..."); // Aumenta leggermente il timeout per la connessione iniziale using var connectCts = new CancellationTokenSource(TimeSpan.FromSeconds(10)); // Correzione: Passare pythonClientTcpHost dichiarato sopra await _tcpControlClient.ConnectAsync(pythonClientTcpHost, python_client_tcp_port, connectCts.Token); if (!_tcpControlClient.Connected) { WriteLineError("Connessione al server TCP del client Python fallita."); return; // Cleanup verrà chiamato nel finally esterno } WriteLineSuccess("Connesso al server TCP del client Python!"); // Ottieni lo stream e crea reader/writer per la connessione di controllo _tcpStream = _tcpControlClient.GetStream(); _tcpReader = new StreamReader(_tcpStream, Encoding.UTF8, true, 1024, true); _tcpWriter = new StreamWriter(_tcpStream, Encoding.UTF8, 1024, true); _tcpWriter.AutoFlush = true; // --- Avvio Task Ricevitore (legge output dal client Python) --- var receiveTask = Task.Run(() => ReceivePythonClientOutput(_cts.Token)); Console.WriteLine("\nPronto per inviare messaggi al client Python (che li inoltrerà via UDP)."); Console.WriteLine("Digita 'exit' per chiudere questo launcher (e il client Python)."); // --- Loop Invio Messaggi (invia comandi/messaggi al client Python via TCP) --- await SendCommandsLoop(); // --- Attesa Terminazione Ricevitore --- Console.WriteLine("\nIn attesa della terminazione del task ricevitore TCP..."); // Usa WaitAsync per non bloccare indefinitamente // Aumentato leggermente il timeout qui await receiveTask.WaitAsync(TimeSpan.FromSeconds(3), CancellationToken.None).ConfigureAwait(false); } // ... resto del blocco try/catch/finally ... catch (SocketException ex) when (ex.SocketErrorCode == SocketError.ConnectionRefused) { WriteLineError($"Errore di connessione: Connessione rifiutata. Il server TCP del client Python su {python_client_tcp_port} non è attivo o raggiungibile?"); } catch (SocketException ex) { WriteLineError($"Errore di connessione Socket: {ex.Message} (SocketErrorCode: {ex.SocketErrorCode})"); } catch (OperationCanceledException) // Catch per il timeout di ConnectAsync { WriteLineError($"Errore di connessione: Timeout durante il tentativo di connessione al server TCP del client Python ({pythonClientTcpHost}:{python_client_tcp_port})."); } catch (Exception ex) { WriteLineError($"Errore generico durante connessione/comunicazione: {ex.Message}"); } finally { // --- Cleanup --- Cleanup(); // Chiama il metodo di cleanup aggiornato Console.WriteLine("\nLauncher C# terminato."); ExitApplication(false); // Esce senza chiedere input } } // Loop principale per leggere l'input dell'utente e inviarlo al client Python via TCP private static async Task SendCommandsLoop() { string? line; while (true) // Continua finché non viene interrotto da 'exit' o errore { // Leggi input in modo asincrono per non bloccare completamente durante l'attesa // Potrebbe comunque bloccare se Console.ReadLine() sincrono blocca il thread pool // Per UI reali servirebbe un approccio diverso. Console.Write("> "); line = await Task.Run(() => Console.ReadLine()); // Esegui ReadLine in un task separato // Controlla se il CancellationToken è stato attivato (es. da Ctrl+C o errore nel ricevitore) if (_cts.IsCancellationRequested) { WriteLineWarning("Operazione annullata, uscita dal loop di invio."); break; } if (line == null) // Input terminato (es. Ctrl+Z) { Console.WriteLine("Input terminato."); break; } // Controlla connessione PRIMA di inviare 'exit' if (_tcpWriter == null || _tcpControlClient == null || !_tcpControlClient.Connected) { WriteLineWarning("Non connesso al client Python. Impossibile inviare."); break; } // Gestione comando 'exit' if (line.Equals("exit", StringComparison.OrdinalIgnoreCase)) { try { // Invia 'exit' al client Python await _tcpWriter.WriteLineAsync(line); // Usa WriteLineAsync Console.WriteLine("Comando 'exit' inviato al client Python..."); // Non attendere qui, lascia che Cleanup gestisca l'attesa e il kill } catch (Exception ex) when (ex is IOException || ex is ObjectDisposedException) { WriteLineWarning($"Impossibile inviare 'exit', connessione probabilmente già chiusa: {ex.Message}"); } break; // Esce dal loop di invio dopo aver tentato di inviare 'exit' } // Invio messaggio normale try { await _tcpWriter.WriteLineAsync(line); } catch (IOException ioEx) { WriteLineError($"Errore durante l'invio TCP (connessione persa?): {ioEx.Message}"); break; // Esce dal loop se l'invio fallisce } catch (ObjectDisposedException) { WriteLineWarning("Impossibile inviare, la connessione TCP è in fase di chiusura."); break; // Esce se le risorse sono già state rilasciate } catch (Exception ex) { WriteLineError($"Errore inatteso durante l'invio TCP: {ex.Message}"); break; // Esce in caso di altri errori } } // Se usciamo dal loop, segnala lo shutdown TriggerShutdown(); } // Metodo eseguito in un task separato per ricevere l'output dal client Python via TCP private static async Task ReceivePythonClientOutput(CancellationToken token) { LogTimestampedMessage("[Ricevitore TCP] Avviato."); try { if (_tcpReader == null) { WriteLineError("[Ricevitore TCP] Errore: StreamReader non inizializzato."); return; } while (!token.IsCancellationRequested && _tcpControlClient != null && _tcpControlClient.Connected) { try { string? message = await _tcpReader.ReadLineAsync(token).ConfigureAwait(false); // ConfigAwait(false) buono per librerie/task di background if (message != null) { // Stampa l'output ricevuto dal client Python WriteLineFromPython(message); } else { // Connessione chiusa dall'altro lato WriteLineWarning("[Ricevitore TCP] Il client Python ha chiuso la connessione TCP (probabilmente sta terminando)."); break; // Esce dal loop } } catch (OperationCanceledException) { LogTimestampedMessage("[Ricevitore TCP] Operazione annullata (shutdown richiesto)."); break; } catch (IOException ioEx) { if (!token.IsCancellationRequested) WriteLineError($"[Ricevitore TCP] Errore IO (connessione persa?): {ioEx.Message}"); break; } catch (ObjectDisposedException) { if (!token.IsCancellationRequested) LogTimestampedMessage("[Ricevitore TCP] Risorse rilasciate durante la lettura."); break; } catch (Exception ex) { if (!token.IsCancellationRequested) WriteLineError($"[Ricevitore TCP] Errore inatteso: {ex.Message}"); break; } } } finally { LogTimestampedMessage("[Ricevitore TCP] Terminato."); TriggerShutdown(); // Assicura che anche il loop di invio si fermi se il ricevitore termina } } // Metodo aggiornato per chiudere le risorse private static void Cleanup() { LogTimestampedMessage("Esecuzione cleanup C# Launcher..."); // 1. Segnala arresto ai task interni (se non già fatto) TriggerShutdown(); // 2. Tenta di inviare il comando "exit" al client Python via TCP (Best Effort) // (Mantieni questa parte - è una buona pratica per una chiusura controllata) if (_tcpControlClient != null && _tcpControlClient.Connected && _tcpWriter != null) { try { LogTimestampedMessage("Invio comando 'exit' al client Python via TCP..."); _tcpWriter.WriteLine("exit"); _tcpWriter.Flush(); LogTimestampedMessage("Comando 'exit' inviato. Attesa chiusura client Python..."); // Riduci leggermente il tempo di attesa, Kill è il fallback Task.Delay(1500).Wait(); } catch (Exception ex) when (ex is IOException || ex is ObjectDisposedException || ex is InvalidOperationException) { WriteLineWarning($"Avviso: Errore durante l'invio del comando 'exit' via TCP (client Python potrebbe essere già chiuso?): {ex.Message}"); } } else { LogTimestampedMessage("Connessione TCP al client Python non attiva, impossibile inviare 'exit'."); } // 3. Chiudi le risorse TCP del Launcher C# LogTimestampedMessage("Chiusura risorse TCP C# Launcher..."); try { _tcpWriter?.Close(); _tcpWriter = null; } catch { /* Ignora */ } try { _tcpReader?.Close(); _tcpReader = null; } catch { /* Ignora */ } try { _tcpStream?.Close(); _tcpStream = null; } catch { /* Ignora */ } try { _tcpControlClient?.Close(); _tcpControlClient = null; } catch { /* Ignora */ } LogTimestampedMessage("Risorse TCP C# Launcher chiuse."); // 4. Termina il PROCESSO SPECIFICO associato a questo launcher // (SOSTITUISCI la logica con Process.GetProcessesByName) LogTimestampedMessage("Terminazione processo Python associato..."); if (_pythonClientProcess != null) { try { // Controlla se non è già terminato (es. dopo aver ricevuto 'exit') if (!_pythonClientProcess.HasExited) { LogTimestampedMessage($"Tentativo di terminare il processo Python associato (PID: {_pythonClientProcess.Id})..."); _pythonClientProcess.Kill(true); // Termina l'intero albero del processo if (_pythonClientProcess.WaitForExit(1000)) // Attendi max 1 secondo per conferma { LogTimestampedMessage($"Processo Python (PID: {_pythonClientProcess.Id}) terminato."); } else { WriteLineWarning($"Processo Python (PID: {_pythonClientProcess.Id}) NON terminato entro 1 sec dopo Kill()."); } } else { LogTimestampedMessage($"Processo Python associato (PID: {_pythonClientProcess.Id}) era già terminato."); } } // Intercetta eccezioni specifiche che si verificano se il processo è già terminato o l'accesso è negato catch (Exception ex) when (ex is InvalidOperationException || ex is System.ComponentModel.Win32Exception) { // InvalidOperationException: Processo già terminato o handle non valido // Win32Exception: Accesso negato, ecc. LogTimestampedMessage($"Info: Impossibile terminare processo Python (PID: {_pythonClientProcess.Id}), potrebbe essere già chiuso o mancano permessi: {ex.Message}"); } catch (Exception ex) // Intercetta altri potenziali errori { WriteLineError($"Errore generico durante terminazione processo Python (PID: {_pythonClientProcess.Id}): {ex.Message}"); } finally { // Assicurati di rilasciare l'handle del processo _pythonClientProcess.Dispose(); _pythonClientProcess = null; LogTimestampedMessage("Oggetto Process Python rilasciato."); } } else { LogTimestampedMessage("Nessun handle di processo Python associato da terminare."); } // --- FINE MODIFICA --- // 5. Rilascia CancellationTokenSource if (_cts != null) { try { _cts.Dispose(); } catch {} LogTimestampedMessage("CancellationTokenSource rilasciato."); } LogTimestampedMessage("Cleanup C# Launcher completato."); } // --- Metodi Helper --- private static void Console_CancelKeyPress(object? sender, ConsoleCancelEventArgs e) { Console.WriteLine("\nInterruzione Ctrl+C rilevata. Avvio cleanup..."); e.Cancel = true; // Impedisce la terminazione immediata dell'app C# TriggerShutdown(); } private static void TriggerShutdown() { if (!_cts.IsCancellationRequested) { _cts.Cancel(); // Segnala ai task di fermarsi } } private static void ExitApplication(bool waitForInput = true) { // Rimuove l'handler per evitare problemi se viene premuto Ctrl+C *durante* l'uscita try { Console.CancelKeyPress -= Console_CancelKeyPress; } catch {} if (waitForInput) { Console.Write("\nPremere Invio per uscire..."); Console.ReadLine(); // Attende l'input solo se richiesto } Environment.Exit(0); // Esce con codice 0 (successo) o 1 se c'è stato errore prima } // Lock per output console per evitare sovrapposizioni con il prompt private static readonly object _consoleLock = new object(); // Stampa un messaggio nella console, gestendo il prompt ">" private static void WriteLineWithPromptInternal(string message, ConsoleColor color = ConsoleColor.Gray) { lock (_consoleLock) { try { // Strategia semplice: pulisci la riga, scrivi messaggio, ridisegna prompt int currentLineCursor = Console.CursorTop; Console.SetCursorPosition(0, currentLineCursor); Console.Write(new string(' ', Console.WindowWidth - 1)); // Cancella la riga Console.SetCursorPosition(0, currentLineCursor); Console.ForegroundColor = color; Console.WriteLine(message); // Scrive il messaggio (va a capo) Console.ResetColor(); Console.Write("> "); // Ridisegna il prompt sulla nuova riga } catch (IOException) { // Ignora errori IO sulla console (es. se viene chiusa) } catch (ArgumentOutOfRangeException) { // Ignora errori se la finestra è troppo piccola } } } // Metodi helper per diversi tipi di messaggi private static void WriteLineError(string message) => WriteLineWithPromptInternal($"ERROR: {message}", ConsoleColor.Red); private static void WriteLineWarning(string message) => WriteLineWithPromptInternal($"WARN: {message}", ConsoleColor.Yellow); private static void WriteLineSuccess(string message) => WriteLineWithPromptInternal($"SUCCESS: {message}", ConsoleColor.Green); // Stampa l'output ricevuto dal client Python private static void WriteLineFromPython(string message) => WriteLineWithPromptInternal($"{message}", ConsoleColor.Cyan); // Colore Ciano per output Python // Log interno del launcher C# private static void LogTimestampedMessage(string message) => WriteLineWithPromptInternal($"[Launcher C# {DateTime.Now:HH:mm:ss.fff}] {message}", ConsoleColor.DarkGray); }