Updated README.md
This commit is contained in:
@@ -1,20 +1,385 @@
|
||||
|
||||
# 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:
|
||||
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
|
||||
|
||||
#### - 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
|
||||
## 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<ConnectionEventArgs> OnConnected;
|
||||
public event EventHandler<ConnectionEventArgs> OnConnectedWithNickname;
|
||||
public event EventHandler<ConnectionEventArgs> OnDisconnected;
|
||||
|
||||
// Data events
|
||||
public event EventHandler<DataReceivedEventArgs> OnDataReceived;
|
||||
|
||||
// Error events
|
||||
public event EventHandler<ErrorEventArgs> OnSslError;
|
||||
public event EventHandler<ErrorEventArgs> OnEncryptionError;
|
||||
public event EventHandler<ErrorEventArgs> OnGeneralError;
|
||||
public event EventHandler<ErrorEventArgs> OnSocketError;
|
||||
|
||||
// Heartbeat events
|
||||
public event EventHandler<PingEventArgs> OnClientPingResponse;
|
||||
public event EventHandler<PingEventArgs> OnPongMissed;
|
||||
|
||||
// Idle/Timeout events
|
||||
public event EventHandler<IdleEventArgs> OnIdleTimeout;
|
||||
|
||||
// Logging
|
||||
public event EventHandler<string> 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<string, Connection> GetClients();
|
||||
public List<Connection> GetClient(string clientId);
|
||||
public async Task DisconnectClientAsync(string clientId, DisconnectReason reason, Exception? exception);
|
||||
|
||||
// Messaging - Send to Specific Client
|
||||
public async Task<bool> SendToClientAsync(string clientId, byte[] data);
|
||||
public async Task<bool> SendToClientAsync(string clientId, string message);
|
||||
|
||||
// Messaging - Client-to-Client
|
||||
public async Task<bool> SendFromClientToClientAsync(string fromClientId, string toClientId, byte[] data);
|
||||
public async Task<bool> SendFromClientToClientAsync(string fromClientId, string toClientId, string message);
|
||||
|
||||
// Broadcast Messaging
|
||||
public async Task<bool> BroadcastAsync(byte[] data);
|
||||
public async Task<bool> BroadcastAsync(string message);
|
||||
|
||||
// Request-Response Patterns
|
||||
public async Task<DataReceivedEventArgs> SendToClientAndWaitForResponseAsync(
|
||||
string clientId, byte[] data, TimeSpan? timeout = null);
|
||||
public async Task<DataReceivedEventArgs> SendToClientAndWaitForResponseAsync(
|
||||
string clientId, string message, TimeSpan? timeout = null);
|
||||
|
||||
public async Task<DataReceivedEventArgs> SendFromClientToClientAndWaitForResponseAsync(
|
||||
string fromClientId, string toClientId, byte[] data, TimeSpan? timeout = null);
|
||||
public async Task<DataReceivedEventArgs> SendFromClientToClientAndWaitForResponseAsync(
|
||||
string fromClientId, string toClientId, string message, TimeSpan? timeout = null);
|
||||
|
||||
public async Task<List<DataReceivedEventArgs>> BroadcastAndWaitForResponsesAsync(
|
||||
byte[] data, TimeSpan? timeout = null);
|
||||
public async Task<List<DataReceivedEventArgs>> 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<ConnectionEventArgs> OnConnected;
|
||||
public event EventHandler<ConnectionEventArgs> OnNicknameSend;
|
||||
public event EventHandler<ConnectionEventArgs> OnDisconnected;
|
||||
|
||||
// Data events
|
||||
public event EventHandler<DataReceivedEventArgs> OnDataReceived;
|
||||
|
||||
// Error events
|
||||
public event EventHandler<ErrorEventArgs> OnSslError;
|
||||
public event EventHandler<ErrorEventArgs> OnEncryptionError;
|
||||
public event EventHandler<ErrorEventArgs> OnGeneralError;
|
||||
public event EventHandler<ErrorEventArgs> OnSocketError;
|
||||
|
||||
// Heartbeat events
|
||||
public event EventHandler<PingEventArgs> OnPingResponse;
|
||||
public event EventHandler<PingEventArgs> OnPongResponse;
|
||||
|
||||
// Idle/Timeout events
|
||||
public event EventHandler<IdleClientEventArgs> OnIdleTimeout;
|
||||
|
||||
// Logging
|
||||
public event EventHandler<string> 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<bool> ConnectAsync();
|
||||
public async Task DisconnectAsync();
|
||||
public async Task DisconnectDueTimeoutAsync();
|
||||
|
||||
// Messaging
|
||||
public async Task<bool> SendAsync(byte[] data);
|
||||
public async Task<bool> SendAsync(string message);
|
||||
|
||||
// Nickname
|
||||
public async Task<bool> SendNicknameAsync(string nickname);
|
||||
|
||||
// Request-Response
|
||||
public async Task<DataReceivedEventArgs> SendAndWaitForResponseAsync(
|
||||
byte[] data, TimeSpan? timeout = null);
|
||||
public async Task<DataReceivedEventArgs> SendAndWaitForResponseAsync(
|
||||
string message, TimeSpan? timeout = null);
|
||||
public async Task<DataReceivedEventArgs> 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" `
|
||||
@@ -28,34 +393,120 @@ servers and clients with optional TLS (for TCP) and optional application-layer e
|
||||
-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.
|
||||
This creates a self-signed certificate with password `p@ss` in `C:\temp\server.pfx`.
|
||||
|
||||
---
|
||||
|
||||
## Server example:
|
||||
## 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
|
||||
{
|
||||
// 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);
|
||||
CreateServerAsync().GetAwaiter().GetResult();
|
||||
|
||||
while (true)
|
||||
{
|
||||
Console.Write("Enter message to send (or 'exit' to quit): ");
|
||||
Console.Write("Enter message to broadcast (or 'exit' to quit): ");
|
||||
var message = Console.ReadLine();
|
||||
|
||||
if (!string.IsNullOrEmpty(message) && message.Equals("exit", StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
_server.Stop();
|
||||
_server.StopAsync().GetAwaiter().GetResult();
|
||||
_server.Dispose();
|
||||
Console.WriteLine("Server stopped.");
|
||||
break;
|
||||
@@ -63,7 +514,7 @@ servers and clients with optional TLS (for TCP) and optional application-layer e
|
||||
|
||||
if (!string.IsNullOrEmpty(message))
|
||||
{
|
||||
_server.BroadcastAsync(message).ConfigureAwait(false);
|
||||
_server.BroadcastAsync(message).GetAwaiter().GetResult();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -74,34 +525,33 @@ servers and clients with optional TLS (for TCP) and optional application-layer e
|
||||
{
|
||||
Protocol = ProtocolType.TCP,
|
||||
Port = 1111,
|
||||
UseSsl = true,
|
||||
Certificate = new X509Certificate2("server.pfx", "p@ss"), // Enables TLS
|
||||
UseAesEncryption = true,
|
||||
MaxConnections = 100000,
|
||||
AesPassword = "EonaCat.Connections.Password",
|
||||
Certificate = new System.Security.Cryptography.X509Certificates.X509Certificate2("server.pfx", "p@ss")
|
||||
MaxConnections = 100000,
|
||||
EnableHeartbeat = true,
|
||||
HeartbeatIntervalSeconds = 5,
|
||||
IdleTimeoutSeconds = 30,
|
||||
EnableHealthApi = true,
|
||||
HealthApiPort = 8888,
|
||||
};
|
||||
|
||||
_server = new NetworkServer(config);
|
||||
|
||||
// Subscribe to events
|
||||
// Connection events
|
||||
_server.OnConnected += (sender, e) =>
|
||||
Console.WriteLine($"Client {e.ClientId} connected from {e.RemoteEndPoint}");
|
||||
Console.WriteLine($"[CONNECTED] Client {e.ClientId} from {e.RemoteEndPoint}");
|
||||
|
||||
_server.OnConnectedWithNickname += (sender, e) =>
|
||||
Console.WriteLine($"Client {e.ClientId} connected with nickname: {e.Nickname}");
|
||||
Console.WriteLine($"[NICKNAME] Client {e.ClientId} identified as: {e.Nickname}");
|
||||
|
||||
// Data reception
|
||||
_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)}");
|
||||
}
|
||||
var clientName = e.HasNickname ? e.Nickname : e.ClientId;
|
||||
Console.WriteLine($"[DATA] From {clientName}: {(e.IsBinary ? $"{e.Data.Length} bytes" : e.StringData)}");
|
||||
|
||||
// Echo back the message
|
||||
// Echo response
|
||||
if (e.IsBinary)
|
||||
{
|
||||
await _server.SendToClientAsync(e.ClientId, e.Data);
|
||||
@@ -112,45 +562,62 @@ servers and clients with optional TLS (for TCP) and optional application-layer e
|
||||
}
|
||||
};
|
||||
|
||||
// Disconnection
|
||||
_server.OnDisconnected += (sender, e) =>
|
||||
{
|
||||
if (e.HasNickname)
|
||||
{
|
||||
Console.WriteLine($"Client {e.Nickname} disconnected");
|
||||
}
|
||||
else
|
||||
{
|
||||
Console.WriteLine($"Client {e.ClientId} disconnected");
|
||||
}
|
||||
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:
|
||||
---
|
||||
|
||||
## Client Example
|
||||
|
||||
```csharp
|
||||
using EonaCat.Connections;
|
||||
using EonaCat.Connections.Models;
|
||||
using System.Security.Cryptography.X509Certificates;
|
||||
|
||||
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);
|
||||
await CreateClientAsync();
|
||||
|
||||
while (true)
|
||||
{
|
||||
if (!_client.IsConnected)
|
||||
{
|
||||
await Task.Delay(1000).ConfigureAwait(false);
|
||||
Console.WriteLine("Waiting for connection...");
|
||||
await Task.Delay(1000);
|
||||
continue;
|
||||
}
|
||||
|
||||
@@ -159,13 +626,23 @@ servers and clients with optional TLS (for TCP) and optional application-layer e
|
||||
|
||||
if (!string.IsNullOrEmpty(message) && message.Equals("exit", StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
await _client.DisconnectAsync().ConfigureAwait(false);
|
||||
await _client.DisconnectAsync();
|
||||
_client.Dispose();
|
||||
break;
|
||||
}
|
||||
|
||||
if (!string.IsNullOrEmpty(message))
|
||||
{
|
||||
await _client.SendAsync(message).ConfigureAwait(false);
|
||||
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}");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -177,39 +654,279 @@ servers and clients with optional TLS (for TCP) and optional application-layer e
|
||||
Protocol = ProtocolType.TCP,
|
||||
Host = "127.0.0.1",
|
||||
Port = 1111,
|
||||
UseSsl = true,
|
||||
Certificate = new X509Certificate2("client.pfx", "p@ss"), // For TLS (optional)
|
||||
UseAesEncryption = true,
|
||||
AesPassword = "EonaCat.Connections.Password",
|
||||
Certificate = new System.Security.Cryptography.X509Certificates.X509Certificate2("client.pfx", "p@ss"),
|
||||
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}");
|
||||
Console.WriteLine($"[ERROR] {e.Message}");
|
||||
|
||||
// Subscribe to events
|
||||
_client.OnSslError += (sender, e) =>
|
||||
Console.WriteLine($"[SSL ERROR] {e.Message}");
|
||||
|
||||
// Connection events
|
||||
_client.OnConnected += async (sender, e) =>
|
||||
{
|
||||
Console.WriteLine($"Connected to server at {e.RemoteEndPoint}");
|
||||
Console.WriteLine($"[CONNECTED] Connected to server at {e.RemoteEndPoint}");
|
||||
|
||||
// Set nickname
|
||||
await _client.SetNicknameAsync("TestUser");
|
||||
// Set client nickname
|
||||
await _client.SendNicknameAsync("TestUser");
|
||||
|
||||
// Send a message
|
||||
await _client.SendAsync("Hello server!");
|
||||
// Send initial message
|
||||
await _client.SendAsync("Hello from client!");
|
||||
};
|
||||
|
||||
// Data reception
|
||||
_client.OnDataReceived += (sender, e) =>
|
||||
Console.WriteLine($"Server says: {(e.IsBinary ? $"{e.Data.Length} bytes" : e.StringData)}");
|
||||
Console.WriteLine($"[SERVER] {(e.IsBinary ? $"{e.Data.Length} bytes" : e.StringData)}");
|
||||
|
||||
// Disconnection
|
||||
_client.OnDisconnected += (sender, e) =>
|
||||
{
|
||||
Console.WriteLine("Disconnected from server");
|
||||
};
|
||||
Console.WriteLine($"[DISCONNECTED] Disconnected from server ({e.DisconnectReason})");
|
||||
|
||||
Console.WriteLine("Connecting to server...");
|
||||
// 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<string> { "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
|
||||
|
||||
Reference in New Issue
Block a user