diff --git a/README.md b/README.md index ce68427..8fc32b1 100644 --- a/README.md +++ b/README.md @@ -1,215 +1,932 @@ - -# EonaCat.Connections -.NET Framework 4.8+ / .NET (Core) compatible library providing high-throughput TCP/UDP -servers and clients with optional TLS (for TCP) and optional application-layer encryption (TCP/UDP). - -## Design goals: - - High performance and low latency - - Scalable to tens of thousands of concurrent connections - - Scalable socket I/O via SocketAsyncEventArgs (SAEA) for raw TCP and UDP - - TLS (SSL) over TCP using SslStream (built-in) - - Optional encryption (AES-CBC + PBKDF2_SHA256) for TCP/UDP payloads - - Minimal allocations, event-driven callbacks - -#### - For highest throughput, run x64, enable LargePage, set appropriate Socket options and OS registry tuning. - -## Generate self-signed certificate for TLS (TCP): -### Run as Administrator - $cert = New-SelfSignedCertificate ` - -DnsName "localhost" ` - -CertStoreLocation "Cert:\LocalMachine\My" ` - -KeyExportPolicy Exportable ` - -NotAfter (Get-Date).AddYears(5) ` - -FriendlyName "EonaCat Connections Test Certificate" - - $password = ConvertTo-SecureString -String "p@ss" -Force -AsPlainText - - Export-PfxCertificate ` - -Cert "Cert:\LocalMachine\My\$($cert.Thumbprint)" ` - -FilePath "C:\temp\server.pfx" ` - -Password $password - -#### This will create a self-signed certificate with the password 'p@ss' in the folder C:\temp\server.pfx. - - -## Server example: - - using EonaCat.Connections.Models; - - namespace EonaCat.Connections.Server.Example - { - // This file is part of the EonaCat project(s) which is released under the Apache License. - // See the LICENSE file or go to https://EonaCat.com/license for full license details. - - public class Program - { - private static NetworkServer _server; - - public static void Main(string[] args) - { - CreateServerAsync().ConfigureAwait(false); - - while (true) - { - Console.Write("Enter message to send (or 'exit' to quit): "); - var message = Console.ReadLine(); - if (!string.IsNullOrEmpty(message) && message.Equals("exit", StringComparison.OrdinalIgnoreCase)) - { - _server.Stop(); - _server.Dispose(); - Console.WriteLine("Server stopped."); - break; - } - - if (!string.IsNullOrEmpty(message)) - { - _server.BroadcastAsync(message).ConfigureAwait(false); - } - } - } - - private static async Task CreateServerAsync() - { - var config = new Configuration - { - Protocol = ProtocolType.TCP, - Port = 1111, - UseSsl = true, - UseAesEncryption = true, - MaxConnections = 100000, - AesPassword = "EonaCat.Connections.Password", - Certificate = new System.Security.Cryptography.X509Certificates.X509Certificate2("server.pfx", "p@ss") - }; - - _server = new NetworkServer(config); - - // Subscribe to events - _server.OnConnected += (sender, e) => - Console.WriteLine($"Client {e.ClientId} connected from {e.RemoteEndPoint}"); - - _server.OnConnectedWithNickname += (sender, e) => - Console.WriteLine($"Client {e.ClientId} connected with nickname: {e.Nickname}"); - - _server.OnDataReceived += async (sender, e) => - { - if (e.HasNickname) - { - Console.WriteLine($"Received from {e.Nickname}: {(e.IsBinary ? $"{e.Data.Length} bytes" : e.StringData)}"); - } - else - { - Console.WriteLine($"Received from {e.ClientId}: {(e.IsBinary ? $"{e.Data.Length} bytes" : e.StringData)}"); - } - - // Echo back the message - if (e.IsBinary) - { - await _server.SendToClientAsync(e.ClientId, e.Data); - } - else - { - await _server.SendToClientAsync(e.ClientId, $"Echo: {e.StringData}"); - } - }; - - _server.OnDisconnected += (sender, e) => - { - if (e.HasNickname) - { - Console.WriteLine($"Client {e.Nickname} disconnected"); - } - else - { - Console.WriteLine($"Client {e.ClientId} disconnected"); - } - }; - - await _server.StartAsync(); - } - } - } - -## Client example: - - using EonaCat.Connections.Models; - - namespace EonaCat.Connections.Client.Example - { - // This file is part of the EonaCat project(s) which is released under the Apache License. - // See the LICENSE file or go to https://EonaCat.com/license for full license details. - - public class Program - { - private static NetworkClient _client; - - public static async Task Main(string[] args) - { - await CreateClientAsync().ConfigureAwait(false); - - while (true) - { - if (!_client.IsConnected) - { - await Task.Delay(1000).ConfigureAwait(false); - continue; - } - - Console.Write("Enter message to send (or 'exit' to quit): "); - var message = Console.ReadLine(); - - if (!string.IsNullOrEmpty(message) && message.Equals("exit", StringComparison.OrdinalIgnoreCase)) - { - await _client.DisconnectAsync().ConfigureAwait(false); - break; - } - - if (!string.IsNullOrEmpty(message)) - { - await _client.SendAsync(message).ConfigureAwait(false); - } - } - } - - private static async Task CreateClientAsync() - { - var config = new Configuration - { - Protocol = ProtocolType.TCP, - Host = "127.0.0.1", - Port = 1111, - UseSsl = true, - UseAesEncryption = true, - AesPassword = "EonaCat.Connections.Password", - Certificate = new System.Security.Cryptography.X509Certificates.X509Certificate2("client.pfx", "p@ss"), - }; - - _client = new NetworkClient(config); - - _client.OnGeneralError += (sender, e) => - Console.WriteLine($"Error: {e.Message}"); - - // Subscribe to events - _client.OnConnected += async (sender, e) => - { - Console.WriteLine($"Connected to server at {e.RemoteEndPoint}"); - - // Set nickname - await _client.SetNicknameAsync("TestUser"); - - // Send a message - await _client.SendAsync("Hello server!"); - }; - - _client.OnDataReceived += (sender, e) => - Console.WriteLine($"Server says: {(e.IsBinary ? $"{e.Data.Length} bytes" : e.StringData)}"); - - _client.OnDisconnected += (sender, e) => - { - Console.WriteLine("Disconnected from server"); - }; - - Console.WriteLine("Connecting to server..."); - await _client.ConnectAsync(); - } - } - } \ No newline at end of file +# EonaCat.Connections + +A high-performance, production-ready .NET Framework 4.8+ / .NET 8 compatible library for building scalable TCP/UDP servers and clients with optional TLS encryption and application-layer AES encryption. + +## Features + +### Core Capabilities +- **High Performance**: Event-driven, asynchronous architecture with minimal allocations +- **Massive Scalability**: Handles tens of thousands of concurrent connections using SocketAsyncEventArgs (SAEA) +- **Dual Protocol Support**: TCP and UDP with independent or mixed configurations +- **TLS/SSL Encryption**: Secure TCP connections using SslStream with certificate support +- **Application-Layer Encryption**: AES-CBC + PBKDF2_SHA256 encryption for both TCP and UDP payloads +- **Low Latency**: Optimized for millisecond-level response times + +### Advanced Features +- **Auto-Reconnection**: Automatic client reconnection with configurable retry logic and exponential backoff +- **Heartbeat/Ping-Pong**: Keep-alive mechanism to detect dead connections +- **Idle Detection**: Automatic detection and handling of idle connections with configurable timeout +- **Message Framing**: Support for delimiter-based, length-prefixed, or raw message modes +- **Request-Response Patterns**: Send message and wait for response with timeout support +- **Broadcast Messaging**: Send to all connected clients simultaneously +- **Client-to-Client Messaging**: Direct peer communication through the server +- **Nicknames**: Optional client identification system +- **Health API**: Lightweight REST API for monitoring server health and statistics +- **Status Reporting**: Auto-generated HTML status pages showing connected clients and throughput +- **Network Monitoring**: Built-in network health monitoring and outage detection +- **SSL Diagnostics**: Detailed SSL handshake diagnostics and performance metrics +- **Connection Management**: Full lifecycle management with detailed disconnect reasons + +## Design Goals + +- High performance and low latency +- Scalable to tens of thousands of concurrent connections +- Scalable socket I/O via SocketAsyncEventArgs (SAEA) for raw TCP and UDP +- TLS (SSL) over TCP using SslStream (built-in) +- Optional encryption (AES-CBC + PBKDF2_SHA256) for TCP/UDP payloads +- Minimal allocations, event-driven callbacks +- For highest throughput, run x64, enable LargePage, set appropriate Socket options and OS registry tuning + +--- + +## Configuration + +The `Configuration` class provides extensive customization options: + +### Connection Settings +- **Protocol**: `TCP` or `UDP` (default: TCP) +- **Host**: Server address for clients (default: 127.0.0.1) +- **Port**: Network port (default: 8080) +- **MaxConnections**: Maximum concurrent connections (default: 100000) +- **ConnectionTimeout**: Timeout for connection attempts (default: 30s) + +### TLS/SSL Settings +- **Certificate**: X509Certificate2 for server (enables TLS when set) +- **AdditionalCertificates**: Additional certificates for chain validation +- **IsSelfSignedEnabled**: Allow self-signed certificates (testing only) +- **CheckCertificateRevocation**: Validate certificate revocation status +- **MutuallyAuthenticate**: Require mutual TLS authentication +- **UseSsl**: Read-only property (true if Certificate is set) +- **EnableSslDiagnostics**: Enable detailed SSL handshake diagnostics +- **AllowTlsRenegotiation**: Allow TLS renegotiation + +### SSL Retry & Handshake Configuration +- **SSLMaxRetries**: Maximum SSL handshake retry attempts (0 = unlimited, default: 0) +- **SSLTimeoutInSeconds**: SSL handshake timeout (default: 35) +- **SSLRetryDelayInSeconds**: Delay between SSL retries (default: 5) +- **UseExponentialBackoffForSslRetries**: Enable exponential backoff (default: false) +- **SSLRetryDelayMaxSeconds**: Maximum retry delay when exponential backoff enabled (default: 60) + +### Encryption Settings +- **UseAesEncryption**: Enable AES-CBC encryption (default: false) +- **AesPassword**: Password for AES key derivation +- **UseBigEndian**: Use big-endian byte order (default: false) + +### Keep-Alive & Heartbeat +- **EnableKeepAlive**: Enable TCP keep-alive probes (default: true) +- **KeepAliveTimeSeconds**: Time before first keep-alive probe (default: 60) +- **KeepAliveIntervalSeconds**: Interval between probes (default: 10) +- **KeepAliveRetryCount**: Unacked probes before disconnect (default: 10) +- **EnableHeartbeat**: Enable ping-pong heartbeat (default: false) +- **HeartbeatIntervalSeconds**: Heartbeat interval (default: 5) +- **EnablePingPongLogs**: Log ping-pong messages (default: false) +- **DisconectOnMissedPong**: Disconnect on missed pong response (default: false) + +### Message Framing +- **MessageFraming**: `None`, `Delimiter`, or `LengthPrefixed` (default: None) +- **Delimiter**: Message separator bytes (default: %) +- **LengthPrefixedLength**: Prefix byte count for length-prefixed mode (default: 4) +- **MAX_MESSAGE_SIZE**: Maximum message size (default: 100 MB) + +### Idle Detection & Timeout +- **IdleTimeoutSeconds**: Idle connection timeout, 0 = disabled (default: 30) + +### Socket Configuration +- **BufferSize**: Socket buffer size (default: ExtraLarge ~65K) +- **EnableNagle**: Enable Nagle's algorithm (default: false) +- **EnableRST**: Send RST flag on close (default: false) + +### Auto-Reconnection (Client) +- **EnableAutoReconnect**: Auto-reconnect on disconnect (default: true) +- **ReconnectDelayInSeconds**: Delay before reconnection attempt (default: 5) +- **MaxReconnectAttempts**: Max reconnect attempts, 0 = unlimited (default: 0) + +### Logging & Diagnostics +- **EnableConnectionDebugLogs**: Log connection lifecycle events (default: false) + +### HTML Status & Health API +- **EnableAutoHtmlReports**: Generate periodic HTML error reports (default: false) +- **HtmlReportOutputDirectory**: Report output directory (default: ./reports) +- **HtmlReportIntervalSeconds**: Report generation interval (default: 60) +- **EnableServerStatusPage**: Generate HTML status page with client list (default: false) +- **ServerStatusPageIntervalSeconds**: Status page update interval (default: 5) +- **EnableHealthApi**: Start lightweight REST health API (default: false) +- **HealthApiPort**: Health API port, 0 = random (default: 0) +- **HealthApiBindAddress**: Health API bind address (default: 127.0.0.1) + +### Certificate Management +- **TrustedThumbprints**: List of trusted certificate thumbprints for validation +- **CheckAgainstInternalTrustedCertificates**: Validate against Windows certificate store (default: true) + +--- + +## NetworkServer API + +### Events + +```csharp +// Connection events +public event EventHandler OnConnected; +public event EventHandler OnConnectedWithNickname; +public event EventHandler OnDisconnected; + +// Data events +public event EventHandler OnDataReceived; + +// Error events +public event EventHandler OnSslError; +public event EventHandler OnEncryptionError; +public event EventHandler OnGeneralError; +public event EventHandler OnSocketError; + +// Heartbeat events +public event EventHandler OnClientPingResponse; +public event EventHandler OnPongMissed; + +// Idle/Timeout events +public event EventHandler OnIdleTimeout; + +// Logging +public event EventHandler OnLog; +``` + +### Properties + +```csharp +// Server Status +public bool IsStarted { get; } +public int Port { get; } +public int ActiveConnections { get; } +public int MaxConnections { get; } +public ProtocolType Protocol { get; } + +// Security Status +public bool IsSecure { get; } // TLS or AES enabled +public bool IsEncrypted { get; } // AES enabled + +// Statistics +public long DroppedPackets { get; } +public long DroppedConnections { get; } +public long TotalConnections { get; } +public long BytesSent { get; } +public long BytesReceived { get; } +public long MessagesSent { get; } +public long MessagesReceived { get; } +public double MessagesPerSecond { get; } +public TimeSpan Uptime { get; } +public DateTime StartTime { get; } +public DateTime LastDataSent { get; } +public DateTime LastDataReceived { get; } + +// Status Pages +public ServerStatusPage ServerStatus { get; } +public SocketStatusPage StatusPage { get; } +public HealthApiServer HealthApi { get; } + +// Debug +public bool DEBUG_DATA_SEND { get; set; } +public bool DEBUG_DATA_RECEIVED { get; set; } +``` + +### Core Methods + +```csharp +// Lifecycle +public Task StartAsync(); +public void Stop(); +public async Task StopAsync(); +public Task StartTcpServerAsync(); +public async Task StartUdpServerAsync(CancellationToken token); +public async Task StopTcpServerAsync(); + +// Client Management +public Dictionary GetClients(); +public List GetClient(string clientId); +public async Task DisconnectClientAsync(string clientId, DisconnectReason reason, Exception? exception); + +// Messaging - Send to Specific Client +public async Task SendToClientAsync(string clientId, byte[] data); +public async Task SendToClientAsync(string clientId, string message); + +// Messaging - Client-to-Client +public async Task SendFromClientToClientAsync(string fromClientId, string toClientId, byte[] data); +public async Task SendFromClientToClientAsync(string fromClientId, string toClientId, string message); + +// Broadcast Messaging +public async Task BroadcastAsync(byte[] data); +public async Task BroadcastAsync(string message); + +// Request-Response Patterns +public async Task SendToClientAndWaitForResponseAsync( + string clientId, byte[] data, TimeSpan? timeout = null); +public async Task SendToClientAndWaitForResponseAsync( + string clientId, string message, TimeSpan? timeout = null); + +public async Task SendFromClientToClientAndWaitForResponseAsync( + string fromClientId, string toClientId, byte[] data, TimeSpan? timeout = null); +public async Task SendFromClientToClientAndWaitForResponseAsync( + string fromClientId, string toClientId, string message, TimeSpan? timeout = null); + +public async Task> BroadcastAndWaitForResponsesAsync( + byte[] data, TimeSpan? timeout = null); +public async Task> BroadcastAndWaitForResponsesAsync( + string message, TimeSpan? timeout = null); + +// Configuration +public void SetSeparator(string separator); +public void SetSeparator(byte[] separator); + +// Statistics +public Stats GetStats(); + +// Cleanup +public void Dispose(); +``` + +--- + +## NetworkClient API + +### Events + +```csharp +// Connection events +public event EventHandler OnConnected; +public event EventHandler OnNicknameSend; +public event EventHandler OnDisconnected; + +// Data events +public event EventHandler OnDataReceived; + +// Error events +public event EventHandler OnSslError; +public event EventHandler OnEncryptionError; +public event EventHandler OnGeneralError; +public event EventHandler OnSocketError; + +// Heartbeat events +public event EventHandler OnPingResponse; +public event EventHandler OnPongResponse; + +// Idle/Timeout events +public event EventHandler OnIdleTimeout; + +// Logging +public event EventHandler OnLog; +``` + +### Properties + +```csharp +// Connection Status +public bool IsConnected { get; set; } +public int Port { get; } + +// Connection Time Metrics +public DateTime ConnectionTime { get; } +public TimeSpan Uptime { get; } +public DateTime LastActive { get; } +public int IdleTimeInSeconds(); +public int IdleTimeInMinutes(); +public int IdleTimeInHours(); +public int IdleTimeInDays(); +public int ConnectedTimeInSeconds(); +public int ConnectedTimeInMinutes(); +public int ConnectedTimeInHours(); +public int ConnectedTimeInDays(); + +// Security Status +public bool IsSecure { get; } // TLS or AES enabled +public bool IsEncrypted { get; } // AES enabled +public bool IsTcp { get; } + +// Statistics +public long BytesSent { get; } +public long BytesReceived { get; } +public long MessagesSent { get; } +public long MessagesReceived { get; } + +// Heartbeat +public bool DisconnectOnMissedPong { get; set; } +public bool EnableKeepAlive { get; set; } + +// Auto-Reconnect +public bool IsAutoConnectStarted { get; } +public bool IsIdleTimeoutTriggered { get; } + +// Debug +public bool DEBUG_DATA_SEND { get; set; } +public bool DEBUG_DATA_RECEIVED { get; set; } +``` + +### Core Methods + +```csharp +// Connection Lifecycle +public async Task ConnectAsync(); +public async Task DisconnectAsync(); +public async Task DisconnectDueTimeoutAsync(); + +// Messaging +public async Task SendAsync(byte[] data); +public async Task SendAsync(string message); + +// Nickname +public async Task SendNicknameAsync(string nickname); + +// Request-Response +public async Task SendAndWaitForResponseAsync( + byte[] data, TimeSpan? timeout = null); +public async Task SendAndWaitForResponseAsync( + string message, TimeSpan? timeout = null); +public async Task SendNicknameAndWaitForResponseAsync( + string nickname, TimeSpan? timeout = null); + +// Cleanup +public void Dispose(); +``` + +--- + +## Message Framing Modes + +Three message framing modes are supported: + +### 1. None (Default) +Raw bytes sent as-is, no frame boundaries marked: +```csharp +config.MessageFraming = FramingMode.None; +``` + +### 2. Delimiter +Messages separated by a delimiter byte sequence (default: %): +```csharp +config.MessageFraming = FramingMode.Delimiter; +config.Delimiter = Encoding.UTF8.GetBytes("%"); +``` + +### 3. Length-Prefixed +Messages prefixed with their length as bytes: +```csharp +config.MessageFraming = FramingMode.LengthPrefixed; +config.LengthPrefixedLength = 4; // 4-byte prefix +``` + +--- + +## SSL/TLS Certificate Setup + +### Generate self-signed certificate for TLS (TCP): +Run the following in PowerShell as Administrator: + +```powershell +$cert = New-SelfSignedCertificate ` + -DnsName "localhost" ` + -CertStoreLocation "Cert:\LocalMachine\My" ` + -KeyExportPolicy Exportable ` + -NotAfter (Get-Date).AddYears(5) ` + -FriendlyName "EonaCat Connections Test Certificate" + +$password = ConvertTo-SecureString -String "p@ss" -Force -AsPlainText + +Export-PfxCertificate ` + -Cert "Cert:\LocalMachine\My\$($cert.Thumbprint)" ` + -FilePath "C:\temp\server.pfx" ` + -Password $password +``` + +This creates a self-signed certificate with password `p@ss` in `C:\temp\server.pfx`. + +--- + +## Advanced Features + +### Auto-Reconnection +The client automatically reconnects on disconnect: +```csharp +config.EnableAutoReconnect = true; // Enable auto-reconnect +config.ReconnectDelayInSeconds = 5; // Delay before retry +config.MaxReconnectAttempts = 0; // 0 = unlimited attempts +``` + +### Heartbeat (Ping-Pong) +Keep connections alive and detect dead connections: +```csharp +config.EnableHeartbeat = true; +config.HeartbeatIntervalSeconds = 5; +config.DisconectOnMissedPong = true; // Disconnect if pong not received +``` + +### Idle Detection +Automatically disconnect idle connections: +```csharp +config.IdleTimeoutSeconds = 30; // 0 = disabled +``` + +### Request-Response Patterns +Send a message and wait for a response with timeout: +```csharp +// Server sends message and waits for client response +var response = await server.SendToClientAndWaitForResponseAsync( + clientId, "question", TimeSpan.FromSeconds(5)); + +if (response != null) +{ + Console.WriteLine($"Got response: {response.StringData}"); +} + +// Client sends message and waits for server response +var response = await client.SendAndWaitForResponseAsync( + "answer", TimeSpan.FromSeconds(5)); +``` + +### Health API +Enable a lightweight REST API for server monitoring: +```csharp +config.EnableHealthApi = true; +config.HealthApiPort = 0; // Random port +config.HealthApiBindAddress = IPAddress.Any; // For Docker/containers + +// After server starts, access the API: +// http://localhost:{HealthApi.Port}/health +// http://localhost:{HealthApi.Port}/stats +``` + +### Status Pages +Auto-generate HTML status reports: +```csharp +config.EnableServerStatusPage = true; +config.ServerStatusPageIntervalSeconds = 5; + +config.EnableAutoHtmlReports = true; +config.HtmlReportOutputDirectory = "./reports"; +config.HtmlReportIntervalSeconds = 60; +``` + +### SSL Diagnostics +Enable detailed SSL handshake diagnostics: +```csharp +config.EnableSslDiagnostics = true; +// Access via: server.StatusPage.SslMetrics +``` + +### Network Monitoring +Monitor network health and detect outages: +```csharp +var monitor = new NetworkMonitor(); +monitor.OnNetworkOutageDetected += (s, e) => +{ + Console.WriteLine($"Network outage detected: {e.Duration}"); +}; +``` + +--- + +## Server Example + +```csharp +using EonaCat.Connections; +using EonaCat.Connections.Models; +using System.Security.Cryptography.X509Certificates; + +namespace EonaCat.Connections.Server.Example +{ + public class Program + { + private static NetworkServer _server; + + public static void Main(string[] args) + { + CreateServerAsync().GetAwaiter().GetResult(); + + while (true) + { + Console.Write("Enter message to broadcast (or 'exit' to quit): "); + var message = Console.ReadLine(); + + if (!string.IsNullOrEmpty(message) && message.Equals("exit", StringComparison.OrdinalIgnoreCase)) + { + _server.StopAsync().GetAwaiter().GetResult(); + _server.Dispose(); + Console.WriteLine("Server stopped."); + break; + } + + if (!string.IsNullOrEmpty(message)) + { + _server.BroadcastAsync(message).GetAwaiter().GetResult(); + } + } + } + + private static async Task CreateServerAsync() + { + var config = new Configuration + { + Protocol = ProtocolType.TCP, + Port = 1111, + Certificate = new X509Certificate2("server.pfx", "p@ss"), // Enables TLS + UseAesEncryption = true, + AesPassword = "EonaCat.Connections.Password", + MaxConnections = 100000, + EnableHeartbeat = true, + HeartbeatIntervalSeconds = 5, + IdleTimeoutSeconds = 30, + EnableHealthApi = true, + HealthApiPort = 8888, + }; + + _server = new NetworkServer(config); + + // Connection events + _server.OnConnected += (sender, e) => + Console.WriteLine($"[CONNECTED] Client {e.ClientId} from {e.RemoteEndPoint}"); + + _server.OnConnectedWithNickname += (sender, e) => + Console.WriteLine($"[NICKNAME] Client {e.ClientId} identified as: {e.Nickname}"); + + // Data reception + _server.OnDataReceived += async (sender, e) => + { + var clientName = e.HasNickname ? e.Nickname : e.ClientId; + Console.WriteLine($"[DATA] From {clientName}: {(e.IsBinary ? $"{e.Data.Length} bytes" : e.StringData)}"); + + // Echo response + if (e.IsBinary) + { + await _server.SendToClientAsync(e.ClientId, e.Data); + } + else + { + await _server.SendToClientAsync(e.ClientId, $"Echo: {e.StringData}"); + } + }; + + // Disconnection + _server.OnDisconnected += (sender, e) => + { + var clientName = e.HasNickname ? e.Nickname : e.ClientId; + Console.WriteLine($"[DISCONNECTED] {clientName} ({e.DisconnectReason})"); + }; + + // Error handling + _server.OnGeneralError += (sender, e) => + Console.WriteLine($"[ERROR] {e.Message}"); + + _server.OnSslError += (sender, e) => + Console.WriteLine($"[SSL ERROR] {e.Message}"); + + // Idle connections + _server.OnIdleTimeout += (sender, e) => + Console.WriteLine($"[IDLE] Client disconnected due to timeout: {e.ClientId}"); + + // Heartbeat + _server.OnPongMissed += (sender, e) => + Console.WriteLine($"[HEARTBEAT] Pong missed from {e.ClientId}"); + + Console.WriteLine("Server starting..."); + await _server.StartAsync(); + Console.WriteLine($"Server listening on port {_server.Port}"); + Console.WriteLine($"Health API available on: http://localhost:8888/health"); + } + } +} +``` + +--- + +## Client Example + +```csharp +using EonaCat.Connections; +using EonaCat.Connections.Models; +using System.Security.Cryptography.X509Certificates; + +namespace EonaCat.Connections.Client.Example +{ + public class Program + { + private static NetworkClient _client; + + public static async Task Main(string[] args) + { + await CreateClientAsync(); + + while (true) + { + if (!_client.IsConnected) + { + Console.WriteLine("Waiting for connection..."); + await Task.Delay(1000); + continue; + } + + Console.Write("Enter message to send (or 'exit' to quit): "); + var message = Console.ReadLine(); + + if (!string.IsNullOrEmpty(message) && message.Equals("exit", StringComparison.OrdinalIgnoreCase)) + { + await _client.DisconnectAsync(); + _client.Dispose(); + break; + } + + if (!string.IsNullOrEmpty(message)) + { + await _client.SendAsync(message); + + // Optional: wait for response + var response = await _client.SendAndWaitForResponseAsync( + message, TimeSpan.FromSeconds(5)); + + if (response != null) + { + Console.WriteLine($"Server response: {response.StringData}"); + } + } + } + } + + private static async Task CreateClientAsync() + { + var config = new Configuration + { + Protocol = ProtocolType.TCP, + Host = "127.0.0.1", + Port = 1111, + Certificate = new X509Certificate2("client.pfx", "p@ss"), // For TLS (optional) + UseAesEncryption = true, + AesPassword = "EonaCat.Connections.Password", + EnableAutoReconnect = true, + ReconnectDelayInSeconds = 5, + MaxReconnectAttempts = 0, // Unlimited retries + EnableHeartbeat = true, + HeartbeatIntervalSeconds = 5, + IdleTimeoutSeconds = 30, + }; + + _client = new NetworkClient(config); + + // Error handling + _client.OnGeneralError += (sender, e) => + Console.WriteLine($"[ERROR] {e.Message}"); + + _client.OnSslError += (sender, e) => + Console.WriteLine($"[SSL ERROR] {e.Message}"); + + // Connection events + _client.OnConnected += async (sender, e) => + { + Console.WriteLine($"[CONNECTED] Connected to server at {e.RemoteEndPoint}"); + + // Set client nickname + await _client.SendNicknameAsync("TestUser"); + + // Send initial message + await _client.SendAsync("Hello from client!"); + }; + + // Data reception + _client.OnDataReceived += (sender, e) => + Console.WriteLine($"[SERVER] {(e.IsBinary ? $"{e.Data.Length} bytes" : e.StringData)}"); + + // Disconnection + _client.OnDisconnected += (sender, e) => + Console.WriteLine($"[DISCONNECTED] Disconnected from server ({e.DisconnectReason})"); + + // Idle/Timeout + _client.OnIdleTimeout += (sender, e) => + Console.WriteLine($"[IDLE] Connection idle for {e.IdleTime} seconds"); + + // Heartbeat + _client.OnPingResponse += (sender, e) => + Console.WriteLine($"[PING] Response time: {e.ResponseTime}ms"); + + Console.WriteLine("Client connecting..."); + await _client.ConnectAsync(); + } + } +} +``` + +--- + +## Advanced Usage Examples + +### Request-Response Pattern + +```csharp +// Server: Wait for client to send data, then respond +_server.OnDataReceived += async (sender, e) => +{ + if (e.StringData == "QUERY") + { + await _server.SendToClientAsync(e.ClientId, "RESPONSE_DATA"); + } +}; + +// Client: Send request and wait for response +var response = await _client.SendAndWaitForResponseAsync( + "QUERY", TimeSpan.FromSeconds(10)); +Console.WriteLine($"Got: {response.StringData}"); + +// Server: Broadcast and wait for all responses +var responses = await _server.BroadcastAndWaitForResponsesAsync( + "QUESTION", TimeSpan.FromSeconds(15)); +Console.WriteLine($"Received {responses.Count} responses"); +``` + +### Client-to-Client Communication + +```csharp +// Client A sends to Client B through server +await _server.SendFromClientToClientAsync( + "clientA_id", "clientB_id", "Direct message"); + +// Or with response +var response = await _server.SendFromClientToClientAndWaitForResponseAsync( + "clientA_id", "clientB_id", "Question for B"); +``` + +### Message Framing + +```csharp +// Delimiter-based framing +config.MessageFraming = FramingMode.Delimiter; +config.Delimiter = Encoding.UTF8.GetBytes("\n"); + +// Length-prefixed framing +config.MessageFraming = FramingMode.LengthPrefixed; +config.LengthPrefixedLength = 4; // 4-byte length prefix + +// Custom separator +server.SetSeparator("||"); +``` + +### Accessing Statistics + +```csharp +// Server statistics +Console.WriteLine($"Active connections: {_server.ActiveConnections}"); +Console.WriteLine($"Total connected: {_server.TotalConnections}"); +Console.WriteLine($"Messages/sec: {_server.MessagesPerSecond}"); +Console.WriteLine($"Data sent: {_server.BytesSent} bytes"); +Console.WriteLine($"Data received: {_server.BytesReceived} bytes"); +Console.WriteLine($"Uptime: {_server.Uptime}"); + +var stats = _server.GetStats(); +Console.WriteLine($"Dropped packets: {stats.DroppedPackets}"); +Console.WriteLine($"Dropped connections: {stats.DroppedConnections}"); + +// Client statistics +Console.WriteLine($"Connection time: {_client.ConnectedTimeInHours()} hours"); +Console.WriteLine($"Idle time: {_client.IdleTimeInSeconds()} seconds"); +Console.WriteLine($"Bytes sent: {_client.BytesSent}"); +Console.WriteLine($"Messages sent: {_client.MessagesSent}"); +``` + +### SSL/TLS with Certificate Validation + +```csharp +// Server with certificate +var config = new Configuration +{ + Certificate = new X509Certificate2("server.pfx", "password"), + MutuallyAuthenticate = true, + CheckCertificateRevocation = true, +}; + +// Client with certificate validation +var clientConfig = new Configuration +{ + Host = "server.example.com", + Port = 443, + Certificate = new X509Certificate2("client.pfx", "password"), + MutuallyAuthenticate = true, + TrustedThumbprints = new List { "CERT_THUMBPRINT" }, + CheckCertificateRevocation = true, +}; +``` + +### Encryption and Security + +```csharp +// AES Encryption +config.UseAesEncryption = true; +config.AesPassword = "SecurePassword123"; + +// Both TLS and AES together +config.Certificate = new X509Certificate2("server.pfx", "pass"); +config.UseAesEncryption = true; +config.AesPassword = "AdditionalPassword"; + +// Disable Nagle for low-latency +config.EnableNagle = false; +``` + +--- + +## Event Arguments Reference + +### ConnectionEventArgs +- `string ClientId` - Unique client identifier +- `EndPoint RemoteEndPoint` - Client's remote address/port +- `string Nickname` - Client's nickname (if set) +- `bool HasNickname` - Whether client has a nickname + +### DataReceivedEventArgs +- `string ClientId` - Sender client ID +- `byte[] Data` - Raw binary data +- `string StringData` - Data decoded as UTF-8 string +- `bool IsBinary` - True if binary data, false if text +- `bool HasNickname` - Whether sender has a nickname +- `string Nickname` - Sender's nickname (if set) + +### ErrorEventArgs +- `string Message` - Error description +- `string ClientId` - Associated client (if applicable) +- `Exception Exception` - Inner exception details + +### PingEventArgs +- `string ClientId` - Client that responded to ping +- `long ResponseTime` - Response time in milliseconds + +### IdleEventArgs / IdleClientEventArgs +- `string ClientId` - Idle client ID +- `double IdleTime` - Idle duration in seconds +- `DisconnectReason Reason` - Reason for disconnection + +--- + +## Disconnect Reasons + +The `DisconnectReason` enum provides detailed disconnect information: + +```csharp +public enum DisconnectReason +{ + Unknown, // Unknown reason + RemoteClosed, // Remote peer closed connection + LocalClosed, // Local side closed connection + Timeout, // Connection timeout + Error, // General error + SSLError, // SSL/TLS handshake error + ServerShutdown, // Server shutting down + Reconnect, // Reconnecting (client) + ClientRequested, // Client requested disconnect + Forced, // Forcefully disconnected + NoPongReceived, // Heartbeat pong not received + ProtocolError, // Protocol violation +} +``` + +--- + +## Performance Considerations + +### Optimization Tips +1. **Disable Nagle's Algorithm**: Set `EnableNagle = false` for lower latency +2. **Buffer Size**: Use appropriate `BufferSize` (default is ExtraLarge ~65KB) +3. **Keep-Alive**: Enable `EnableKeepAlive` to detect dead connections early +4. **Message Framing**: Use `LengthPrefixed` for better parsing performance +5. **Connection Pool**: Design clients to maintain persistent connections +6. **Batching**: Group small messages when possible +7. **Thread Affinity**: Run on dedicated cores for high throughput + +### Monitoring +- Use `HealthApi` for external monitoring +- Enable `EnableServerStatusPage` for HTML reports +- Monitor `MessagesPerSecond` for throughput +- Track `IdleTimeout` events for connection health +- Use `NetworkMonitor` for advanced diagnostics + +--- + +## Thread Safety + +- `NetworkServer` and `NetworkClient` are thread-safe for all public methods +- Events are called on socket completion threads +- Use `ConfigureAwait(false)` in async handlers +- Access shared state with appropriate synchronization + +--- + +## Supported Platforms + +- **.NET Framework 4.8+** - Full support +- **.NET 8.0** - Full support +- **Platforms**: Windows, Linux, macOS (with .NET Core) + +--- + +## License + +Licensed under the Apache License. See LICENSE file for details. + +--- + +## Resources + +- **Repository**: https://git.saey.me/EonaCat/EonaCat.Connections +- **Issues**: Report bugs and feature requests on the project repository +- **Documentation**: Inline XML documentation in source code