This commit is contained in:
EonaCat 2024-05-05 17:04:53 +02:00
parent 681f8c725f
commit 6f1dd0db77
4 changed files with 138 additions and 140 deletions

View File

@ -13,16 +13,27 @@
<PackageTags>EonaCat, Network, .NET Standard, EonaCatHelpers, Jeroen, Saey, Protocol, Quic, UDP, TCP, Web, Server</PackageTags> <PackageTags>EonaCat, Network, .NET Standard, EonaCatHelpers, Jeroen, Saey, Protocol, Quic, UDP, TCP, Web, Server</PackageTags>
<PackageReleaseNotes></PackageReleaseNotes> <PackageReleaseNotes></PackageReleaseNotes>
<Description>EonaCat Networking library with Quic, TCP, UDP, WebSockets and a Webserver</Description> <Description>EonaCat Networking library with Quic, TCP, UDP, WebSockets and a Webserver</Description>
<Version>1.1.6</Version> <Version>1.1.7</Version>
<AssemblyVersion>1.1.6</AssemblyVersion> <AssemblyVersion>1.1.7</AssemblyVersion>
<FileVersion>1.1.6</FileVersion> <FileVersion>1.1.7</FileVersion>
<PackageIcon>icon.png</PackageIcon> <PackageIcon>icon.png</PackageIcon>
<PackageReadmeFile>README.md</PackageReadmeFile> <PackageReadmeFile>README.md</PackageReadmeFile>
<PackageLicenseFile>LICENSE</PackageLicenseFile> <PackageLicenseFile>LICENSE</PackageLicenseFile>
</PropertyGroup> </PropertyGroup>
<PropertyGroup>
<EVRevisionFormat>1.1.7+{chash:10}.{c:ymd}</EVRevisionFormat>
<EVDefault>true</EVDefault>
<EVInfo>true</EVInfo>
<EVTagMatch>v[0-9]*</EVTagMatch>
<EVRemoveTagV>true</EVRemoveTagV>
<EVVcs>git</EVVcs>
<EVCheckAllAttributes>true</EVCheckAllAttributes>
<EVShowRevision>true</EVShowRevision>
</PropertyGroup>
<PropertyGroup Condition=" '$(TargetFramework)' == 'netstandard2.1' "> <PropertyGroup Condition=" '$(TargetFramework)' == 'netstandard2.1' ">
<GenerateAssemblyInfo>false</GenerateAssemblyInfo> <GenerateAssemblyInfo>True</GenerateAssemblyInfo>
<GenerateDocumentationFile>True</GenerateDocumentationFile> <GenerateDocumentationFile>True</GenerateDocumentationFile>
<PackageReadmeFile>README.md</PackageReadmeFile> <PackageReadmeFile>README.md</PackageReadmeFile>
<SignAssembly>False</SignAssembly> <SignAssembly>False</SignAssembly>
@ -49,10 +60,14 @@
<ItemGroup> <ItemGroup>
<PackageReference Include="EonaCat.Controls" Version="1.0.2" /> <PackageReference Include="EonaCat.Controls" Version="1.0.2" />
<PackageReference Include="EonaCat.Json" Version="1.0.3" /> <PackageReference Include="EonaCat.Json" Version="1.0.5" />
<PackageReference Include="EonaCat.Logger" Version="1.2.4" /> <PackageReference Include="EonaCat.Logger" Version="1.2.8" />
<PackageReference Include="EonaCat.LogSystem" Version="1.0.0" /> <PackageReference Include="EonaCat.LogSystem" Version="1.0.0" />
<PackageReference Include="EonaCat.Matchers" Version="1.0.0" /> <PackageReference Include="EonaCat.Matchers" Version="1.0.0" />
<PackageReference Include="EonaCat.Versioning" Version="1.0.5">
<PrivateAssets>all</PrivateAssets>
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
</PackageReference>
<PackageReference Include="System.Net.WebSockets" Version="4.3.0" /> <PackageReference Include="System.Net.WebSockets" Version="4.3.0" />
<PackageReference Include="System.Text.Encodings.Web" Version="8.0.0" /> <PackageReference Include="System.Text.Encodings.Web" Version="8.0.0" />
</ItemGroup> </ItemGroup>

View File

@ -14,6 +14,7 @@ public class RemoteInfo
public IPAddress Address => HasEndpoint ? ((IPEndPoint)EndPoint).Address : null; public IPAddress Address => HasEndpoint ? ((IPEndPoint)EndPoint).Address : null;
public int Port => HasEndpoint ? ((IPEndPoint)EndPoint).Port : 0; public int Port => HasEndpoint ? ((IPEndPoint)EndPoint).Port : 0;
public Socket Socket { get; set; } public Socket Socket { get; set; }
public string NickName { get; set; }
public bool IsIPv6 { get; set; } public bool IsIPv6 { get; set; }
public bool IsWebSocket { get; internal set; } public bool IsWebSocket { get; internal set; }
public string ClientId { get; internal set; } public string ClientId { get; internal set; }

View File

