using IPClab._5.Models; using System; using System.Collections.Generic; using System.IO; using System.Linq; using System.Net.Sockets; using System.Text; using System.Threading; using System.Threading.Tasks; namespace IPClab._5.Service; public class TcpClientService { private TcpClient? _client; private NetworkStream? _stream; private CancellationTokenSource? _cts; private long _totalBytesReceived; private long _totalBytesSent; public bool IsConnected => _client?.Connected ?? false; public async Task ConnectAsync(string ipAddress, int port, IProgress progress) { if (IsConnected) return; _totalBytesReceived = 0; _totalBytesSent = 0; _client = new TcpClient(); _cts = new CancellationTokenSource(); try { progress.Report(new NetworkEvent { StatusText = "Connecting", Message = $"Connecting to {ipAddress}:{port}..." }); await _client.ConnectAsync(ipAddress, port).ConfigureAwait(false); _stream = _client.GetStream(); // Quality Control Step: Apply structural operation timeouts _stream.ReadTimeout = 5000; _stream.WriteTimeout = 5000; progress.Report(new NetworkEvent { StatusText = "Connected", Message = "Successfully connected to server." }); // Fire and forget background incoming processing loop _ = Task.Run(() => ListenForServerPayloadsAsync(_cts.Token, progress), _cts.Token); } catch (Exception ex) { CleanUp(); progress.Report(new NetworkEvent { StatusText = "Disconnected", Message = $"Connection failed: {ex.Message}", IsError = true }); throw; } } public async Task SendMessageAsync(string message, IProgress progress) { if (!IsConnected || _stream == null) { progress.Report(new NetworkEvent { StatusText = "Disconnected", Message = "Cannot send message. Client is offline.", IsError = true }); return; } try { // Quality Control Requirement: Delimiter-based separation byte[] data = Encoding.UTF8.GetBytes(message + "\n"); await _stream.WriteAsync(data, 0, data.Length).ConfigureAwait(false); _totalBytesSent += data.Length; progress.Report(new NetworkEvent { StatusText = "Connected", Message = $"Sent: {message}", BytesTransferred = _totalBytesSent }); } catch (Exception ex) { progress.Report(new NetworkEvent { StatusText = "Disconnected", Message = $"Send fault occurred: {ex.Message}", IsError = true }); CleanUp(); } } private async Task ListenForServerPayloadsAsync(CancellationToken token, IProgress progress) { using (StreamReader reader = new StreamReader(_stream!, Encoding.UTF8)) { while (!token.IsCancellationRequested && IsConnected) { try { string? incomingPayload = await reader.ReadLineAsync(token).ConfigureAwait(false); if (incomingPayload == null) break; // Clean TCP drop detected long rawBytesCount = Encoding.UTF8.GetByteCount(incomingPayload) + Environment.NewLine.Length; _totalBytesReceived += rawBytesCount; progress.Report(new NetworkEvent { StatusText = "Connected", Message = $"Received: {incomingPayload}", BytesTransferred = _totalBytesReceived }); } catch (OperationCanceledException) when (token.IsCancellationRequested) { break; } catch (Exception ex) { progress.Report(new NetworkEvent { StatusText = "Disconnected", Message = $"Read thread error: {ex.Message}", IsError = true }); break; } } } CleanUp(); progress.Report(new NetworkEvent { StatusText = "Disconnected", Message = "Disconnected from server cleanly." }); } public void Disconnect() { CleanUp(); } private void CleanUp() { _cts?.Cancel(); _stream?.Dispose(); _client?.Dispose(); _stream = null; _client = null; } }