// dotnet new console -o client_UDP_crypto_protocollo_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) // compatibile Windows 11 // compatibile .NET SDK 9.0 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 = @"client_UDP_crypto_protocollo_TLV_csharp.exe"; public static async Task Main(string[] args) { Console.WriteLine("--- Simple C# TCP Client ---"); Console.Write("Inserisci l'indirizzo IP del server UDP remoto: "); string serverUdp = Console.ReadLine() ?? "127.0.0.1"; // Default a localhost se l'input è nullo Console.Write("Inserisci la porta del server UDP: "); int portUdp; while (!int.TryParse(Console.ReadLine(), out portUdp) || portUdp <= 0 || portUdp > 65535) { Console.WriteLine("Porta non valida. Inserisci un numero tra 1 e 65535."); Console.Write("Inserisci la porta del server UDP: "); } // 1. Richiesta IP e Porta //Console.Write("Inserisci l'indirizzo IP del server TCP: "); //string TcpAddr = Console.ReadLine() ?? "127.0.0.1"; // Default a localhost se l'input è nullo string TcpAddr = "127.0.0.1"; Console.Write("Inserisci la porta del server TCP: "); int TcpPort; while (!int.TryParse(Console.ReadLine(), out TcpPort) || TcpPort <= 0 || TcpPort > 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 = $"--udp-server-host {serverUdp} --udp-server-port {portUdp} --tcp-control-port {TcpPort}"; 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 {TcpAddr}:{TcpPort}..."); await _client.ConnectAsync(TcpAddr, TcpPort); // 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); 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 private static void Cleanup() { Console.WriteLine("Esecuzione cleanup..."); // Segnala l'arresto se non già fatto if (!_cts.IsCancellationRequested) { _cts.Cancel(); } // Chiudi writer, reader, stream e client in ordine inverso di creazione // I try-catch vuoti o ?.Close() ignorano errori se già chiusi/nulli try { _writer?.Close(); } catch { /* Ignora */ } try { _reader?.Close(); } catch { /* Ignora */ } try { _stream?.Close(); } catch { /* Ignora */ } try { _client?.Close(); } catch { /* Ignora */ } // Rilascia CancellationTokenSource _cts.Dispose(); Console.WriteLine("Risorse rilasciate."); } // --- Metodi Helper --- (Invariati) private static void Console_CancelKeyPress(object? sender, ConsoleCancelEventArgs e) { /* ... */ } private static void TriggerShutdown() { /* ... */ } private static void ExitApplication(bool waitForInput = true) { /* ... */ } private static readonly object _consoleLock = new object(); private static void WriteLineWithPrompt(string message, ConsoleColor color = ConsoleColor.Gray) { /* ... */ } 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); }