@ -1,4 +1,5 @@
using System; using System;
using System.Collections.Concurrent;
using System.IO; using System.IO;
using System.Net; using System.Net;
using System.Net.Security; using System.Net.Security;
@ -20,6 +21,8 @@ namespace EonaCat.Network
private Task _acceptTask; private Task _acceptTask;
private CancellationTokenSource _cancellationTokenSource; private CancellationTokenSource _cancellationTokenSource;
private readonly ConcurrentDictionary<TcpClient, string> _clientNicknames = new ConcurrentDictionary<TcpClient, string>();
public SocketTcpServer(IPAddress ipAddress, int port, X509Certificate2 certificate = null, SslOptions sslOptions = null) public SocketTcpServer(IPAddress ipAddress, int port, X509Certificate2 certificate = null, SslOptions sslOptions = null)
{ {
_listener = new TcpListener(ipAddress, port); _listener = new TcpListener(ipAddress, port);
@ -36,7 +39,7 @@ namespace EonaCat.Network
} }
public event Action<RemoteInfo> OnConnect; public event Action<RemoteInfo> OnConnect;
public event Action<RemoteInfo> OnReceive; public event Action<RemoteInfo, string> OnReceive;
public event Action<RemoteInfo> OnSend; public event Action<RemoteInfo> OnSend;
public event Action<RemoteInfo> OnDisconnect; public event Action<RemoteInfo> OnDisconnect;
public event Action<Exception, string> OnError; public event Action<Exception, string> OnError;
@ -45,8 +48,8 @@ namespace EonaCat.Network
{ {
_listener.Start(); _listener.Start();
_cancellationTokenSource = CancellationTokenSource.CreateLinkedTokenSource(cancellationToken); _cancellationTokenSource = CancellationTokenSource.CreateLinkedTokenSource(cancellationToken);
_acceptTask = AcceptConnectionsAsync(_cancellationTokenSource.Token); _acceptTask = Task.Run(() => AcceptConnectionsAsync(_cancellationTokenSource.Token));
return _acceptTask; return Task.CompletedTask;
} }
private async Task AcceptConnectionsAsync(CancellationToken cancellationToken) private async Task AcceptConnectionsAsync(CancellationToken cancellationToken)
@ -56,7 +59,7 @@ namespace EonaCat.Network
try try
{ {
var tcpClient = await _listener.AcceptTcpClientAsync().ConfigureAwait(false); var tcpClient = await _listener.AcceptTcpClientAsync().ConfigureAwait(false);
_ = HandleConnectionAsync(tcpClient, cancellationToken); _ = Task.Run(() => HandleConnectionAsync(tcpClient, cancellationToken));
} }
catch (SocketException ex) catch (SocketException ex)
{ {
@ -70,12 +73,16 @@ namespace EonaCat.Network
var remoteEndpoint = tcpClient.Client.RemoteEndPoint; var remoteEndpoint = tcpClient.Client.RemoteEndPoint;
using (tcpClient) using (tcpClient)
{ {
var nickName = await GetNicknameAsync(tcpClient).ConfigureAwait(false);
_clientNicknames.TryAdd(tcpClient, nickName);
OnConnect?.Invoke(new RemoteInfo OnConnect?.Invoke(new RemoteInfo
{ {
Socket = tcpClient.Client, Socket = tcpClient.Client,
IsTcp = true, IsTcp = true,
IsIPv6 = remoteEndpoint.AddressFamily == AddressFamily.InterNetworkV6, IsIPv6 = remoteEndpoint.AddressFamily == AddressFamily.InterNetworkV6,
EndPoint = remoteEndpoint EndPoint = remoteEndpoint,
NickName = nickName
}); });
Stream stream = tcpClient.GetStream(); Stream stream = tcpClient.GetStream();
@ -84,7 +91,7 @@ namespace EonaCat.Network
var sslStream = new SslStream(stream, false, ValidateRemoteCertificate, null); var sslStream = new SslStream(stream, false, ValidateRemoteCertificate, null);
try 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; stream = sslStream;
} }
catch (AuthenticationException ex) catch (AuthenticationException ex)
@ -110,8 +117,9 @@ namespace EonaCat.Network
IsTcp = true, IsTcp = true,
IsIPv6 = remoteEndpoint.AddressFamily == AddressFamily.InterNetworkV6, IsIPv6 = remoteEndpoint.AddressFamily == AddressFamily.InterNetworkV6,
EndPoint = remoteEndpoint, EndPoint = remoteEndpoint,
NickName = nickName,
Data = data Data = data
}); }, nickName);
} }
else else
{ {
@ -124,20 +132,47 @@ namespace EonaCat.Network
break; 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<string> 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 _clientNicknames[tcpClient] = nickname;
{ return true;
Socket = tcpClient.Client,
IsTcp = true,
EndPoint = remoteEndpoint,
IsIPv6 = remoteEndpoint.AddressFamily == AddressFamily.InterNetworkV6
});
} }
public async Task SendToAsync(Socket socket, byte[] data) public async Task SendToAsync(Socket socket, byte[] data)
{ {
await socket.SendAsync(new ArraySegment<byte>(data), SocketFlags.None).ConfigureAwait(false); await Task.Run(() => socket.SendAsync(new ArraySegment<byte>(data), SocketFlags.None)).ConfigureAwait(false);
OnSend?.Invoke(new RemoteInfo OnSend?.Invoke(new RemoteInfo
{ {
Socket = socket, Socket = socket,

View File

@ -2,148 +2,95 @@
using System.Net; using System.Net;
using System.Net.NetworkInformation; using System.Net.NetworkInformation;
using System.Net.Sockets; using System.Net.Sockets;
using System.Runtime.InteropServices;
using System.Threading; using System.Threading;
using System.Threading.Tasks; using System.Threading.Tasks;
namespace EonaCat.Network; namespace EonaCat.Network
public class SocketUdpServer
{ {
private UdpClient udpClient; public class SocketUdpServer
/// <summary>
/// Create UDP server
/// </summary>
/// <param name="ipAddress"></param>
/// <param name="port"></param>
public SocketUdpServer(IPAddress ipAddress, int port)
{ {
CreateUdpServer(ipAddress, port); private readonly UdpClient _udpClient;
}
/// <summary> public bool IsIPv6 { get; private set; }
/// Create UDP server
/// </summary> public event Action<RemoteInfo> OnReceive;
/// <param name="ipAddress"></param> public event Action<RemoteInfo> OnSend;
/// <param name="port"></param> public event Action<Exception, string> OnError;
/// <exception cref="Exception"></exception>
public SocketUdpServer(string ipAddress, int port) public SocketUdpServer(IPAddress ipAddress, int port)
{
if (!IPAddress.TryParse(ipAddress, out var ip))
{ {
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); public async Task StartAsync(CancellationToken cancellationToken = default)
}
/// <summary>
/// Determines if the UDP server is IpV6
/// </summary>
public bool IsIp6 { get; private set; }
/// <summary>
/// OnReceive event
/// </summary>
public event Action<RemoteInfo> OnReceive;
/// <summary>
/// OnSend event
/// </summary>
public event Action<RemoteInfo> OnSend;
/// <summary>
/// OnError event
/// </summary>
public event Action<Exception, string> OnError;
/// <summary>
/// OnDisconnect event
/// </summary>
public event Action<RemoteInfo> OnDisconnect;
private static void SetConnectionReset(Socket socket)
{
if (socket.ProtocolType != ProtocolType.Udp && !RuntimeInformation.IsOSPlatform(OSPlatform.Windows))
{ {
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) public async Task SendToAsync(IPEndPoint endPoint, byte[] data)
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)
{ {
udpClient.Client.SetSocketOption(SocketOptionLevel.IPv6, SocketOptionName.IPv6Only, false);
}
udpClient.Client.Bind(new IPEndPoint(ipAddress, port));
}
/// <summary>
/// Start UDP server
/// </summary>
/// <param name="cancellationToken"></param>
/// <returns></returns>
public async Task StartAsync(CancellationToken cancellationToken = default)
{
while (!cancellationToken.IsCancellationRequested)
try try
{ {
var result = await udpClient.ReceiveAsync().ConfigureAwait(false); await _udpClient.SendAsync(data, data.Length, endPoint).ConfigureAwait(false);
OnReceive?.Invoke(new RemoteInfo OnSend?.Invoke(new RemoteInfo
{ {
EndPoint = result.RemoteEndPoint, EndPoint = endPoint,
IsIPv6 = result.RemoteEndPoint.AddressFamily == AddressFamily.InterNetworkV6, Data = data,
Data = result.Buffer IsIPv6 = endPoint.AddressFamily == AddressFamily.InterNetworkV6
}); });
} }
catch (SocketException ex) catch (SocketException ex)
{ {
OnError?.Invoke(ex, $"SocketException: {ex.Message}"); OnError?.Invoke(ex, $"SocketException: {ex.Message}");
} }
} catch (ObjectDisposedException ex)
{
OnError?.Invoke(ex, $"ObjectDisposedException: {ex.Message}");
}
catch (Exception ex)
{
OnError?.Invoke(ex, $"Exception: {ex.Message}");
}
}
/// <summary> public async Task SendToAllAsync(byte[] data)
/// Send data to endPoint
/// </summary>
/// <param name="endPoint"></param>
/// <param name="data"></param>
/// <returns></returns>
public async Task SendToAsync(EndPoint endPoint, byte[] data)
{
if (endPoint is IPEndPoint ipEndPoint)
{ {
await udpClient.SendAsync(data, data.Length, ipEndPoint).ConfigureAwait(false); var ipProperties = IPGlobalProperties.GetIPGlobalProperties();
OnSend?.Invoke(new RemoteInfo var endPoints = ipProperties.GetActiveUdpListeners();
{ EndPoint = endPoint, Data = data, IsIPv6 = endPoint.AddressFamily == AddressFamily.InterNetworkV6 }); foreach (var endPoint in endPoints)
{
await SendToAsync(endPoint, data).ConfigureAwait(false);
}
} }
} }
}
/// <summary>
/// Send data to all clients
/// </summary>
/// <param name="data"></param>
/// <returns></returns>
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);
}
}
}