diff --git a/EonaCat.Network/EonaCat.Network.csproj b/EonaCat.Network/EonaCat.Network.csproj
index 698e847..f0177ef 100644
--- a/EonaCat.Network/EonaCat.Network.csproj
+++ b/EonaCat.Network/EonaCat.Network.csproj
@@ -15,10 +15,10 @@
https://www.nuget.org/packages/EonaCat.Network/
EonaCat, Network, .NET Standard, EonaCatHelpers, Jeroen, Saey, Protocol, Quic, UDP, TCP, Web, Server
- EonaCat Networking library with Quic, TCP, UDP and a Webserver
- 1.0.9
- 1.0.0.9
- 1.0.0.9
+ EonaCat Networking library with Quic, TCP, UDP, WebSockets and a Webserver
+ 1.1.0
+ 1.1.0.0
+ 1.1.0.0
icon.png
@@ -52,9 +52,6 @@
-
-
-
-
+
diff --git a/EonaCat.Network/System/Sockets/RemoteInfo.cs b/EonaCat.Network/System/Sockets/RemoteInfo.cs
index 347f1db..8cbf966 100644
--- a/EonaCat.Network/System/Sockets/RemoteInfo.cs
+++ b/EonaCat.Network/System/Sockets/RemoteInfo.cs
@@ -13,5 +13,8 @@ namespace EonaCat.Network
public int Port => HasEndpoint ? ((IPEndPoint)EndPoint).Port : 0;
public Socket Socket { get; set; }
public bool IsIpv6 { get; set; }
+ public bool IsWebSocket { get; internal set; }
+ public string ClientId { get; internal set; }
+ public string ClientName { get; internal set; }
}
}
diff --git a/EonaCat.Network/System/Sockets/Web/WebSocketSecureClient.cs b/EonaCat.Network/System/Sockets/Web/WebSocketSecureClient.cs
new file mode 100644
index 0000000..c90eb5d
--- /dev/null
+++ b/EonaCat.Network/System/Sockets/Web/WebSocketSecureClient.cs
@@ -0,0 +1,133 @@
+using System;
+using System.Net.WebSockets;
+using System.Security.Cryptography.X509Certificates;
+using System.Text;
+using System.Threading;
+using System.Threading.Tasks;
+
+namespace EonaCat.Network
+{
+ public class WebSocketSecureClient
+ {
+ private const int BUFFER_SIZE = 4096;
+
+ ///
+ /// OnConnect event
+ ///
+ public event Action OnConnect;
+
+ ///
+ /// OnReceive event
+ ///
+ public event Action OnReceive;
+
+ ///
+ /// OnDisconnect event
+ ///
+ public event Action OnDisconnect;
+
+ ///
+ /// OnError event
+ ///
+ public event Action OnError;
+
+ private ClientWebSocket _webSocket;
+
+ ///
+ /// The client name to be sent when connecting (optional).
+ ///
+ public string ClientName { get; set; }
+
+ ///
+ /// Create secure WebSocket client with certificate support
+ ///
+ ///
+ /// The client certificate for the connection
+ /// The password for the connection
+ ///
+ public Task ConnectAsync(string uri, X509Certificate2 clientCertificate = null, string password = null)
+ {
+ return CreateWebSocketClientAsync(uri, clientCertificate, password);
+ }
+
+ private async Task CreateWebSocketClientAsync(string uri, X509Certificate2 clientCertificate, string password)
+ {
+ _webSocket = new ClientWebSocket();
+
+ if (clientCertificate != null)
+ {
+ _webSocket.Options.ClientCertificates.Add(clientCertificate);
+ }
+
+ if (!string.IsNullOrEmpty(password))
+ {
+ var passwordBytes = Encoding.UTF8.GetBytes(password);
+ _webSocket.Options.SetRequestHeader("Password", Convert.ToBase64String(passwordBytes));
+ }
+
+ if (!string.IsNullOrEmpty(ClientName))
+ {
+ _webSocket.Options.SetRequestHeader("ClientName", ClientName);
+ }
+
+ try
+ {
+ Uri serverUri = new Uri(uri);
+ await _webSocket.ConnectAsync(serverUri, CancellationToken.None).ConfigureAwait(false);
+ OnConnect?.Invoke(new RemoteInfo { IsWebSocket = true });
+ _ = StartReceivingAsync();
+ }
+ catch (Exception ex)
+ {
+ OnError?.Invoke(ex, $"Exception: {ex.Message}");
+ Disconnect();
+ }
+ }
+
+ private async Task StartReceivingAsync()
+ {
+ byte[] buffer = new byte[BUFFER_SIZE];
+ while (_webSocket.State == WebSocketState.Open)
+ {
+ try
+ {
+ var result = await _webSocket.ReceiveAsync(new ArraySegment(buffer), CancellationToken.None).ConfigureAwait(false);
+ if (result.Count > 0)
+ {
+ byte[] data = new byte[result.Count];
+ Buffer.BlockCopy(buffer, 0, data, 0, result.Count);
+ OnReceive?.Invoke(new RemoteInfo { IsWebSocket = true, Data = data });
+ }
+ else
+ {
+ break;
+ }
+ }
+ catch (Exception ex)
+ {
+ OnError?.Invoke(ex, $"Exception: {ex.Message}");
+ break;
+ }
+ }
+ OnDisconnect?.Invoke(new RemoteInfo { IsWebSocket = true });
+ }
+
+ ///
+ /// Send data
+ ///
+ ///
+ ///
+ public Task SendAsync(byte[] data)
+ {
+ return _webSocket.SendAsync(new ArraySegment(data), WebSocketMessageType.Binary, true, CancellationToken.None);
+ }
+
+ ///
+ /// Disconnect
+ ///
+ public void Disconnect()
+ {
+ _webSocket?.CloseAsync(WebSocketCloseStatus.NormalClosure, "Disconnecting", CancellationToken.None);
+ }
+ }
+}
diff --git a/EonaCat.Network/System/Sockets/Web/WebSocketSecureServer.cs b/EonaCat.Network/System/Sockets/Web/WebSocketSecureServer.cs
new file mode 100644
index 0000000..6f5351d
--- /dev/null
+++ b/EonaCat.Network/System/Sockets/Web/WebSocketSecureServer.cs
@@ -0,0 +1,190 @@
+using System;
+using System.Collections.Generic;
+using System.Net;
+using System.Net.WebSockets;
+using System.Security.Cryptography.X509Certificates;
+using System.Text;
+using System.Threading;
+using System.Threading.Tasks;
+
+namespace EonaCat.Network
+{
+ public class WebSocketSecureServer
+ {
+ private const int BUFFER_SIZE = 4096;
+
+ ///
+ /// OnConnect event
+ ///
+ public event Action OnConnect;
+
+ ///
+ /// OnReceive event
+ ///
+ public event Action OnReceive;
+
+ ///
+ /// OnSend event
+ ///
+ public event Action OnSend;
+
+ ///
+ /// OnDisconnect event
+ ///
+ public event Action OnDisconnect;
+
+ ///
+ /// OnError event
+ ///
+ public event Action OnError;
+
+ private readonly HttpListener _listener;
+ private CancellationTokenSource _cancellationTokenSource;
+ private Task _acceptTask;
+ private readonly X509Certificate2 _serverCertificate;
+ private readonly string _requiredPassword;
+ private bool _passwordProtectionEnabled;
+
+ private readonly Dictionary _connectedClients = new Dictionary();
+ private readonly Dictionary _clientNames = new Dictionary();
+
+ ///
+ /// Create secure WebSocket server with certificate support
+ ///
+ /// Array of URI prefixes to listen on, e.g., "https://localhost:8443/"
+ /// The server certificate for HTTPS
+ /// The password required from clients for the connection (optional)
+ public WebSocketSecureServer(List uriPrefixes, X509Certificate2 serverCertificate = null, string requiredPassword = null)
+ {
+ _listener = new HttpListener();
+ _serverCertificate = serverCertificate;
+ _requiredPassword = requiredPassword;
+ _passwordProtectionEnabled = !string.IsNullOrEmpty(requiredPassword);
+
+ foreach (var uriPrefix in uriPrefixes)
+ {
+ _listener.Prefixes.Add(uriPrefix);
+ }
+ }
+
+ ///
+ /// Start secure WebSocket server
+ ///
+ ///
+ ///
+ public Task StartAsync(CancellationToken cancellationToken = default)
+ {
+ _listener.Start();
+ _cancellationTokenSource = CancellationTokenSource.CreateLinkedTokenSource(cancellationToken);
+ _acceptTask = AcceptConnectionsAsync(_cancellationTokenSource.Token);
+ return _acceptTask;
+ }
+
+ private async Task AcceptConnectionsAsync(CancellationToken cancellationToken)
+ {
+ while (!cancellationToken.IsCancellationRequested)
+ {
+ try
+ {
+ var context = await _listener.GetContextAsync().ConfigureAwait(false);
+
+ if (context.Request.IsWebSocketRequest)
+ {
+ if (_passwordProtectionEnabled)
+ {
+ var password = context.Request.Headers["Password"];
+
+ if (password != _requiredPassword)
+ {
+ context.Response.StatusCode = 401; // Unauthorized
+ context.Response.Close();
+ continue;
+ }
+ }
+
+ string clientName = null;
+ foreach (var key in context.Request.Headers.AllKeys)
+ {
+ if (key == "ClientName")
+ {
+ clientName = context.Request.Headers["ClientName"];
+ break;
+ }
+ }
+
+ var webSocketContext = await context.AcceptWebSocketAsync(subProtocol: null).ConfigureAwait(false);
+ _ = HandleWebSocketConnectionAsync(webSocketContext.WebSocket, clientName, cancellationToken);
+ }
+ else
+ {
+ context.Response.StatusCode = 400;
+ context.Response.Close();
+ }
+ }
+ catch (Exception ex)
+ {
+ OnError?.Invoke(ex, $"Exception: {ex.Message}");
+ }
+ }
+ }
+
+ private async Task HandleWebSocketConnectionAsync(WebSocket webSocket, string clientName, CancellationToken cancellationToken)
+ {
+ string clientId = Guid.NewGuid().ToString();
+ _connectedClients.Add(clientId, webSocket);
+
+ if (!string.IsNullOrEmpty(clientName))
+ {
+ _clientNames.TryAdd(clientId, clientName);
+ }
+
+ OnConnect?.Invoke(new RemoteInfo { IsWebSocket = true, ClientId = clientId, ClientName = clientName });
+
+ byte[] buffer = new byte[BUFFER_SIZE];
+ while (webSocket.State == WebSocketState.Open)
+ {
+ try
+ {
+ var result = await webSocket.ReceiveAsync(new ArraySegment(buffer), cancellationToken).ConfigureAwait(false);
+ if (result.Count > 0)
+ {
+ byte[] data = new byte[result.Count];
+ Buffer.BlockCopy(buffer, 0, data, 0, result.Count);
+ OnReceive?.Invoke(new RemoteInfo { IsWebSocket = true, ClientId = clientId, ClientName = clientName, Data = data });
+ }
+ else
+ {
+ break;
+ }
+ }
+ catch (Exception ex)
+ {
+ OnError?.Invoke(ex, $"Exception: {ex.Message}");
+ break;
+ }
+ }
+
+ OnDisconnect?.Invoke(new RemoteInfo { IsWebSocket = true, ClientId = clientId, ClientName = clientName });
+ _connectedClients.Remove(clientId);
+ _clientNames.Remove(clientId);
+ }
+
+ ///
+ /// Stop secure WebSocket server
+ ///
+ ///
+ public async Task StopAsync()
+ {
+ _cancellationTokenSource.Cancel();
+ _listener.Stop();
+ if (_acceptTask != null)
+ {
+ try
+ {
+ await _acceptTask.ConfigureAwait(false);
+ }
+ catch (AggregateException) { }
+ }
+ }
+ }
+}
diff --git a/README.md b/README.md
index b1ea2f3..de5e416 100644
--- a/README.md
+++ b/README.md
@@ -2,4 +2,5 @@
------------
-EonaCat Network Library
\ No newline at end of file
+EonaCat Network Library
+