diff --git a/EonaCat.Network/System/Sockets/Tcp/SocketTcpClient.cs b/EonaCat.Network/System/Sockets/Tcp/SocketTcpClient.cs index 8206a27..d30cf0f 100644 --- a/EonaCat.Network/System/Sockets/Tcp/SocketTcpClient.cs +++ b/EonaCat.Network/System/Sockets/Tcp/SocketTcpClient.cs @@ -23,6 +23,11 @@ namespace EonaCat.Network public event Action OnDisconnect; public event Action OnError; + public async Task ConnectAsync(IPAddress ipAddress, int port, bool useSsl = false, SslOptions sslOptions = null) + { + await CreateSocketTcpClientAsync(ipAddress, port, useSsl, sslOptions); + } + public async Task ConnectAsync(string ipAddress, int port, bool useSsl = false, SslOptions sslOptions = null) { if (!IPAddress.TryParse(ipAddress, out var ip)) diff --git a/EonaCat.Network/System/Sockets/Tcp/SocketTcpServer.cs b/EonaCat.Network/System/Sockets/Tcp/SocketTcpServer.cs index 8d24a29..e21871a 100644 --- a/EonaCat.Network/System/Sockets/Tcp/SocketTcpServer.cs +++ b/EonaCat.Network/System/Sockets/Tcp/SocketTcpServer.cs @@ -32,8 +32,12 @@ namespace EonaCat.Network public SocketTcpServer(string ipAddress, int port, X509Certificate2 certificate = null, SslOptions sslOptions = null) { - var address = IPAddress.Parse(ipAddress); - _listener = new TcpListener(address, port); + if (!IPAddress.TryParse(ipAddress, out var ip)) + { + throw new ArgumentException("Invalid ipAddress given"); + } + + _listener = new TcpListener(ip, port); _certificate = certificate; _sslOptions = sslOptions; } diff --git a/EonaCat.Network/System/Sockets/Udp/SocketUdpClient.cs b/EonaCat.Network/System/Sockets/Udp/SocketUdpClient.cs index 129d3f6..01293ab 100644 --- a/EonaCat.Network/System/Sockets/Udp/SocketUdpClient.cs +++ b/EonaCat.Network/System/Sockets/Udp/SocketUdpClient.cs @@ -21,6 +21,22 @@ public class SocketUdpClient CreateUdpClient(ipAddress, port, cancellationToken); } + /// + /// Create UDP client + /// + /// + /// + /// + public SocketUdpClient(string ipAddress, int port, CancellationToken cancellationToken = default) + { + if (!IPAddress.TryParse(ipAddress, out var ip)) + { + throw new ArgumentException("Invalid ipAddress given"); + } + + CreateUdpClient(ip, port, cancellationToken); + } + public bool IsMulticastGroupEnabled { get; set; } public bool IsIp6 { get; private set; } diff --git a/EonaCat.Network/System/Sockets/Udp/SocketUdpServer.cs b/EonaCat.Network/System/Sockets/Udp/SocketUdpServer.cs index 92f8ebc..4017a93 100644 --- a/EonaCat.Network/System/Sockets/Udp/SocketUdpServer.cs +++ b/EonaCat.Network/System/Sockets/Udp/SocketUdpServer.cs @@ -17,6 +17,19 @@ namespace EonaCat.Network public event Action OnSend; public event Action OnError; + public SocketUdpServer(string ipAddress, int port) + { + if (!IPAddress.TryParse(ipAddress, out var ip)) + { + throw new ArgumentException("Invalid ipAddress given"); + } + + _udpClient = new UdpClient(ip.AddressFamily); + IsIPv6 = ip.AddressFamily == AddressFamily.InterNetworkV6; + _udpClient.Client.SetSocketOption(SocketOptionLevel.Socket, SocketOptionName.ReuseAddress, true); + _udpClient.Client.Bind(new IPEndPoint(ip, port)); + } + public SocketUdpServer(IPAddress ipAddress, int port) { _udpClient = new UdpClient(ipAddress.AddressFamily); diff --git a/EonaCat.Network/icon.png b/EonaCat.Network/icon.png index 0595b89..15aaf35 100644 Binary files a/EonaCat.Network/icon.png and b/EonaCat.Network/icon.png differ diff --git a/README.md b/README.md index de5e416..8ceb931 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,564 @@ -# EonaCat Network +# EonaCat.Network ------------- +[![NuGet](https://img.shields.io/nuget/v/EonaCat.Network.svg)](https://www.nuget.org/packages/EonaCat.Network/) +[![NuGet Downloads](https://img.shields.io/nuget/dt/EonaCat.Network.svg)](https://www.nuget.org/packages/EonaCat.Network/) +[![License](https://img.shields.io/badge/license-Apache%202.0-blue.svg)](LICENSE) -EonaCat Network Library +**EonaCat.Network** is a .NET networking library that provides a common set of APIs for building networked applications and services. +The library includes support for: + +- TCP client and server communication +- UDP client and server communication +- WebSockets +- An embedded HTTP/HTTPS web server +- HTTP routing, including static and parameterized routes +- QUIC client/server and stream primitives +- IPv4 and IPv6 networking +- TCP SSL/TLS support +- WebSocket extensions, fragmentation and compression support +- Network-related helper and utility classes + +## Installation + +Install the NuGet package: + +```bash +dotnet add package EonaCat.Network +``` + +NuGet: + +https://www.nuget.org/packages/EonaCat.Network/ + +## Target Frameworks + +The library currently targets: + +- .NET Framework 4.8 (`net48`) +- .NET Standard 2.1 +- .NET 6 +- .NET 7 +- .NET 8 + +## Quick Start + +### TCP server + +```csharp +using EonaCat.Network; +using System.Net; +using System.Threading; +using System.Threading.Tasks; + +var server = new SocketTcpServer(IPAddress.Any, 5000); + +server.OnConnect += remote => +{ + Console.WriteLine("Client connected"); +}; + +server.OnReceive += (remote, message) => +{ + Console.WriteLine($"Received: {message}"); +}; + +server.OnDisconnect += remote => +{ + Console.WriteLine("Client disconnected"); +}; + +server.OnError += (exception, message) => +{ + Console.WriteLine(message); +}; + +await server.StartAsync(CancellationToken.None); +``` + +### TCP client + +```csharp +using EonaCat.Network; + +var client = new SocketTcpClient(); + +client.OnConnect += remote => +{ + Console.WriteLine("Connected"); +}; + +client.OnReceive += remote => +{ + Console.WriteLine($"Received {remote.Data?.Length ?? 0} bytes"); +}; + +client.OnDisconnect += remote => +{ + Console.WriteLine("Disconnected"); +}; + +client.OnError += (exception, message) => +{ + Console.WriteLine(message); +}; + +await client.ConnectAsync("127.0.0.1", 5000); + +await client.SendAsync( + System.Text.Encoding.UTF8.GetBytes("Hello from EonaCat.Network!")); +``` + +### UDP server + +```csharp +using EonaCat.Network; +using System.Net; + +var server = new SocketUdpServer(IPAddress.Any, 5001); + +server.OnReceive += remote => +{ + Console.WriteLine($"Received {remote.Data?.Length ?? 0} bytes"); +}; + +server.OnSend += remote => +{ + Console.WriteLine("UDP packet sent"); +}; + +server.OnError += (exception, message) => +{ + Console.WriteLine(message); +}; + +await server.StartAsync(); +``` + +Send a packet: + +```csharp +await server.SendToAsync( + new IPEndPoint(IPAddress.Loopback, 5002), + System.Text.Encoding.UTF8.GetBytes("Hello UDP")); +``` + +### UDP client + +```csharp +using EonaCat.Network; +using System.Net; + +var client = new SocketUdpClient(IPAddress.Loopback, 5001); + +client.OnReceive += data => +{ + Console.WriteLine($"Received {data.Length} bytes"); +}; + +client.OnError += (exception, message) => +{ + Console.WriteLine(message); +}; + +await client.SendTo( + new IPEndPoint(IPAddress.Loopback, 5001), + System.Text.Encoding.UTF8.GetBytes("Hello UDP")); +``` + +## Embedded Web Server + +The library includes an HTTP/HTTPS web server exposed through `WebServer` (`EonaCat.Network.WebServer`). + +### Start a web server + +```csharp +using EonaCat.Network; + +var server = new WebServer("localhost", 8080); + +await server.StartAsync(); +``` + +For synchronous startup: + +```csharp +server.Start(); +``` + +Stop it with: + +```csharp +server.Stop(); +``` + +Or dispose it: + +```csharp +server.Dispose(); +``` + +### Web server settings + +You can configure the server through `EonaCatWebserverSettings`: + +```csharp +using EonaCat.Network; + +var settings = new EonaCatWebserverSettings("localhost", 8080); + +settings.IO.MaxRequests = 1024; +settings.IO.StreamBufferSize = 65536; + +settings.Debug.Requests = true; +settings.Debug.Responses = true; +settings.Debug.Routing = true; + +var server = new WebServer(settings); + +await server.StartAsync(); +``` + +The settings API includes: + +- Listening prefixes +- I/O buffer size +- Maximum concurrent requests +- SSL settings +- Default response headers +- Access-control configuration +- Debug logging options + +> **Security note:** review the SSL and access-control configuration before exposing a server to an untrusted network. The current settings class allows invalid certificates by default, so production deployments should explicitly configure certificate validation behavior appropriate to the application. + +### Web server events + +The server exposes events for common lifecycle and HTTP operations through `server.Events`, including: + +- `ConnectionReceived` +- `RequestReceived` +- `RequestDenied` +- `RequestorDisconnected` +- `ResponseSent` +- `ExceptionEncountered` +- `ServerStarted` +- `ServerStopped` +- `ServerDisposing` + +Example: + +```csharp +server.Events.ServerStarted += (_, _) => +{ + Console.WriteLine("Web server started"); +}; + +server.Events.RequestReceived += (_, args) => +{ + Console.WriteLine("HTTP request received"); +}; +``` + +## HTTP Routing + +EonaCat.Network provides several route types. + +### Static routes + +`StaticRouteManager` matches an HTTP method and exact path. + +```csharp +var routes = new StaticRouteManager(); + +routes.Add( + HttpMethod.GET, + "/hello", + async context => + { + // Build and send the response here. + await Task.CompletedTask; + }); +``` + +Static routes normalize paths to begin and end with `/` and use case-insensitive matching. + +Useful operations include: + +```csharp +routes.Add(...); +routes.Get(HttpMethod.GET, "/hello"); +routes.Exists(HttpMethod.GET, "/hello"); +routes.Remove(HttpMethod.GET, "/hello"); +``` + +### Parameter routes + +`ParameterRouteManager` supports URL parameters such as: + +```text +GET /api/{version}/users +``` + +Example: + +```csharp +var routes = new ParameterRouteManager(); + +routes.Add( + HttpMethod.GET, + "/api/{version}/users", + async context => + { + await Task.CompletedTask; + }); +``` + +The matching operation returns the extracted values: + +```csharp +Dictionary values; +ParameterRoute route; + +var handler = routes.Match( + HttpMethod.GET, + "/api/v1/users", + out values, + out route); +``` + +For the example above, `values` can contain: + +```text +version = v1 +``` + +## WebSockets + +The library contains asynchronous WebSocket client and server implementations under the `EonaCat.WebSockets` namespace. + +Supported functionality includes: + +- Text messages +- Binary messages +- Streaming binary data +- Sessions +- Broadcasting +- Fragmentation +- Close handling +- Sub-protocol support +- WebSocket extensions +- Per-message compression support + +### WebSocket client + +```csharp +using EonaCat.WebSockets; + +var client = new AsyncWebSocketClient( + new Uri("ws://localhost:8080/")); + +await client.ConnectAsync(); + +await client.SendTextAsync("Hello WebSocket!"); +``` + +Binary data: + +```csharp +await client.SendBinaryAsync( + System.Text.Encoding.UTF8.GetBytes("Binary message")); +``` + +Close the connection: + +```csharp +await client.CloseAsync(); +``` + +### WebSocket server + +The server uses `AsyncWebSocketServerModule` implementations to handle sessions and messages. + +A module can override handlers such as: + +```csharp +public override async Task OnSessionStarted( + AsyncWebSocketSession session) +{ + await session.SendTextAsync("Welcome!"); +} + +public override async Task OnSessionTextReceived( + AsyncWebSocketSession session, + string text) +{ + await session.SendTextAsync($"Echo: {text}"); +} + +public override async Task OnSessionBinaryReceived( + AsyncWebSocketSession session, + byte[] data, + int offset, + int count) +{ + await Task.CompletedTask; +} + +public override async Task OnSessionClosed( + AsyncWebSocketSession session) +{ + await base.OnSessionClosed(session); +} +``` + +Server instances can send to individual sessions or broadcast to all connected sessions: + +```csharp +await server.SendTextToAsync(sessionKey, "Hello"); + +await server.BroadcastTextAsync("Message for everyone"); +``` + +Binary equivalents are also available. + +## QUIC + +EonaCat.Network contains a QUIC implementation with connection and stream abstractions. + +The main public types include: + +- `QuicClient` +- `QuicServer` +- `QuicConnection` +- `QuicStream` +- `StreamType` +- QUIC packet and frame infrastructure +- QUIC settings and protocol helpers + +### QUIC client + +```csharp +using EonaCat.Quic; + +var client = new QuicClient(); + +var connection = client.Connect("127.0.0.1", 11000); + +var stream = connection.CreateStream( + StreamType.ClientBidirectional); + +stream.Send( + System.Text.Encoding.UTF8.GetBytes("Hello QUIC")); +``` + +### QUIC server + +```csharp +using EonaCat.Quic; + +var server = new QuicServer("0.0.0.0", 11000); + +server.OnClientConnected += connection => +{ + Console.WriteLine("QUIC client connected"); +}; + +server.Start(); +``` + +> **Important:** the QUIC implementation is part of this library's own protocol stack. Validate the protocol behavior, interoperability and security characteristics against your application's requirements before using it for production Internet-facing workloads. + +## `NetworkHelper` + +For simple QUIC scenarios, `NetworkHelper` provides convenience methods: + +```csharp +using EonaCat.Network; + +NetworkHelper.QuicStartServer("0.0.0.0", 11000); + +var stream = NetworkHelper.QuicStartClient( + "127.0.0.1", + 11000); + +NetworkHelper.QuicStopServer(); +``` + +It also exposes events for: + +- `OnQuicClientConnected` +- `OnQuicStreamOpened` +- `OnQuicStreamDataReceived` + +The default global encoding is UTF-8: + +```csharp +NetworkHelper.GlobalEncoding = System.Text.Encoding.UTF8; +``` + +## IPv4 and IPv6 + +The socket implementations support IPv4 and IPv6 where supported by the underlying .NET socket APIs. + +The library also exposes: + +```csharp +public enum IPType : byte +{ + IPv4, + IPv6 +} +``` + +TCP and UDP implementations expose IPv6-related information through their connection/remote information objects. + +## TCP SSL/TLS + +TCP clients and servers can use SSL/TLS. + +Client example: + +```csharp +var client = new SocketTcpClient(); + +await client.ConnectAsync( + "127.0.0.1", + 5000, + useSsl: true, + sslOptions: new SslOptions()); +``` + +The TCP server accepts a certificate and SSL options: + +```csharp +var server = new SocketTcpServer( + "0.0.0.0", + 5000, + certificate, + sslOptions); +``` + +Always use certificates and TLS settings appropriate for the environment in which the application is deployed. + +## Dependencies + +The package uses several EonaCat and .NET dependencies, including: + +- EonaCat.Controls +- EonaCat.Json +- EonaCat.LogStack +- EonaCat.LogSystem +- EonaCat.Matchers +- EonaCat.Versioning +- System.Net.WebSockets +- System.Text.Encodings.Web + +These are restored automatically when installing the NuGet package. + +## License + +EonaCat.Network is released under the **Apache License 2.0**. + +See [LICENSE](LICENSE) for the complete license text. + +Copyright © EonaCat (Jeroen Saey). diff --git a/icon.png b/icon.png index 0595b89..15aaf35 100644 Binary files a/icon.png and b/icon.png differ