128 lines
4.7 KiB
C#
128 lines
4.7 KiB
C#
using IPClab._4.Models;
|
|
using System;
|
|
using System.Collections.Generic;
|
|
using System.IO;
|
|
using System.Linq;
|
|
using System.Net;
|
|
using System.Net.Sockets;
|
|
using System.Text;
|
|
using System.Threading;
|
|
using System.Threading.Tasks;
|
|
|
|
namespace IPClab._4.Services;
|
|
|
|
public class TcpServerService
|
|
{
|
|
private TcpListener? _listener;
|
|
private CancellationTokenSource? _cts;
|
|
private long _totalBytesReceived;
|
|
private long _totalBytesSent;
|
|
|
|
public bool IsListening { get; private set; }
|
|
|
|
public async Task StartAsync(string ipAddress, int port, IProgress<NetworkEvent> progress)
|
|
{
|
|
if (IsListening) return;
|
|
|
|
_totalBytesReceived = 0;
|
|
_totalBytesSent = 0;
|
|
_cts = new CancellationTokenSource();
|
|
|
|
try
|
|
{
|
|
IPAddress localAddr = IPAddress.Parse(ipAddress);
|
|
_listener = new TcpListener(localAddr, port);
|
|
_listener.Start();
|
|
IsListening = true;
|
|
|
|
progress.Report(new NetworkEvent { StatusText = "Listening", Message = $"Server started on {ipAddress}:{port}" });
|
|
|
|
// Run the acceptance loop on a background thread task
|
|
_ = Task.Run(() => AcceptClientsAsync(_cts.Token, progress), _cts.Token);
|
|
}
|
|
catch (Exception ex)
|
|
{
|
|
IsListening = false;
|
|
progress.Report(new NetworkEvent { StatusText = "Faulted", Message = $"Failed to start server: {ex.Message}", IsError = true });
|
|
throw;
|
|
}
|
|
}
|
|
|
|
private async Task AcceptClientsAsync(CancellationToken token, IProgress<NetworkEvent> progress)
|
|
{
|
|
while (!token.IsCancellationRequested)
|
|
{
|
|
try
|
|
{
|
|
// Accept incoming connection
|
|
TcpClient client = await _listener!.AcceptTcpClientAsync(token).ConfigureAwait(false);
|
|
progress.Report(new NetworkEvent { StatusText = "Connected", Message = "Client connected to server." });
|
|
|
|
// Handle this client session
|
|
await HandleClientSessionAsync(client, token, progress).ConfigureAwait(false);
|
|
}
|
|
catch (OperationCanceledException) when (token.IsCancellationRequested)
|
|
{
|
|
break;
|
|
}
|
|
catch (Exception ex)
|
|
{
|
|
progress.Report(new NetworkEvent { StatusText = "Listening", Message = $"Error accepting client: {ex.Message}", IsError = true });
|
|
}
|
|
}
|
|
}
|
|
|
|
private async Task HandleClientSessionAsync(TcpClient client, CancellationToken token, IProgress<NetworkEvent> progress)
|
|
{
|
|
using (client)
|
|
using (NetworkStream stream = client.GetStream())
|
|
{
|
|
// Quality Control Step: Enforce timeout boundaries
|
|
stream.ReadTimeout = 5000;
|
|
stream.WriteTimeout = 5000;
|
|
|
|
using (StreamReader reader = new StreamReader(stream, Encoding.UTF8))
|
|
{
|
|
while (!token.IsCancellationRequested && client.Connected)
|
|
{
|
|
try
|
|
{
|
|
// Delimiter-based Framing: Read line until '\n' boundary
|
|
string? receivedLine = await reader.ReadLineAsync(token).ConfigureAwait(false);
|
|
|
|
// TCP Disconnect Signal: stream returns null/0 bytes
|
|
if (receivedLine == null) break;
|
|
|
|
long messageBytes = Encoding.UTF8.GetByteCount(receivedLine) + Environment.NewLine.Length;
|
|
_totalBytesReceived += messageBytes;
|
|
|
|
progress.Report(new NetworkEvent
|
|
{
|
|
StatusText = "Data Received",
|
|
Message = receivedLine,
|
|
BytesTransferred = _totalBytesReceived
|
|
});
|
|
|
|
// Lab 6 Echo/Response confirmation logic
|
|
byte[] responseBytes = Encoding.UTF8.GetBytes($"ACK: {receivedLine}\n");
|
|
await stream.WriteAsync(responseBytes, 0, responseBytes.Length, token).ConfigureAwait(false);
|
|
_totalBytesSent += responseBytes.Length;
|
|
}
|
|
catch (Exception ex)
|
|
{
|
|
progress.Report(new NetworkEvent { StatusText = "Disconnected", Message = $"Session terminated: {ex.Message}", IsError = true });
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
progress.Report(new NetworkEvent { StatusText = "Listening", Message = "Client disconnected clean." });
|
|
}
|
|
|
|
public void Stop()
|
|
{
|
|
_cts?.Cancel();
|
|
_listener?.Stop();
|
|
IsListening = false;
|
|
}
|
|
} |