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>
<PackageReleaseNotes></PackageReleaseNotes>
<Description>EonaCat Networking library with Quic, TCP, UDP, WebSockets and a Webserver</Description>
<Version>1.1.6</Version>
<AssemblyVersion>1.1.6</AssemblyVersion>
<FileVersion>1.1.6</FileVersion>
<Version>1.1.7</Version>
<AssemblyVersion>1.1.7</AssemblyVersion>
<FileVersion>1.1.7</FileVersion>
<PackageIcon>icon.png</PackageIcon>
<PackageReadmeFile>README.md</PackageReadmeFile>
<PackageLicenseFile>LICENSE</PackageLicenseFile>
</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' ">
<GenerateAssemblyInfo>false</GenerateAssemblyInfo>
<GenerateAssemblyInfo>True</GenerateAssemblyInfo>
<GenerateDocumentationFile>True</GenerateDocumentationFile>
<PackageReadmeFile>README.md</PackageReadmeFile>
<SignAssembly>False</SignAssembly>
@ -49,10 +60,14 @@
<ItemGroup>
<PackageReference Include="EonaCat.Controls" Version="1.0.2" />
<PackageReference Include="EonaCat.Json" Version="1.0.3" />
<PackageReference Include="EonaCat.Logger" Version="1.2.4" />
<PackageReference Include="EonaCat.Json" Version="1.0.5" />
<PackageReference Include="EonaCat.Logger" Version="1.2.8" />
<PackageReference Include="EonaCat.LogSystem" 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.Text.Encodings.Web" Version="8.0.0" />
</ItemGroup>

View File

@ -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; }

View File

@ -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<TcpClient, string> _clientNicknames = new ConcurrentDictionary<TcpClient, string>();
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<RemoteInfo> OnConnect;
public event Action<RemoteInfo> OnReceive;
public event Action<RemoteInfo, string> OnReceive;
public event Action<RemoteInfo> OnSend;
public event Action<RemoteInfo> OnDisconnect;
public event Action<Exception, string> 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<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
{
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<byte>(data), SocketFlags.None).ConfigureAwait(false);
await Task.Run(() => socket.SendAsync(new ArraySegment<byte>(data), SocketFlags.None)).ConfigureAwait(false);
OnSend?.Invoke(new RemoteInfo
{
Socket = socket,

View File

@ -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;
/// <summary>
/// Create UDP server
/// </summary>
/// <param name="ipAddress"></param>
/// <param name="port"></param>
public SocketUdpServer(IPAddress ipAddress, int port)
public class SocketUdpServer
{
CreateUdpServer(ipAddress, port);
}
private readonly UdpClient _udpClient;
/// <summary>
/// Create UDP server
/// </summary>
/// <param name="ipAddress"></param>
/// <param name="port"></param>
/// <exception cref="Exception"></exception>
public SocketUdpServer(string ipAddress, int port)
{
if (!IPAddress.TryParse(ipAddress, out var ip))
public bool IsIPv6 { get; private set; }
public event Action<RemoteInfo> OnReceive;
public event Action<RemoteInfo> OnSend;
public event Action<Exception, string> 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);
}
/// <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))
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));
}
/// <summary>
/// Start UDP server
/// </summary>
/// <param name="cancellationToken"></param>
/// <returns></returns>
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}");
}
}
/// <summary>
/// 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)
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);
}
}
}
/// <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);
}
}
}
}