diff --git a/EonaCat.Network/EonaCat.Network.csproj b/EonaCat.Network/EonaCat.Network.csproj index 1aae10a..60c089c 100644 --- a/EonaCat.Network/EonaCat.Network.csproj +++ b/EonaCat.Network/EonaCat.Network.csproj @@ -18,9 +18,9 @@ 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.1 - 1.0.0.1 - 1.0.0.1 + 1.0.2 + 1.0.0.2 + 1.0.0.2 icon.png diff --git a/EonaCat.Network/System/Sockets/SocketServer.cs b/EonaCat.Network/System/Sockets/SocketServer.cs index 726ff7b..796d5dd 100644 --- a/EonaCat.Network/System/Sockets/SocketServer.cs +++ b/EonaCat.Network/System/Sockets/SocketServer.cs @@ -4,13 +4,11 @@ using System.IO; using System.Linq; using System.Net; using System.Net.Sockets; +using System.Runtime.InteropServices; using System.Threading; namespace EonaCat.Sockets { - /// - /// - /// public class SocketServer : IDisposable { /// @@ -38,6 +36,19 @@ namespace EonaCat.Sockets /// public int DisconnectionCheckInterval { get; set; } = 100; + /// + /// Determines if we need to reuse the socket address + /// (default: false) + /// + public bool ReuseAddress { get; set; } + + /// + /// Determines if the socket is in blocking mode + /// (default: true) + /// + public bool IsSocketInBlockingMode { get; set; } = true; + + /// /// Socket backlog. The maximum length of the pending connections queue. /// @@ -48,12 +59,14 @@ namespace EonaCat.Sockets public NetworkType ServerType { get; set; } /// - /// Only for UDP. Require to use SocketClient class for this. + /// Only for UDP. + /// Required to use SocketClient class for this. /// public bool UDPClientManage { get; set; } = true; /// - /// Only for UDP. Accept 1 byte data for check connected state. + /// Only for UDP. + /// Accept 1 byte data for connected state check. /// private int _UDPDataInterval { get => (int)(UDPDataInterval * 1.5); } public int UDPDataInterval { get; set; } = 5000; @@ -77,7 +90,7 @@ namespace EonaCat.Sockets /// /// Called when executing Start() function /// - public event EventHandler OnStart; + public event EventHandler OnStart; /// /// Called when executing Stop() function /// @@ -85,7 +98,7 @@ namespace EonaCat.Sockets private Socket Listener { get; set; } private System.Timers.Timer DisconnectTimer { get; set; } - private Dictionary LastDataRecievedTime { get; set; } + private Dictionary LastDataReceivedTime { get; set; } private double TimeNow { get => (DateTime.UtcNow - new DateTime(1970, 1, 1)).TotalSeconds; } public SocketServer(NetworkType type, string address, int port) @@ -128,6 +141,14 @@ namespace EonaCat.Sockets { case NetworkType.Tcp: Listener = new Socket(IPAddress.AddressFamily, SocketType.Stream, ProtocolType.Tcp); + ConfigureSocketOptions(); + + // Check if we need to override the socket with a custom socket + if (CustomTcpSocket != null) + { + Listener = CustomTcpSocket; + } + DisconnectTimer.Elapsed += (s, e) => { lock (ConnectedClients) @@ -146,44 +167,52 @@ namespace EonaCat.Sockets }; break; case NetworkType.Udp: - { - if (UDPClientManage) { - LastDataRecievedTime = new Dictionary(); - DisconnectTimer.Elapsed += (s, e) => + if (UDPClientManage) { - var now = TimeNow; - var times = LastDataRecievedTime; - var removed = new List(); - foreach (var kp in times) + LastDataReceivedTime = new Dictionary(); + DisconnectTimer.Elapsed += (s, e) => { - if (now - kp.Value > _UDPDataInterval / 1000) + var now = TimeNow; + var times = LastDataReceivedTime; + var removed = new List(); + foreach (var kp in times) { - lock (ConnectedClients) + if (now - kp.Value > _UDPDataInterval / 1000) { - var client = ConnectedClients.Where(x => x.Guid == kp.Key.ToString()); - if (!client.Any()) + lock (ConnectedClients) { - continue; + var client = ConnectedClients.Where(x => x.Guid == kp.Key.ToString()); + if (!client.Any()) + { + continue; + } + OnDisconnected?.Invoke(this, new SocketServerClientEventArgs() { Client = client.First() }); + ConnectedClients.Remove(client.First()); + removed.Add(kp.Key); } - OnDisconnected?.Invoke(this, new SocketServerClientEventArgs() { Client = client.First() }); - ConnectedClients.Remove(client.First()); - removed.Add(kp.Key); } } - } - lock (LastDataRecievedTime) - { - foreach (var r in removed) + lock (LastDataReceivedTime) { - LastDataRecievedTime.Remove(r); + foreach (var r in removed) + { + LastDataReceivedTime.Remove(r); + } } - } - }; + }; + } + Listener = new Socket(IPAddress.AddressFamily, SocketType.Dgram, ProtocolType.Udp); + ConfigureSocketOptions(); + + // Check if we need to override the socket with a custom socket + if (CustomUdpSocket != null) + { + Listener = CustomUdpSocket; + } + + break; } - Listener = new Socket(IPAddress.AddressFamily, SocketType.Dgram, ProtocolType.Udp); - break; - } } Listener.ReceiveBufferSize = ClientConnection.BufferSize; Listener.SendBufferSize = ClientConnection.BufferSize; @@ -201,6 +230,47 @@ namespace EonaCat.Sockets } } + /// + /// Setup this udp socket to override the default one + /// (default: null => using default) + /// + public Socket CustomUdpSocket { get; set; } + + /// + /// Setup this tcp socket to override the default one + /// (default: null => using default) + /// + public Socket CustomTcpSocket { get; set; } + + static void SetConnectionReset(Socket socket) + { + try + { + if (socket.ProtocolType == ProtocolType.Udp && RuntimeInformation.IsOSPlatform(OSPlatform.Windows)) + { + // Disable ICMP packet shutdown (forcibly closed) + const uint IOC_IN = 0x80000000; + const uint IOC_VENDOR = 0x18000000; + uint SIO_UDP_CONNRESET = IOC_IN | IOC_VENDOR | 12; + socket.IOControl((int)SIO_UDP_CONNRESET, new[] { Convert.ToByte(false) }, null); + }; + } + catch { } + } + + private void ConfigureSocketOptions() + { + // Disable ICMP packet shutdown + SetConnectionReset(Listener); + + if (ReuseAddress) + { + Listener.SetSocketOption(SocketOptionLevel.Socket, SocketOptionName.ReuseAddress, true); + } + + Listener.Blocking = IsSocketInBlockingMode; + } + /// /// Stop server /// @@ -209,18 +279,18 @@ namespace EonaCat.Sockets try { DisconnectTimer.Stop(); - foreach (var c in ConnectedClients) + foreach (var client in ConnectedClients) { - OnDisconnected?.Invoke(this, new SocketServerClientEventArgs() { Client = c }); - c.Socket.Close(); + OnDisconnected?.Invoke(this, new SocketServerClientEventArgs() { Client = client }); + client.Socket.Close(); } ConnectedClients.Clear(); OnStop?.Invoke(this, EventArgs.Empty); Listener.Close(); } - catch (SocketException se) + catch (SocketException socketException) { - OnError?.Invoke(this, new ErrorEventArgs(se)); + OnError?.Invoke(this, new ErrorEventArgs(socketException)); } } @@ -228,7 +298,7 @@ namespace EonaCat.Sockets { try { - OnStart?.Invoke(this, EventArgs.Empty); + OnStart?.Invoke(this, CustomTcpSocket != null || CustomUdpSocket != null ? "Custom socket was used" : string.Empty); if (ServerType == NetworkType.Tcp) { Listener.BeginAccept(AcceptCallback, Listener); @@ -317,15 +387,7 @@ namespace EonaCat.Sockets var buffer = objects[0] as byte[]; var remoteIp = objects[1] as EndPoint; var listener = objects[2] as Socket; - var bytesRead = 0; - try - { - bytesRead = listener.EndReceiveFrom(ar, ref remoteIp); - } - catch (SocketException se) - { - // ignore - } + var bytesRead = listener.EndReceiveFrom(ar, ref remoteIp); var clients = ConnectedClients.Where(x => x.Guid == remoteIp.ToString()).ToList(); ClientConnection client; if (clients.Count == 0) @@ -355,13 +417,13 @@ namespace EonaCat.Sockets } else { - if (LastDataRecievedTime.ContainsKey(remoteIp)) + if (LastDataReceivedTime.ContainsKey(remoteIp)) { - LastDataRecievedTime[remoteIp] = TimeNow; + LastDataReceivedTime[remoteIp] = TimeNow; } else { - LastDataRecievedTime.Add(remoteIp, TimeNow); + LastDataReceivedTime.Add(remoteIp, TimeNow); } } Listener.BeginReceiveFrom(buffer, 0, ClientConnection.BufferSize, 0, ref remoteIp, AcceptCallbackUDP, new object[] { buffer, remoteIp, Listener });