// dotnet new console -o server_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; // Per Task.Run e async using System.Diagnostics; public class SimpleTcpClient { private static TcpClient? _client = null; private static NetworkStream? _stream = null; private static StreamReader? _reader = null; private static StreamWriter? _writer = null; private static CancellationTokenSource _cts = new CancellationTokenSource(); // Per gestire l'arresto private static Process? _serverProcess = null; private const string ServerExecutablePath = @"server_UDP_crypto_protocollo_TLV_multicast_relay_simm_csharp.exe"; public static async Task Main(string[] args) { Console.WriteLine("--- Simple C# TCP Client ---"); Console.Write("Inserisci l'indirizzo del gruppo multicast del server: "); string multicast_group = Console.ReadLine() ?? "224.1.1.1"; // Default se l'input è nullo //string multicast_group = "224.1.1.1"; Console.Write("Inserisci la porta multicast del server: "); //int multicast_port = 5007; int multicast_port; while (!int.TryParse(Console.ReadLine(), out multicast_port) || multicast_port <= 0 || multicast_port > 65535) { Console.WriteLine("Porta non valida. Inserisci un numero tra 1 e 65535."); Console.Write("Inserisci la porta multicast del server: "); } string serverTCP = "127.0.0.1"; Console.Write("Inserisci la porta del server TCP: "); //int tcp_port = 5008; int tcp_port; while (!int.TryParse(Console.ReadLine(), out tcp_port) || tcp_port <= 0 || tcp_port > 65535) { Console.WriteLine("Porta non valida. Inserisci un numero tra 1 e 65535."); Console.Write("Inserisci la porta del server TCP: "); } if (!File.Exists(ServerExecutablePath)) { WriteLineError($"Errore: Eseguibile server non trovato in '{Path.GetFullPath(ServerExecutablePath)}'"); ExitApplication(); return; } string arguments = $"--multicast-group {multicast_group} --multicast-port {multicast_port} --tcp-control-port {tcp_port}"; Console.WriteLine($"Avvio server: \"{ServerExecutablePath}\" {arguments}"); string? workingDir = Path.GetDirectoryName(ServerExecutablePath); ProcessStartInfo startInfo = new ProcessStartInfo { FileName = ServerExecutablePath, Arguments = arguments, WorkingDirectory = workingDir ?? ".", UseShellExecute = true, // Manteniamo per visibilità console Python }; try { _serverProcess = Process.Start(startInfo); if (_serverProcess == null) throw new InvalidOperationException("Fallimento avvio processo server."); Console.WriteLine($"Processo server avviato (PID: {_serverProcess.Id}). Attesa inizializzazione..."); await Task.Delay(2500); } catch (Exception ex) { WriteLineError($"Errore avvio processo server: {ex.Message}"); ExitApplication(); return; } try { // 2. Connessione al Server _client = new TcpClient(); Console.WriteLine($"Tentativo di connessione a {serverTCP}:{tcp_port}..."); await _client.ConnectAsync(serverTCP, tcp_port); // Connessione asincrona if (!_client.Connected) // Verifica aggiuntiva (anche se ConnectAsync lancia eccezioni) { Console.ForegroundColor = ConsoleColor.Red; Console.WriteLine("Connessione fallita (stato non connesso dopo ConnectAsync)."); Console.ResetColor(); return; } Console.ForegroundColor = ConsoleColor.Green; Console.WriteLine("Connesso al server!"); Console.ResetColor(); // Ottieni lo stream e crea reader/writer _stream = _client.GetStream(); _reader = new StreamReader(_stream, Encoding.UTF8, true, 1024, true); // Lascia lo stream aperto alla chiusura del reader _writer = new StreamWriter(_stream, Encoding.UTF8, 1024, true); // Lascia lo stream aperto alla chiusura del writer _writer.AutoFlush = true; // Invia i dati immediatamente // 3. Avvio Thread Ricevitore // Usiamo Task.Run per semplicità nell'uso di async/await nel ricevitore // Passiamo il CancellationToken per permettere l'arresto pulito var receiveTask = Task.Run(() => ReceiveMessages(_cts.Token)); Console.WriteLine("Pronto per inviare messaggi. Digita 'exit' per uscire."); // 4. Loop Invio Messaggi (Main Thread) await SendMessagesLoop(); // 5. Attesa Terminazione Ricevitore (dopo che SendMessagesLoop è uscito) Console.WriteLine("In attesa della terminazione del task ricevitore..."); // Aspetta che il task finisca o un timeout breve await receiveTask.WaitAsync(TimeSpan.FromSeconds(2), CancellationToken.None).ConfigureAwait(false); } catch (SocketException ex) { Console.ForegroundColor = ConsoleColor.Red; Console.WriteLine($"Errore di connessione: {ex.Message} (SocketErrorCode: {ex.SocketErrorCode})"); Console.ResetColor(); } catch (Exception ex) { Console.ForegroundColor = ConsoleColor.Red; Console.WriteLine($"Errore generico: {ex.Message}"); Console.ResetColor(); } finally { // 6. Cleanup Cleanup(); Console.WriteLine("Client terminato."); } } // Loop principale per leggere l'input dell'utente e inviarlo private static async Task SendMessagesLoop() { string? line; while (true) { Console.Write("> "); line = Console.ReadLine(); if (line == null) // Input terminato (es. Ctrl+Z in Windows) { Console.WriteLine("Input terminato."); break; } if (line.Equals("exit", StringComparison.OrdinalIgnoreCase)) { await _writer.WriteLineAsync(line); Console.WriteLine("Comando 'exit' ricevuto. Chiusura in corso..."); await Task.Delay(3000); //ExitApplication(); //return; break; // Esce dal loop di invio } if (_writer != null && _client != null && _client.Connected) { try { // Invia il messaggio al server (aggiunge automaticamente newline) // Assicurati che il tuo server si aspetti messaggi terminati da newline await _writer.WriteLineAsync(line); } catch (IOException ioEx) { Console.ForegroundColor = ConsoleColor.Red; Console.WriteLine($"Errore durante l'invio (connessione persa?): {ioEx.Message}"); Console.ResetColor(); break; // Esce dal loop se l'invio fallisce } catch (ObjectDisposedException) { Console.ForegroundColor = ConsoleColor.Yellow; Console.WriteLine("Impossibile inviare, la connessione è in fase di chiusura."); Console.ResetColor(); break; // Esce se le risorse sono già state rilasciate } catch (Exception ex) { Console.ForegroundColor = ConsoleColor.Red; Console.WriteLine($"Errore inatteso durante l'invio: {ex.Message}"); Console.ResetColor(); break; // Esce in caso di altri errori } } else { Console.ForegroundColor = ConsoleColor.Yellow; Console.WriteLine("Non connesso al server. Impossibile inviare."); Console.ResetColor(); break; // Esce se non siamo (più) connessi } } // Segnala ai task in ascolto (come ReceiveMessages) di fermarsi if (!_cts.IsCancellationRequested) { _cts.Cancel(); } } // Metodo eseguito in un task separato per ricevere messaggi private static async Task ReceiveMessages(CancellationToken token) { Console.WriteLine("[Ricevitore] Avviato."); try { // Assicurati che il reader sia valido prima di entrare nel loop if (_reader == null) { Console.WriteLine("[Ricevitore] Errore: StreamReader non inizializzato."); return; } // Continua a leggere finché la cancellazione non è richiesta e siamo connessi while (!token.IsCancellationRequested && _client != null && _client.Connected) { try { // Legge una riga in modo asincrono, rispettando il token string? message = await _reader.ReadLineAsync(token); if (message != null) { // Stampa il messaggio ricevuto // Usiamo un lock per evitare che l'output si sovrapponga con il prompt ">" lock (Console.Out) { // Sposta il cursore all'inizio della riga corrente, puliscila, // poi scrivi il messaggio ricevuto e ridisegna il prompt. // Questo potrebbe non funzionare perfettamente in tutti i terminali. // int currentCursorLeft = Console.CursorLeft; // Salva posizione cursore // Console.Write("\r" + new string(' ', Console.WindowWidth -1) + "\r"); // Pulisce la riga Console.ForegroundColor = ConsoleColor.Cyan; Console.WriteLine($"[Server]: {message}"); Console.ResetColor(); // Console.Write("> "); // Ridisegna il prompt "> " // Console.SetCursorPosition(currentCursorLeft > 2 ? currentCursorLeft : 2 , Console.CursorTop); // Riposiziona cursore (circa) } } else { // ReadLineAsync ritorna null se lo stream viene chiuso dal server Console.ForegroundColor = ConsoleColor.Yellow; Console.WriteLine("[Ricevitore] Il server ha chiuso la connessione (CTRL-C per uscire)."); Console.ResetColor(); break; // Esce dal loop } } catch (OperationCanceledException) { // Previsto quando _cts.Cancel() viene chiamato Console.WriteLine("[Ricevitore] Operazione annullata (shutdown richiesto)."); break; } catch (IOException ioEx) { // Errore IO, probabilmente connessione interrotta if (!token.IsCancellationRequested) // Mostra solo se non stiamo già chiudendo { Console.ForegroundColor = ConsoleColor.Red; Console.WriteLine($"[Ricevitore] Errore IO (connessione persa?): {ioEx.Message}"); Console.ResetColor(); } break; // Esce dal loop } catch (ObjectDisposedException) { // Le risorse sono state rilasciate (cleanup in corso) if (!token.IsCancellationRequested) Console.WriteLine("[Ricevitore] Risorse rilasciate durante la lettura."); break; // Esce dal loop } catch (Exception ex) { // Altri errori imprevisti if (!token.IsCancellationRequested) { Console.ForegroundColor = ConsoleColor.Red; Console.WriteLine($"[Ricevitore] Errore inatteso: {ex.Message}"); Console.ResetColor(); } break; // Esce dal loop } } } finally { Console.WriteLine("[Ricevitore] Terminato."); // Se il ricevitore termina (es. disconnessione), assicurati che anche il loop di invio si fermi if (!_cts.IsCancellationRequested) { _cts.Cancel(); } } } // Metodo per chiudere le risorse // Metodo per chiudere le risorse private static void Cleanup() { Console.WriteLine("Esecuzione cleanup..."); // NOTA: Rimuovo i Console.ReadLine() di debug che avevi aggiunto // Segnala l'arresto ai task interni C# (come ReceiveMessages) if (!_cts.IsCancellationRequested) { _cts.Cancel(); } // --- Tenta di inviare il comando "exit" al server Python via TCP (Miglior Approccio) --- if (_client != null && _client.Connected && _writer != null) { try { Console.WriteLine("Invio comando 'exit' al server Python via TCP..."); _writer.WriteLine("exit"); _writer.Flush(); Console.WriteLine("Comando 'exit' inviato. Attesa chiusura server..."); // Dai un po' di tempo al server Python per chiudersi da solo Task.Delay(2000).Wait(); // Attendi 2 secondi } catch (Exception ex) { Console.ForegroundColor = ConsoleColor.Yellow; Console.WriteLine($"Avviso: Errore durante l'invio del comando 'exit' via TCP (server potrebbe essere già chiuso?): {ex.Message}"); Console.ResetColor(); } } // -------------------------------------------------------------------- // Chiudi le risorse TCP del client C# (IMPORTANTE: fallo prima di cercare processi) Console.WriteLine("Chiusura risorse TCP client..."); try { _writer?.Close(); _writer = null; } catch { /* Ignora */ } // Azzera dopo chiusura try { _reader?.Close(); _reader = null; } catch { /* Ignora */ } try { _stream?.Close(); _stream = null; } catch { /* Ignora */ } try { _client?.Close(); _client = null; } catch { /* Ignora */ } Console.WriteLine("Risorse TCP client chiuse."); // --- Termina TUTTI i processi server Python trovati per nome (Approccio Robusto) --- Console.WriteLine("Ricerca e terminazione processi server Python residui..."); string targetProcessName = Path.GetFileNameWithoutExtension(ServerExecutablePath); int currentProcessId = Process.GetCurrentProcess().Id; // Ottieni l'ID del processo C# stesso int killedCount = 0; try { // Ottieni tutti i processi con il nome dell'eseguibile (senza .exe) Process[] matchingProcesses = Process.GetProcessesByName(targetProcessName); if (matchingProcesses.Length > 0) { Console.WriteLine($"Trovati {matchingProcesses.Length} processi con nome '{targetProcessName}'. Tentativo di terminazione..."); foreach (Process process in matchingProcesses) { // Sicurezza aggiuntiva: non tentare di terminare il processo C# stesso if (process.Id == currentProcessId) { Console.WriteLine($"Ignorato processo corrente (PID: {process.Id})."); continue; } try { // Verifica se è ancora vivo prima di Kill if (!process.HasExited) { Console.WriteLine($"Tentativo di terminare il processo '{process.ProcessName}' (PID: {process.Id})..."); process.Kill(true); // Usa true per terminare anche eventuali processi figli diretti process.WaitForExit(500); // Breve attesa per la terminazione if (process.HasExited) { Console.WriteLine($"Processo (PID: {process.Id}) terminato."); killedCount++; } else { WriteLineWarning($"Processo (PID: {process.Id}) non terminato dopo Kill()."); } } else { Console.WriteLine($"Processo (PID: {process.Id}) era già terminato prima del Kill."); } } catch (InvalidOperationException) { // Il processo potrebbe essere terminato tra GetProcessesByName e l'accesso alle sue proprietà/metodi Console.WriteLine($"Processo (PID: {process.Id}) non più accessibile (già terminato?)."); } catch (Exception ex) { WriteLineError($"Errore durante il tentativo di terminare il processo (PID: {process.Id}): {ex.Message}"); } finally { // Rilascia l'oggetto Process process.Dispose(); } } Console.WriteLine($"Terminati {killedCount} processi esterni con nome '{targetProcessName}'."); } else { Console.WriteLine($"Nessun processo trovato con nome '{targetProcessName}'."); } } catch (Exception ex) // Errore durante GetProcessesByName stesso { WriteLineError($"Errore durante la ricerca dei processi ('{targetProcessName}'): {ex.Message}"); } finally { // L'oggetto _serverProcess originale (il bootloader) dovrebbe essere // terminato da questo loop se aveva lo stesso nome. // Possiamo comunque fare Dispose se non è null. if (_serverProcess != null) { try { _serverProcess.Dispose(); } catch {} _serverProcess = null; Console.WriteLine("Oggetto Process originale (_serverProcess) rilasciato (se esisteva)."); } } // -------------------------------------------------------------------------------- // Rilascia CancellationTokenSource if (_cts != null) { try { _cts.Dispose(); } catch {} Console.WriteLine("CancellationTokenSource rilasciato."); } Console.WriteLine("Cleanup completato."); } // --- Metodi Helper --- (Invariati) private static void Console_CancelKeyPress(object? sender, ConsoleCancelEventArgs e) { /* ... */ } private static void TriggerShutdown() { if (!_cts.IsCancellationRequested) { _cts.Cancel(); // Segnala ai task di fermarsi } // Non chiamare Cleanup() direttamente qui, verrà chiamato nel finally del Main } private static void ExitApplication(bool waitForInput = true) { if (waitForInput) { Console.WriteLine("\nPremere Invio per uscire..."); Console.ReadLine(); } Environment.Exit(1); // Esce con codice di errore } private static readonly object _consoleLock = new object(); private static void WriteLineWithPrompt(string message, ConsoleColor color = ConsoleColor.Gray) { lock (_consoleLock) { // Salva posizione cursore corrente e input parziale (se possibile/necessario) // ... (logica complessa per preservare l'input utente, omessa per semplicità) // Pulisce la riga corrente e scrive il messaggio Console.Write("\r" + new string(' ', Console.WindowWidth -1) + "\r"); Console.ForegroundColor = color; Console.WriteLine(message); Console.ResetColor(); // Ridisegna il prompt e l'input parziale Console.Write("> "); // ... (ripristina input parziale) } } private static void WriteLineError(string message) => WriteLineWithPrompt(message, ConsoleColor.Red); private static void WriteLineWarning(string message) => WriteLineWithPrompt(message, ConsoleColor.Yellow); private static void WriteLineSuccess(string message) => WriteLineWithPrompt(message, ConsoleColor.Green); private static void WriteLineServerMessage(string message) => WriteLineWithPrompt($"[Server] {message}", ConsoleColor.Cyan); }