131 lines
4.4 KiB
C#
131 lines
4.4 KiB
C#
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<NetworkEvent> 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<NetworkEvent> 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<NetworkEvent> 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;
|
|
}
|
|
} |