Updated
This commit is contained in:
parent
1a15416613
commit
bdf2d1b935
|
@ -1,5 +1,6 @@
|
||||||
using EonaCat.Connections;
|
using EonaCat.Connections;
|
||||||
using EonaCat.Connections.Models;
|
using EonaCat.Connections.Models;
|
||||||
|
using System.Text;
|
||||||
|
|
||||||
namespace EonaCat.Connections.Client.Example
|
namespace EonaCat.Connections.Client.Example
|
||||||
{
|
{
|
||||||
|
@ -7,12 +8,9 @@ namespace EonaCat.Connections.Client.Example
|
||||||
{
|
{
|
||||||
private static NetworkClient _client;
|
private static NetworkClient _client;
|
||||||
|
|
||||||
public static void Main(string[] args)
|
public static async Task Main(string[] args)
|
||||||
{
|
{
|
||||||
for (int i = 0; i < 100000; i++)
|
await CreateClientAsync().ConfigureAwait(false);
|
||||||
{
|
|
||||||
CreateClientAsync(i).ConfigureAwait(false);
|
|
||||||
}
|
|
||||||
|
|
||||||
while (true)
|
while (true)
|
||||||
{
|
{
|
||||||
|
@ -20,27 +18,42 @@ namespace EonaCat.Connections.Client.Example
|
||||||
var message = Console.ReadLine();
|
var message = Console.ReadLine();
|
||||||
if (!string.IsNullOrEmpty(message) && message.Equals("exit", StringComparison.OrdinalIgnoreCase))
|
if (!string.IsNullOrEmpty(message) && message.Equals("exit", StringComparison.OrdinalIgnoreCase))
|
||||||
{
|
{
|
||||||
_client.DisconnectAsync().ConfigureAwait(false);
|
await _client.DisconnectAsync().ConfigureAwait(false);
|
||||||
break;
|
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))
|
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
|
var config = new Configuration
|
||||||
{
|
{
|
||||||
Protocol = ProtocolType.TCP,
|
Protocol = ProtocolType.TCP,
|
||||||
Host = "127.0.0.1",
|
Host = "127.0.0.1",
|
||||||
Port = 8080,
|
Port = 1111,
|
||||||
UseSsl = true,
|
UseSsl = false,
|
||||||
UseAesEncryption = true,
|
UseAesEncryption = false,
|
||||||
ServerCertificate = new System.Security.Cryptography.X509Certificates.X509Certificate2("client.pfx", "p@ss"),
|
//ServerCertificate = new System.Security.Cryptography.X509Certificates.X509Certificate2("client.pfx", "p@ss"),
|
||||||
};
|
};
|
||||||
|
|
||||||
_client = new NetworkClient(config);
|
_client = new NetworkClient(config);
|
||||||
|
@ -58,7 +71,7 @@ namespace EonaCat.Connections.Client.Example
|
||||||
await _client.ConnectAsync();
|
await _client.ConnectAsync();
|
||||||
|
|
||||||
// Send nickname
|
// Send nickname
|
||||||
await _client.SendNicknameAsync($"TestUser{i}");
|
await _client.SendNicknameAsync("TestUser");
|
||||||
|
|
||||||
// Send a message
|
// Send a message
|
||||||
await _client.SendAsync("Hello server!");
|
await _client.SendAsync("Hello server!");
|
||||||
|
|
|
@ -35,26 +35,21 @@ namespace EonaCat.Connections.Server.Example
|
||||||
var config = new Configuration
|
var config = new Configuration
|
||||||
{
|
{
|
||||||
Protocol = ProtocolType.TCP,
|
Protocol = ProtocolType.TCP,
|
||||||
Port = 8080,
|
Port = 1111,
|
||||||
UseSsl = true,
|
UseSsl = false,
|
||||||
UseAesEncryption = true,
|
UseAesEncryption = false,
|
||||||
MaxConnections = 100000,
|
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);
|
_server = new NetworkServer(config);
|
||||||
int totalClients = 0;
|
|
||||||
// Subscribe to events
|
// Subscribe to events
|
||||||
_server.OnConnected += (sender, e) =>
|
_server.OnConnected += (sender, e) =>
|
||||||
{
|
|
||||||
Console.WriteLine($"Client {e.ClientId} connected from {e.RemoteEndPoint}");
|
Console.WriteLine($"Client {e.ClientId} connected from {e.RemoteEndPoint}");
|
||||||
Console.Title = $"Active Connections: {++totalClients}";
|
|
||||||
};
|
|
||||||
|
|
||||||
_server.OnConnectedWithNickname += (sender, e) =>
|
_server.OnConnectedWithNickname += (sender, e) =>
|
||||||
{
|
|
||||||
Console.WriteLine($"Client {e.ClientId} connected with nickname: {e.Nickname}");
|
Console.WriteLine($"Client {e.ClientId} connected with nickname: {e.Nickname}");
|
||||||
};
|
|
||||||
|
|
||||||
_server.OnDataReceived += async (sender, e) =>
|
_server.OnDataReceived += async (sender, e) =>
|
||||||
{
|
{
|
||||||
|
@ -72,10 +67,7 @@ namespace EonaCat.Connections.Server.Example
|
||||||
};
|
};
|
||||||
|
|
||||||
_server.OnDisconnected += (sender, e) =>
|
_server.OnDisconnected += (sender, e) =>
|
||||||
{
|
|
||||||
Console.WriteLine($"Client {e.ClientId} disconnected");
|
Console.WriteLine($"Client {e.ClientId} disconnected");
|
||||||
Console.Title = $"Active Connections: {--totalClients}";
|
|
||||||
};
|
|
||||||
|
|
||||||
await _server.StartAsync();
|
await _server.StartAsync();
|
||||||
}
|
}
|
||||||
|
|
|
@ -11,10 +11,6 @@
|
||||||
<Copyright>EonaCat (Jeroen Saey)</Copyright>
|
<Copyright>EonaCat (Jeroen Saey)</Copyright>
|
||||||
<PackageIcon>EonaCat.png</PackageIcon>
|
<PackageIcon>EonaCat.png</PackageIcon>
|
||||||
<PackageReadmeFile>readme.md</PackageReadmeFile>
|
<PackageReadmeFile>readme.md</PackageReadmeFile>
|
||||||
<PackageId>EonaCat.Connections</PackageId>
|
|
||||||
<Version>1.0.1</Version>
|
|
||||||
<Authors>EonaCat (Jeroen Saey)</Authors>
|
|
||||||
<PackageLicenseFile>LICENSE</PackageLicenseFile>
|
|
||||||
</PropertyGroup>
|
</PropertyGroup>
|
||||||
|
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
|
@ -22,10 +18,6 @@
|
||||||
<Pack>True</Pack>
|
<Pack>True</Pack>
|
||||||
<PackagePath>\</PackagePath>
|
<PackagePath>\</PackagePath>
|
||||||
</None>
|
</None>
|
||||||
<None Include="..\LICENSE">
|
|
||||||
<Pack>True</Pack>
|
|
||||||
<PackagePath>\</PackagePath>
|
|
||||||
</None>
|
|
||||||
<None Include="..\readme.md">
|
<None Include="..\readme.md">
|
||||||
<Pack>True</Pack>
|
<Pack>True</Pack>
|
||||||
<PackagePath>\</PackagePath>
|
<PackagePath>\</PackagePath>
|
||||||
|
|
|
@ -143,39 +143,73 @@ namespace EonaCat.Connections
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
private async Task ReceiveDataAsync()
|
private async Task ReceiveDataAsync()
|
||||||
{
|
{
|
||||||
var buffer = new byte[_config.BufferSize];
|
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<int> 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()
|
private async Task ReceiveUdpDataAsync()
|
||||||
{
|
{
|
||||||
|
@ -198,91 +232,86 @@ namespace EonaCat.Connections
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private async Task ProcessReceivedDataAsync(byte[] data)
|
private async Task ProcessReceivedDataAsync(byte[] data)
|
||||||
{
|
{
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
// Decrypt if AES encryption is enabled
|
// Data is already decrypted if AES is enabled
|
||||||
if (_config.UseAesEncryption && _aesEncryption != null)
|
// Just update stats / handle string conversion
|
||||||
{
|
|
||||||
data = await DecryptDataAsync(data, _aesEncryption);
|
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
|
public async Task SendAsync(byte[] data)
|
||||||
{
|
{
|
||||||
stringData = Encoding.UTF8.GetString(data);
|
if (!_isConnected) return;
|
||||||
if (Encoding.UTF8.GetBytes(stringData).Length == data.Length)
|
|
||||||
{
|
try
|
||||||
isBinary = false;
|
{
|
||||||
}
|
if (_config.UseAesEncryption && _aesEncryption != null)
|
||||||
}
|
{
|
||||||
catch
|
// Encrypt payload
|
||||||
{
|
data = await EncryptDataAsync(data, _aesEncryption);
|
||||||
// Keep as binary
|
|
||||||
}
|
// Prepend 4-byte length for framing
|
||||||
|
var lengthPrefix = BitConverter.GetBytes(data.Length);
|
||||||
OnDataReceived?.Invoke(this, new DataReceivedEventArgs
|
if (BitConverter.IsLittleEndian) Array.Reverse(lengthPrefix);
|
||||||
{
|
|
||||||
ClientId = "server",
|
var framed = new byte[lengthPrefix.Length + data.Length];
|
||||||
Data = data,
|
Buffer.BlockCopy(lengthPrefix, 0, framed, 0, lengthPrefix.Length);
|
||||||
StringData = stringData,
|
Buffer.BlockCopy(data, 0, framed, lengthPrefix.Length, data.Length);
|
||||||
IsBinary = isBinary
|
|
||||||
});
|
data = framed;
|
||||||
}
|
}
|
||||||
catch (Exception ex)
|
|
||||||
{
|
if (_config.Protocol == ProtocolType.TCP)
|
||||||
if (_config.UseAesEncryption)
|
{
|
||||||
{
|
await _stream.WriteAsync(data, 0, data.Length);
|
||||||
OnEncryptionError?.Invoke(this, new ErrorEventArgs { Exception = ex, Message = "Error decrypting data" });
|
await _stream.FlushAsync();
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
OnGeneralError?.Invoke(this, new ErrorEventArgs { Exception = ex, Message = "Error processing received data" });
|
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)
|
public async Task SendAsync(string message)
|
||||||
{
|
{
|
||||||
|
|
|
@ -243,103 +243,131 @@ namespace EonaCat.Connections
|
||||||
await ProcessReceivedDataAsync(client, result.Buffer);
|
await ProcessReceivedDataAsync(client, result.Buffer);
|
||||||
}
|
}
|
||||||
|
|
||||||
private async Task HandleClientCommunicationAsync(Connection client)
|
private async Task HandleClientCommunicationAsync(Connection client)
|
||||||
{
|
{
|
||||||
var buffer = new byte[_config.BufferSize];
|
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<int> 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)
|
private async Task ProcessReceivedDataAsync(Connection client, byte[] data)
|
||||||
{
|
{
|
||||||
break; // Client disconnected
|
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)
|
public async Task SendToClientAsync(string clientId, byte[] data)
|
||||||
{
|
{
|
||||||
|
@ -369,46 +397,54 @@ namespace EonaCat.Connections
|
||||||
await BroadcastAsync(Encoding.UTF8.GetBytes(message));
|
await BroadcastAsync(Encoding.UTF8.GetBytes(message));
|
||||||
}
|
}
|
||||||
|
|
||||||
private async Task SendDataAsync(Connection client, byte[] data)
|
private async Task SendDataAsync(Connection client, byte[] data)
|
||||||
{
|
{
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
// Encrypt if AES encryption is enabled
|
if (client.IsEncrypted && client.AesEncryption != null)
|
||||||
if (client.IsEncrypted && client.AesEncryption != null)
|
{
|
||||||
{
|
// Encrypt payload
|
||||||
data = await EncryptDataAsync(data, client.AesEncryption);
|
data = await EncryptDataAsync(data, client.AesEncryption);
|
||||||
}
|
|
||||||
|
// Prepend length for safe framing
|
||||||
if (_config.Protocol == ProtocolType.TCP)
|
var lengthPrefix = BitConverter.GetBytes(data.Length);
|
||||||
{
|
if (BitConverter.IsLittleEndian)
|
||||||
await client.Stream.WriteAsync(data, 0, data.Length);
|
Array.Reverse(lengthPrefix);
|
||||||
await client.Stream.FlushAsync();
|
|
||||||
}
|
var framed = new byte[lengthPrefix.Length + data.Length];
|
||||||
else
|
Buffer.BlockCopy(lengthPrefix, 0, framed, 0, lengthPrefix.Length);
|
||||||
{
|
Buffer.BlockCopy(data, 0, framed, lengthPrefix.Length, data.Length);
|
||||||
await _udpListener.SendAsync(data, data.Length, client.RemoteEndPoint);
|
|
||||||
}
|
data = framed; // replace data with framed payload
|
||||||
|
}
|
||||||
client.BytesSent += data.Length;
|
|
||||||
lock (_statsLock)
|
if (_config.Protocol == ProtocolType.TCP)
|
||||||
{
|
{
|
||||||
_stats.BytesSent += data.Length;
|
await client.Stream.WriteAsync(data, 0, data.Length);
|
||||||
_stats.MessagesSent++;
|
await client.Stream.FlushAsync();
|
||||||
}
|
}
|
||||||
}
|
else
|
||||||
catch (Exception ex)
|
{
|
||||||
{
|
await _udpListener.SendAsync(data, data.Length, client.RemoteEndPoint);
|
||||||
if (client.IsEncrypted)
|
}
|
||||||
{
|
|
||||||
OnEncryptionError?.Invoke(this, new ErrorEventArgs { ClientId = client.Id, Exception = ex, Message = "Error encrypting/sending data" });
|
client.BytesSent += data.Length;
|
||||||
}
|
lock (_statsLock)
|
||||||
else
|
{
|
||||||
{
|
_stats.BytesSent += data.Length;
|
||||||
OnGeneralError?.Invoke(this, new ErrorEventArgs { ClientId = client.Id, Exception = ex, Message = "Error sending data" });
|
_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<byte[]> EncryptDataAsync(byte[] data, Aes aes)
|
private async Task<byte[]> EncryptDataAsync(byte[] data, Aes aes)
|
||||||
{
|
{
|
||||||
using (var encryptor = aes.CreateEncryptor())
|
using (var encryptor = aes.CreateEncryptor())
|
||||||
|
|
Loading…
Reference in New Issue