diff --git a/EonaCat.Connections.Client/Program.cs b/EonaCat.Connections.Client/Program.cs
index 1fbd48a..7dfdaf6 100644
--- a/EonaCat.Connections.Client/Program.cs
+++ b/EonaCat.Connections.Client/Program.cs
@@ -1,5 +1,6 @@
using EonaCat.Connections;
using EonaCat.Connections.Models;
+using System.Text;
namespace EonaCat.Connections.Client.Example
{
@@ -7,12 +8,9 @@ namespace EonaCat.Connections.Client.Example
{
private static NetworkClient _client;
- public static void Main(string[] args)
+ public static async Task Main(string[] args)
{
- for (int i = 0; i < 100000; i++)
- {
- CreateClientAsync(i).ConfigureAwait(false);
- }
+ await CreateClientAsync().ConfigureAwait(false);
while (true)
{
@@ -20,27 +18,42 @@ namespace EonaCat.Connections.Client.Example
var message = Console.ReadLine();
if (!string.IsNullOrEmpty(message) && message.Equals("exit", StringComparison.OrdinalIgnoreCase))
{
- _client.DisconnectAsync().ConfigureAwait(false);
+ await _client.DisconnectAsync().ConfigureAwait(false);
break;
}
+ var jsonUrl = "https://microsoftedge.github.io/Demos/json-dummy-data/5MB-min.json";
+
+ try
+ {
+ using var httpClient = new HttpClient();
+ var jsonContent = await httpClient.GetStringAsync(jsonUrl);
+ var jsonSize = Encoding.UTF8.GetByteCount(jsonContent);
+ Console.WriteLine($"Using large JSON file (size: {jsonSize / 1024 / 1024} MB)");
+ message = jsonContent;
+ }
+ catch (Exception ex)
+ {
+ Console.WriteLine($"Failed to download large JSON file: {ex.Message}");
+ }
+
if (!string.IsNullOrEmpty(message))
{
- _client.SendAsync(message).ConfigureAwait(false);
+ await _client.SendAsync(message).ConfigureAwait(false);
}
}
}
- private static async Task CreateClientAsync(int i)
+ private static async Task CreateClientAsync()
{
var config = new Configuration
{
Protocol = ProtocolType.TCP,
Host = "127.0.0.1",
- Port = 8080,
- UseSsl = true,
- UseAesEncryption = true,
- ServerCertificate = new System.Security.Cryptography.X509Certificates.X509Certificate2("client.pfx", "p@ss"),
+ Port = 1111,
+ UseSsl = false,
+ UseAesEncryption = false,
+ //ServerCertificate = new System.Security.Cryptography.X509Certificates.X509Certificate2("client.pfx", "p@ss"),
};
_client = new NetworkClient(config);
@@ -58,7 +71,7 @@ namespace EonaCat.Connections.Client.Example
await _client.ConnectAsync();
// Send nickname
- await _client.SendNicknameAsync($"TestUser{i}");
+ await _client.SendNicknameAsync("TestUser");
// Send a message
await _client.SendAsync("Hello server!");
diff --git a/EonaCat.Connections.Server/Program.cs b/EonaCat.Connections.Server/Program.cs
index f41ec5f..98c32be 100644
--- a/EonaCat.Connections.Server/Program.cs
+++ b/EonaCat.Connections.Server/Program.cs
@@ -35,26 +35,21 @@ namespace EonaCat.Connections.Server.Example
var config = new Configuration
{
Protocol = ProtocolType.TCP,
- Port = 8080,
- UseSsl = true,
- UseAesEncryption = true,
+ Port = 1111,
+ UseSsl = false,
+ UseAesEncryption = false,
MaxConnections = 100000,
- ServerCertificate = new System.Security.Cryptography.X509Certificates.X509Certificate2("server.pfx", "p@ss"),
+ //ServerCertificate = new System.Security.Cryptography.X509Certificates.X509Certificate2("server.pfx", "p@ss"),
};
_server = new NetworkServer(config);
- int totalClients = 0;
+
// Subscribe to events
_server.OnConnected += (sender, e) =>
- {
Console.WriteLine($"Client {e.ClientId} connected from {e.RemoteEndPoint}");
- Console.Title = $"Active Connections: {++totalClients}";
- };
_server.OnConnectedWithNickname += (sender, e) =>
- {
Console.WriteLine($"Client {e.ClientId} connected with nickname: {e.Nickname}");
- };
_server.OnDataReceived += async (sender, e) =>
{
@@ -72,10 +67,7 @@ namespace EonaCat.Connections.Server.Example
};
_server.OnDisconnected += (sender, e) =>
- {
Console.WriteLine($"Client {e.ClientId} disconnected");
- Console.Title = $"Active Connections: {--totalClients}";
- };
await _server.StartAsync();
}
diff --git a/EonaCat.Connections/EonaCat.Connections.csproj b/EonaCat.Connections/EonaCat.Connections.csproj
index 691545c..dacc565 100644
--- a/EonaCat.Connections/EonaCat.Connections.csproj
+++ b/EonaCat.Connections/EonaCat.Connections.csproj
@@ -11,10 +11,6 @@
EonaCat (Jeroen Saey)
EonaCat.png
readme.md
- EonaCat.Connections
- 1.0.1
- EonaCat (Jeroen Saey)
- LICENSE
@@ -22,10 +18,6 @@
True
\
-
- True
- \
-
True
\
diff --git a/EonaCat.Connections/NetworkClient.cs b/EonaCat.Connections/NetworkClient.cs
index b6d174e..2936634 100644
--- a/EonaCat.Connections/NetworkClient.cs
+++ b/EonaCat.Connections/NetworkClient.cs
@@ -143,39 +143,73 @@ namespace EonaCat.Connections
}
- private async Task ReceiveDataAsync()
- {
- var buffer = new byte[_config.BufferSize];
+ private async Task ReceiveDataAsync()
+ {
+ while (!_cancellation.Token.IsCancellationRequested && _isConnected)
+ {
+ try
+ {
+ byte[] data;
+
+ if (_config.UseAesEncryption && _aesEncryption != null)
+ {
+ // Read 4-byte length prefix
+ var lengthBuffer = new byte[4];
+ int read = await ReadExactAsync(_stream, lengthBuffer, 4, _cancellation.Token);
+ if (read == 0) break;
+
+ if (BitConverter.IsLittleEndian) Array.Reverse(lengthBuffer);
+ int length = BitConverter.ToInt32(lengthBuffer, 0);
+
+ // Read encrypted payload
+ var encrypted = new byte[length];
+ await ReadExactAsync(_stream, encrypted, length, _cancellation.Token);
+
+ // **Decrypt once here**
+ data = await DecryptDataAsync(encrypted, _aesEncryption);
+ }
+ else
+ {
+ data = new byte[_config.BufferSize];
+ int bytesRead = await _stream.ReadAsync(data, 0, data.Length, _cancellation.Token);
+ if (bytesRead == 0) break;
+
+ if (bytesRead < data.Length)
+ {
+ var tmp = new byte[bytesRead];
+ Array.Copy(data, tmp, bytesRead);
+ data = tmp;
+ }
+ }
+
+ await ProcessReceivedDataAsync(data);
+ }
+ catch (Exception ex)
+ {
+ OnGeneralError?.Invoke(this, new ErrorEventArgs { Exception = ex, Message = "Error receiving data" });
+ _isConnected = false;
+
+ _ = Task.Run(() => AutoReconnectAsync());
+ break;
+ }
+ }
+
+ await DisconnectAsync();
+ }
+
+
+ private async Task ReadExactAsync(Stream stream, byte[] buffer, int length, CancellationToken ct)
+ {
+ int offset = 0;
+ while (offset < length)
+ {
+ int read = await stream.ReadAsync(buffer, offset, length - offset, ct);
+ if (read == 0) return 0;
+ offset += read;
+ }
+ return offset;
+ }
- while (!_cancellation.Token.IsCancellationRequested && _isConnected)
- {
- try
- {
- var bytesRead = await _stream.ReadAsync(buffer, 0, buffer.Length, _cancellation.Token);
-
- if (bytesRead == 0)
- {
- break;
- }
-
- var data = new byte[bytesRead];
- Array.Copy(buffer, data, bytesRead);
-
- await ProcessReceivedDataAsync(data);
- }
- catch (Exception ex)
- {
- OnGeneralError?.Invoke(this, new ErrorEventArgs { Exception = ex, Message = "Error receiving data" });
- _isConnected = false;
-
- // Start reconnect
- _ = Task.Run(() => AutoReconnectAsync());
- break;
- }
- }
-
- await DisconnectAsync();
- }
private async Task ReceiveUdpDataAsync()
{
@@ -198,91 +232,86 @@ namespace EonaCat.Connections
}
}
- private async Task ProcessReceivedDataAsync(byte[] data)
- {
- try
- {
- // Decrypt if AES encryption is enabled
- if (_config.UseAesEncryption && _aesEncryption != null)
- {
- data = await DecryptDataAsync(data, _aesEncryption);
- }
+ private async Task ProcessReceivedDataAsync(byte[] data)
+ {
+ try
+ {
+ // Data is already decrypted if AES is enabled
+ // Just update stats / handle string conversion
+
+ bool isBinary = true;
+ string stringData = null;
+
+ try
+ {
+ stringData = Encoding.UTF8.GetString(data);
+ if (Encoding.UTF8.GetBytes(stringData).Length == data.Length)
+ isBinary = false;
+ }
+ catch
+ {
+ // Keep as binary
+ }
+
+ OnDataReceived?.Invoke(this, new DataReceivedEventArgs
+ {
+ ClientId = "server",
+ Data = data,
+ StringData = stringData,
+ IsBinary = isBinary
+ });
+ }
+ catch (Exception ex)
+ {
+ if (_config.UseAesEncryption)
+ OnEncryptionError?.Invoke(this, new ErrorEventArgs { Exception = ex, Message = "Error processing data" });
+ else
+ OnGeneralError?.Invoke(this, new ErrorEventArgs { Exception = ex, Message = "Error processing data" });
+ }
+ }
- // Try to decode as string, fallback to binary
- bool isBinary = true;
- string stringData = null;
- try
- {
- stringData = Encoding.UTF8.GetString(data);
- if (Encoding.UTF8.GetBytes(stringData).Length == data.Length)
- {
- isBinary = false;
- }
- }
- catch
- {
- // Keep as binary
- }
-
- OnDataReceived?.Invoke(this, new DataReceivedEventArgs
- {
- ClientId = "server",
- Data = data,
- StringData = stringData,
- IsBinary = isBinary
- });
- }
- catch (Exception ex)
- {
- if (_config.UseAesEncryption)
- {
- OnEncryptionError?.Invoke(this, new ErrorEventArgs { Exception = ex, Message = "Error decrypting data" });
- }
- else
- {
- OnGeneralError?.Invoke(this, new ErrorEventArgs { Exception = ex, Message = "Error processing received data" });
- }
- }
+ public async Task SendAsync(byte[] data)
+ {
+ if (!_isConnected) return;
+
+ try
+ {
+ if (_config.UseAesEncryption && _aesEncryption != null)
+ {
+ // Encrypt payload
+ data = await EncryptDataAsync(data, _aesEncryption);
+
+ // Prepend 4-byte length for framing
+ var lengthPrefix = BitConverter.GetBytes(data.Length);
+ if (BitConverter.IsLittleEndian) Array.Reverse(lengthPrefix);
+
+ var framed = new byte[lengthPrefix.Length + data.Length];
+ Buffer.BlockCopy(lengthPrefix, 0, framed, 0, lengthPrefix.Length);
+ Buffer.BlockCopy(data, 0, framed, lengthPrefix.Length, data.Length);
+
+ data = framed;
+ }
+
+ if (_config.Protocol == ProtocolType.TCP)
+ {
+ await _stream.WriteAsync(data, 0, data.Length);
+ await _stream.FlushAsync();
+ }
+ else
+ {
+ await _udpClient.SendAsync(data, data.Length);
+ }
+ }
+ catch (Exception ex)
+ {
+ if (_config.UseAesEncryption)
+ OnEncryptionError?.Invoke(this, new ErrorEventArgs { Exception = ex, Message = "Error encrypting/sending data" });
+ else
+ OnGeneralError?.Invoke(this, new ErrorEventArgs { Exception = ex, Message = "Error sending data" });
+ }
}
- public async Task SendAsync(byte[] data)
- {
- if (!_isConnected)
- {
- return;
- }
-
- try
- {
- // Encrypt if AES encryption is enabled
- if (_config.UseAesEncryption && _aesEncryption != null)
- {
- data = await EncryptDataAsync(data, _aesEncryption);
- }
-
- if (_config.Protocol == ProtocolType.TCP)
- {
- await _stream.WriteAsync(data, 0, data.Length);
- await _stream.FlushAsync();
- }
- else
- {
- await _udpClient.SendAsync(data, data.Length);
- }
- }
- catch (Exception ex)
- {
- if (_config.UseAesEncryption)
- {
- OnEncryptionError?.Invoke(this, new ErrorEventArgs { Exception = ex, Message = "Error encrypting/sending data" });
- }
- else
- {
- OnGeneralError?.Invoke(this, new ErrorEventArgs { Exception = ex, Message = "Error sending data" });
- }
- }
- }
public async Task SendAsync(string message)
{
diff --git a/EonaCat.Connections/NetworkServer.cs b/EonaCat.Connections/NetworkServer.cs
index 82450be..3350539 100644
--- a/EonaCat.Connections/NetworkServer.cs
+++ b/EonaCat.Connections/NetworkServer.cs
@@ -243,103 +243,131 @@ namespace EonaCat.Connections
await ProcessReceivedDataAsync(client, result.Buffer);
}
- private async Task HandleClientCommunicationAsync(Connection client)
- {
- var buffer = new byte[_config.BufferSize];
+ private async Task HandleClientCommunicationAsync(Connection client)
+ {
+ var lengthBuffer = new byte[4]; // length prefix
+
+ while (!client.CancellationToken.Token.IsCancellationRequested && client.TcpClient.Connected)
+ {
+ try
+ {
+ byte[] data;
+
+ if (client.IsEncrypted && client.AesEncryption != null)
+ {
+ // Read 4-byte length first
+ int read = await ReadExactAsync(client.Stream, lengthBuffer, 4, client.CancellationToken.Token);
+ if (read == 0) break;
+
+ if (BitConverter.IsLittleEndian)
+ Array.Reverse(lengthBuffer);
+
+ int length = BitConverter.ToInt32(lengthBuffer, 0);
+
+ // Read full encrypted message
+ var encrypted = new byte[length];
+ await ReadExactAsync(client.Stream, encrypted, length, client.CancellationToken.Token);
+
+ // **Decrypt once here**
+ data = await DecryptDataAsync(encrypted, client.AesEncryption);
+ }
+ else
+ {
+ // Non-encrypted: just read raw bytes
+ data = new byte[_config.BufferSize];
+ int bytesRead = await client.Stream.ReadAsync(data, 0, data.Length, client.CancellationToken.Token);
+ if (bytesRead == 0) break;
+
+ if (bytesRead < data.Length)
+ {
+ var tmp = new byte[bytesRead];
+ Array.Copy(data, tmp, bytesRead);
+ data = tmp;
+ }
+ }
+
+ await ProcessReceivedDataAsync(client, data);
+ }
+ catch (Exception ex)
+ {
+ OnGeneralError?.Invoke(this, new ErrorEventArgs
+ {
+ ClientId = client.Id,
+ Exception = ex,
+ Message = "Error reading from client"
+ });
+ break;
+ }
+ }
+ }
+
+ private async Task ReadExactAsync(Stream stream, byte[] buffer, int length, CancellationToken ct)
+ {
+ int offset = 0;
+ while (offset < length)
+ {
+ int read = await stream.ReadAsync(buffer, offset, length - offset, ct);
+ if (read == 0) return 0; // disconnected
+ offset += read;
+ }
+ return offset;
+ }
- while (!client.CancellationToken.Token.IsCancellationRequested && client.TcpClient.Connected)
- {
- try
- {
- var bytesRead = await client.Stream.ReadAsync(buffer, 0, buffer.Length, client.CancellationToken.Token);
- if (bytesRead == 0)
- {
- break; // Client disconnected
- }
+ private async Task ProcessReceivedDataAsync(Connection client, byte[] data)
+ {
+ try
+ {
+ client.BytesReceived += data.Length;
+ lock (_statsLock)
+ {
+ _stats.BytesReceived += data.Length;
+ _stats.MessagesReceived++;
+ }
+
+ // Try to decode as string, fallback to binary
+ bool isBinary = true;
+ string stringData = null;
+
+ try
+ {
+ stringData = Encoding.UTF8.GetString(data);
+ if (Encoding.UTF8.GetBytes(stringData).Length == data.Length)
+ isBinary = false;
+ }
+ catch { }
+
+ // Handle special commands
+ if (!isBinary && stringData.StartsWith("NICKNAME:"))
+ {
+ var nickname = stringData.Substring(9);
+ client.Nickname = nickname;
+ OnConnectedWithNickname?.Invoke(this, new NicknameConnectionEventArgs
+ {
+ ClientId = client.Id,
+ RemoteEndPoint = client.RemoteEndPoint,
+ Nickname = nickname
+ });
+ return;
+ }
+
+ OnDataReceived?.Invoke(this, new DataReceivedEventArgs
+ {
+ ClientId = client.Id,
+ Data = data,
+ StringData = stringData,
+ IsBinary = isBinary
+ });
+ }
+ catch (Exception ex)
+ {
+ if (client.IsEncrypted)
+ OnEncryptionError?.Invoke(this, new ErrorEventArgs { ClientId = client.Id, Exception = ex, Message = "Error processing data" });
+ else
+ OnGeneralError?.Invoke(this, new ErrorEventArgs { ClientId = client.Id, Exception = ex, Message = "Error processing data" });
+ }
+ }
- var data = new byte[bytesRead];
- Array.Copy(buffer, data, bytesRead);
-
- await ProcessReceivedDataAsync(client, data);
- }
- catch (Exception ex)
- {
- OnGeneralError?.Invoke(this, new ErrorEventArgs { ClientId = client.Id, Exception = ex, Message = "Error reading from client" });
- break;
- }
- }
- }
-
- private async Task ProcessReceivedDataAsync(Connection client, byte[] data)
- {
- try
- {
- // Decrypt if AES encryption is enabled
- if (client.IsEncrypted && client.AesEncryption != null)
- {
- data = await DecryptDataAsync(data, client.AesEncryption);
- }
-
- client.BytesReceived += data.Length;
- lock (_statsLock)
- {
- _stats.BytesReceived += data.Length;
- _stats.MessagesReceived++;
- }
-
- // Try to decode as string, fallback to binary
- bool isBinary = true;
- string stringData = null;
-
- try
- {
- stringData = Encoding.UTF8.GetString(data);
- // Check if it's valid UTF-8
- if (Encoding.UTF8.GetBytes(stringData).Length == data.Length)
- {
- isBinary = false;
- }
- }
- catch
- {
- // Keep as binary
- }
-
- // Handle special commands
- if (!isBinary && stringData.StartsWith("NICKNAME:"))
- {
- var nickname = stringData.Substring(9);
- client.Nickname = nickname;
- OnConnectedWithNickname?.Invoke(this, new NicknameConnectionEventArgs
- {
- ClientId = client.Id,
- RemoteEndPoint = client.RemoteEndPoint,
- Nickname = nickname
- });
- return;
- }
-
- OnDataReceived?.Invoke(this, new DataReceivedEventArgs
- {
- ClientId = client.Id,
- Data = data,
- StringData = stringData,
- IsBinary = isBinary
- });
- }
- catch (Exception ex)
- {
- if (client.IsEncrypted)
- {
- OnEncryptionError?.Invoke(this, new ErrorEventArgs { ClientId = client.Id, Exception = ex, Message = "Error decrypting data" });
- }
- else
- {
- OnGeneralError?.Invoke(this, new ErrorEventArgs { ClientId = client.Id, Exception = ex, Message = "Error processing received data" });
- }
- }
- }
public async Task SendToClientAsync(string clientId, byte[] data)
{
@@ -369,46 +397,54 @@ namespace EonaCat.Connections
await BroadcastAsync(Encoding.UTF8.GetBytes(message));
}
- private async Task SendDataAsync(Connection client, byte[] data)
- {
- try
- {
- // Encrypt if AES encryption is enabled
- if (client.IsEncrypted && client.AesEncryption != null)
- {
- data = await EncryptDataAsync(data, client.AesEncryption);
- }
-
- if (_config.Protocol == ProtocolType.TCP)
- {
- await client.Stream.WriteAsync(data, 0, data.Length);
- await client.Stream.FlushAsync();
- }
- else
- {
- await _udpListener.SendAsync(data, data.Length, client.RemoteEndPoint);
- }
-
- client.BytesSent += data.Length;
- lock (_statsLock)
- {
- _stats.BytesSent += data.Length;
- _stats.MessagesSent++;
- }
- }
- catch (Exception ex)
- {
- if (client.IsEncrypted)
- {
- OnEncryptionError?.Invoke(this, new ErrorEventArgs { ClientId = client.Id, Exception = ex, Message = "Error encrypting/sending data" });
- }
- else
- {
- OnGeneralError?.Invoke(this, new ErrorEventArgs { ClientId = client.Id, Exception = ex, Message = "Error sending data" });
- }
- }
+ private async Task SendDataAsync(Connection client, byte[] data)
+ {
+ try
+ {
+ if (client.IsEncrypted && client.AesEncryption != null)
+ {
+ // Encrypt payload
+ data = await EncryptDataAsync(data, client.AesEncryption);
+
+ // Prepend length for safe framing
+ var lengthPrefix = BitConverter.GetBytes(data.Length);
+ if (BitConverter.IsLittleEndian)
+ Array.Reverse(lengthPrefix);
+
+ var framed = new byte[lengthPrefix.Length + data.Length];
+ Buffer.BlockCopy(lengthPrefix, 0, framed, 0, lengthPrefix.Length);
+ Buffer.BlockCopy(data, 0, framed, lengthPrefix.Length, data.Length);
+
+ data = framed; // replace data with framed payload
+ }
+
+ if (_config.Protocol == ProtocolType.TCP)
+ {
+ await client.Stream.WriteAsync(data, 0, data.Length);
+ await client.Stream.FlushAsync();
+ }
+ else
+ {
+ await _udpListener.SendAsync(data, data.Length, client.RemoteEndPoint);
+ }
+
+ client.BytesSent += data.Length;
+ lock (_statsLock)
+ {
+ _stats.BytesSent += data.Length;
+ _stats.MessagesSent++;
+ }
+ }
+ catch (Exception ex)
+ {
+ if (client.IsEncrypted)
+ OnEncryptionError?.Invoke(this, new ErrorEventArgs { ClientId = client.Id, Exception = ex, Message = "Error encrypting/sending data" });
+ else
+ OnGeneralError?.Invoke(this, new ErrorEventArgs { ClientId = client.Id, Exception = ex, Message = "Error sending data" });
+ }
}
+
private async Task EncryptDataAsync(byte[] data, Aes aes)
{
using (var encryptor = aes.CreateEncryptor())