diff --git a/EonaCat.Network/EonaCat.Network.csproj b/EonaCat.Network/EonaCat.Network.csproj index 443f940..d0f4ebe 100644 --- a/EonaCat.Network/EonaCat.Network.csproj +++ b/EonaCat.Network/EonaCat.Network.csproj @@ -13,16 +13,27 @@ EonaCat, Network, .NET Standard, EonaCatHelpers, Jeroen, Saey, Protocol, Quic, UDP, TCP, Web, Server EonaCat Networking library with Quic, TCP, UDP, WebSockets and a Webserver - 1.1.6 - 1.1.6 - 1.1.6 + 1.1.7 + 1.1.7 + 1.1.7 icon.png README.md LICENSE + + 1.1.7+{chash:10}.{c:ymd} + true + true + v[0-9]* + true + git + true + true + + - false + True True README.md False @@ -49,10 +60,14 @@ - - + + + + all + runtime; build; native; contentfiles; analyzers; buildtransitive + diff --git a/EonaCat.Network/System/Sockets/RemoteInfo.cs b/EonaCat.Network/System/Sockets/RemoteInfo.cs index 8ab1fd6..440cf30 100644 --- a/EonaCat.Network/System/Sockets/RemoteInfo.cs +++ b/EonaCat.Network/System/Sockets/RemoteInfo.cs @@ -14,6 +14,7 @@ public class RemoteInfo public IPAddress Address => HasEndpoint ? ((IPEndPoint)EndPoint).Address : null; public int Port => HasEndpoint ? ((IPEndPoint)EndPoint).Port : 0; public Socket Socket { get; set; } + public string NickName { get; set; } public bool IsIPv6 { get; set; } public bool IsWebSocket { get; internal set; } public string ClientId { get; internal set; } diff --git a/EonaCat.Network/System/Sockets/Tcp/SocketTcpServer.cs b/EonaCat.Network/System/Sockets/Tcp/SocketTcpServer.cs index c6244eb..8d24a29 100644 --- a/EonaCat.Network/System/Sockets/Tcp/SocketTcpServer.cs +++ b/EonaCat.Network/System/Sockets/Tcp/SocketTcpServer.cs @@ -1,4 +1,5 @@ using System; +using System.Collections.Concurrent; using System.IO; using System.Net; using System.Net.Security; @@ -20,6 +21,8 @@ namespace EonaCat.Network private Task _acceptTask; private CancellationTokenSource _cancellationTokenSource; + private readonly ConcurrentDictionary _clientNicknames = new ConcurrentDictionary(); + public SocketTcpServer(IPAddress ipAddress, int port, X509Certificate2 certificate = null, SslOptions sslOptions = null) { _listener = new TcpListener(ipAddress, port); @@ -36,7 +39,7 @@ namespace EonaCat.Network } public event Action OnConnect; - public event Action OnReceive; + public event Action OnReceive; public event Action OnSend; public event Action OnDisconnect; public event Action OnError; @@ -45,8 +48,8 @@ namespace EonaCat.Network { _listener.Start(); _cancellationTokenSource = CancellationTokenSource.CreateLinkedTokenSource(cancellationToken); - _acceptTask = AcceptConnectionsAsync(_cancellationTokenSource.Token); - return _acceptTask; + _acceptTask = Task.Run(() => AcceptConnectionsAsync(_cancellationTokenSource.Token)); + return Task.CompletedTask; } private async Task AcceptConnectionsAsync(CancellationToken cancellationToken) @@ -56,7 +59,7 @@ namespace EonaCat.Network try { var tcpClient = await _listener.AcceptTcpClientAsync().ConfigureAwait(false); - _ = HandleConnectionAsync(tcpClient, cancellationToken); + _ = Task.Run(() => HandleConnectionAsync(tcpClient, cancellationToken)); } catch (SocketException ex) { @@ -70,12 +73,16 @@ namespace EonaCat.Network var remoteEndpoint = tcpClient.Client.RemoteEndPoint; using (tcpClient) { + var nickName = await GetNicknameAsync(tcpClient).ConfigureAwait(false); + _clientNicknames.TryAdd(tcpClient, nickName); + OnConnect?.Invoke(new RemoteInfo { Socket = tcpClient.Client, IsTcp = true, IsIPv6 = remoteEndpoint.AddressFamily == AddressFamily.InterNetworkV6, - EndPoint = remoteEndpoint + EndPoint = remoteEndpoint, + NickName = nickName }); Stream stream = tcpClient.GetStream(); @@ -84,7 +91,7 @@ namespace EonaCat.Network var sslStream = new SslStream(stream, false, ValidateRemoteCertificate, null); try { - await sslStream.AuthenticateAsServerAsync(_certificate, _sslOptions.ClientCertificateRequired, _sslOptions.SslProtocol, _sslOptions.CheckCertificateRevocation); + await sslStream.AuthenticateAsServerAsync(_certificate, _sslOptions.ClientCertificateRequired, _sslOptions.SslProtocol, _sslOptions.CheckCertificateRevocation).ConfigureAwait(false); stream = sslStream; } catch (AuthenticationException ex) @@ -110,8 +117,9 @@ namespace EonaCat.Network IsTcp = true, IsIPv6 = remoteEndpoint.AddressFamily == AddressFamily.InterNetworkV6, EndPoint = remoteEndpoint, + NickName = nickName, Data = data - }); + }, nickName); } else { @@ -124,20 +132,47 @@ namespace EonaCat.Network break; } } + + _clientNicknames.TryRemove(tcpClient, out _); + OnDisconnect?.Invoke(new RemoteInfo + { + Socket = tcpClient.Client, + IsTcp = true, + EndPoint = remoteEndpoint, + IsIPv6 = remoteEndpoint != null && remoteEndpoint.AddressFamily == AddressFamily.InterNetworkV6, + NickName = nickName + }); + } + } + + private async Task GetNicknameAsync(TcpClient tcpClient) + { + if (_clientNicknames.TryGetValue(tcpClient, out string nickname)) + { + return await Task.FromResult(nickname).ConfigureAwait(false); + } + else + { + // Return a default nickname if one is not set + return await Task.FromResult("Anonymous").ConfigureAwait(false); + } + } + + public bool SetNickname(TcpClient tcpClient, string nickname) + { + if (tcpClient != null && !_clientNicknames.ContainsKey(tcpClient)) + { + return false; } - OnDisconnect?.Invoke(new RemoteInfo - { - Socket = tcpClient.Client, - IsTcp = true, - EndPoint = remoteEndpoint, - IsIPv6 = remoteEndpoint.AddressFamily == AddressFamily.InterNetworkV6 - }); + _clientNicknames[tcpClient] = nickname; + return true; + } public async Task SendToAsync(Socket socket, byte[] data) { - await socket.SendAsync(new ArraySegment(data), SocketFlags.None).ConfigureAwait(false); + await Task.Run(() => socket.SendAsync(new ArraySegment(data), SocketFlags.None)).ConfigureAwait(false); OnSend?.Invoke(new RemoteInfo { Socket = socket, diff --git a/EonaCat.Network/System/Sockets/Udp/SocketUdpServer.cs b/EonaCat.Network/System/Sockets/Udp/SocketUdpServer.cs index bd9d403..92f8ebc 100644 --- a/EonaCat.Network/System/Sockets/Udp/SocketUdpServer.cs +++ b/EonaCat.Network/System/Sockets/Udp/SocketUdpServer.cs @@ -2,148 +2,95 @@ using System.Net; using System.Net.NetworkInformation; using System.Net.Sockets; -using System.Runtime.InteropServices; using System.Threading; using System.Threading.Tasks; -namespace EonaCat.Network; - -public class SocketUdpServer +namespace EonaCat.Network { - private UdpClient udpClient; - - /// - /// Create UDP server - /// - /// - /// - public SocketUdpServer(IPAddress ipAddress, int port) + public class SocketUdpServer { - CreateUdpServer(ipAddress, port); - } + private readonly UdpClient _udpClient; - /// - /// Create UDP server - /// - /// - /// - /// - public SocketUdpServer(string ipAddress, int port) - { - if (!IPAddress.TryParse(ipAddress, out var ip)) + public bool IsIPv6 { get; private set; } + + public event Action OnReceive; + public event Action OnSend; + public event Action OnError; + + public SocketUdpServer(IPAddress ipAddress, int port) { - throw new Exception("EonaCat Network: Invalid ipAddress given"); + _udpClient = new UdpClient(ipAddress.AddressFamily); + IsIPv6 = ipAddress.AddressFamily == AddressFamily.InterNetworkV6; + _udpClient.Client.SetSocketOption(SocketOptionLevel.Socket, SocketOptionName.ReuseAddress, true); + _udpClient.Client.Bind(new IPEndPoint(ipAddress, port)); } - CreateUdpServer(ip, port); - } - - /// - /// Determines if the UDP server is IpV6 - /// - public bool IsIp6 { get; private set; } - - /// - /// OnReceive event - /// - public event Action OnReceive; - - /// - /// OnSend event - /// - public event Action OnSend; - - /// - /// OnError event - /// - public event Action OnError; - - /// - /// OnDisconnect event - /// - public event Action OnDisconnect; - - private static void SetConnectionReset(Socket socket) - { - if (socket.ProtocolType != ProtocolType.Udp && !RuntimeInformation.IsOSPlatform(OSPlatform.Windows)) + public async Task StartAsync(CancellationToken cancellationToken = default) { - return; + while (!cancellationToken.IsCancellationRequested) + { + try + { + var result = await _udpClient.ReceiveAsync().ConfigureAwait(false); + var remoteInfo = new RemoteInfo + { + EndPoint = result.RemoteEndPoint, + IsIPv6 = result.RemoteEndPoint.AddressFamily == AddressFamily.InterNetworkV6, + Data = result.Buffer + }; + + OnReceive?.Invoke(remoteInfo); + } + catch (SocketException ex) + { + OnError?.Invoke(ex, $"SocketException: {ex.Message}"); + } + catch (ObjectDisposedException ex) + { + OnError?.Invoke(ex, $"ObjectDisposedException: {ex.Message}"); + break; + } + catch (Exception ex) + { + OnError?.Invoke(ex, $"Exception: {ex.Message}"); + } + } } - // Disable ICMP packet shutdown (forcibly closed) - const int SioUdpConnReset = -1744830452; - - byte[] inValue = { 0, 0, 0, 0 }; - socket.IOControl(SioUdpConnReset, inValue, null); - } - - private void CreateUdpServer(IPAddress ipAddress, int port) - { - IsIp6 = ipAddress.AddressFamily == AddressFamily.InterNetworkV6; - udpClient = new UdpClient(ipAddress.AddressFamily); - SetConnectionReset(udpClient.Client); - - if (IsIp6) + public async Task SendToAsync(IPEndPoint endPoint, byte[] data) { - udpClient.Client.SetSocketOption(SocketOptionLevel.IPv6, SocketOptionName.IPv6Only, false); - } - - udpClient.Client.Bind(new IPEndPoint(ipAddress, port)); - } - - /// - /// Start UDP server - /// - /// - /// - public async Task StartAsync(CancellationToken cancellationToken = default) - { - while (!cancellationToken.IsCancellationRequested) try { - var result = await udpClient.ReceiveAsync().ConfigureAwait(false); - OnReceive?.Invoke(new RemoteInfo + await _udpClient.SendAsync(data, data.Length, endPoint).ConfigureAwait(false); + OnSend?.Invoke(new RemoteInfo { - EndPoint = result.RemoteEndPoint, - IsIPv6 = result.RemoteEndPoint.AddressFamily == AddressFamily.InterNetworkV6, - Data = result.Buffer + EndPoint = endPoint, + Data = data, + IsIPv6 = endPoint.AddressFamily == AddressFamily.InterNetworkV6 }); } catch (SocketException ex) { OnError?.Invoke(ex, $"SocketException: {ex.Message}"); } - } + catch (ObjectDisposedException ex) + { + OnError?.Invoke(ex, $"ObjectDisposedException: {ex.Message}"); + } + catch (Exception ex) + { + OnError?.Invoke(ex, $"Exception: {ex.Message}"); + } + } - /// - /// Send data to endPoint - /// - /// - /// - /// - public async Task SendToAsync(EndPoint endPoint, byte[] data) - { - if (endPoint is IPEndPoint ipEndPoint) + public async Task SendToAllAsync(byte[] data) { - await udpClient.SendAsync(data, data.Length, ipEndPoint).ConfigureAwait(false); - OnSend?.Invoke(new RemoteInfo - { EndPoint = endPoint, Data = data, IsIPv6 = endPoint.AddressFamily == AddressFamily.InterNetworkV6 }); + var ipProperties = IPGlobalProperties.GetIPGlobalProperties(); + var endPoints = ipProperties.GetActiveUdpListeners(); + foreach (var endPoint in endPoints) + { + await SendToAsync(endPoint, data).ConfigureAwait(false); + } } } - - /// - /// Send data to all clients - /// - /// - /// - public async Task SendToAllAsync(byte[] data) - { - // get all connected clients - var ipProperties = IPGlobalProperties.GetIPGlobalProperties(); - var endPoints = ipProperties.GetActiveUdpListeners(); - foreach (var endPoint in endPoints) - { - await udpClient.SendAsync(data, data.Length, endPoint).ConfigureAwait(false); - } - } -} \ No newline at end of file +}