From: Forest Date: Fri, 26 Apr 2019 19:26:53 +0000 (-0700) Subject: Re-remove TCP (this time for good), add a counter to track potentionally fragmented... X-Git-Tag: 1.0.0~46 X-Git-Url: https://git.deb.at/?a=commitdiff_plain;h=020071a7bcaca63c8d09585590d212dead8e5fa1;p=rhonda%2Fimpostor.hazel.git Re-remove TCP (this time for good), add a counter to track potentionally fragmented (by MTU) packets, catch a small crash-causer caused by disposing client connections --- diff --git a/Hazel.UnitTests/UdpConnectionTests.cs b/Hazel.UnitTests/UdpConnectionTests.cs index c30696f..17515f4 100644 --- a/Hazel.UnitTests/UdpConnectionTests.cs +++ b/Hazel.UnitTests/UdpConnectionTests.cs @@ -2,9 +2,9 @@ using Microsoft.VisualStudio.TestTools.UnitTesting; using System.Net; using System.Threading; - using Hazel.Udp; using System.Linq; +using System.Collections.Generic; namespace Hazel.UnitTests { @@ -95,7 +95,7 @@ namespace Hazel.UnitTests } } } - + /// /// Tests IPv4 connectivity. /// diff --git a/Hazel/ConnectionStatistics.cs b/Hazel/ConnectionStatistics.cs index dc44c08..7e11c20 100644 --- a/Hazel/ConnectionStatistics.cs +++ b/Hazel/ConnectionStatistics.cs @@ -24,6 +24,27 @@ namespace Hazel } } + /// + /// The number of messages sent larger than 1400 bytes. This is smaller than most default MTUs. + /// + /// + /// This is the number of unreliable messages that were sent from the , incremented + /// each time that LogUnreliableSend is called by the Connection. Messages that caused an error are not + /// counted and messages are only counted once all other operations in the send are complete. + /// + public int FragmentableMessagesSent + { + get + { + return fragmentableMessagesSent; + } + } + + /// + /// The number of messages sent larger than 1400 bytes. + /// + int fragmentableMessagesSent; + /// /// The number of unreliable messages sent. /// @@ -358,6 +379,11 @@ namespace Hazel Interlocked.Increment(ref unreliableMessagesSent); Interlocked.Add(ref dataBytesSent, dataLength); Interlocked.Add(ref totalBytesSent, totalLength); + + if (totalLength > 1400) + { + Interlocked.Increment(ref fragmentableMessagesSent); + } } /// @@ -373,6 +399,11 @@ namespace Hazel Interlocked.Increment(ref reliableMessagesSent); Interlocked.Add(ref dataBytesSent, dataLength); Interlocked.Add(ref totalBytesSent, totalLength); + + if (totalLength > 1400) + { + Interlocked.Increment(ref fragmentableMessagesSent); + } } /// @@ -388,6 +419,11 @@ namespace Hazel Interlocked.Increment(ref fragmentedMessagesSent); Interlocked.Add(ref dataBytesSent, dataLength); Interlocked.Add(ref totalBytesSent, totalLength); + + if (totalLength > 1400) + { + Interlocked.Increment(ref fragmentableMessagesSent); + } } /// diff --git a/Hazel/Hazel.csproj b/Hazel/Hazel.csproj index 23cd33e..6466a40 100644 --- a/Hazel/Hazel.csproj +++ b/Hazel/Hazel.csproj @@ -80,9 +80,6 @@ - - - diff --git a/Hazel/MessageWriter.cs b/Hazel/MessageWriter.cs index 54f0dd1..37d9c94 100644 --- a/Hazel/MessageWriter.cs +++ b/Hazel/MessageWriter.cs @@ -55,12 +55,6 @@ namespace Hazel System.Buffer.BlockCopy(this.Buffer, 1, output, 0, this.Length - 1); return output; } - case SendOption.Tcp: - { - byte[] output = new byte[this.Length]; - System.Buffer.BlockCopy(this.Buffer, 0, output, 0, this.Length); - return output; - } } } @@ -124,9 +118,6 @@ namespace Hazel case SendOption.Reliable: this.Length = this.Position = 3; break; - case SendOption.Tcp: - this.Length = this.Position = 0; - break; } } diff --git a/Hazel/NetworkConnection.cs b/Hazel/NetworkConnection.cs index 8e7bd7e..0a224a8 100644 --- a/Hazel/NetworkConnection.cs +++ b/Hazel/NetworkConnection.cs @@ -22,11 +22,6 @@ namespace Hazel /// public EndPoint RemoteEndPoint { get; protected set; } - /// - /// The IPMode the client is connected using. - /// - public IPMode IPMode { get; protected set; } - public long GetIP4Address() { if (IPMode == IPMode.IPv4) diff --git a/Hazel/SendOption.cs b/Hazel/SendOption.cs index 23d92cc..c2ffb22 100644 --- a/Hazel/SendOption.cs +++ b/Hazel/SendOption.cs @@ -31,7 +31,5 @@ namespace Hazel /// a larger number of protocol bytes and can be slower than unreliable delivery. /// Reliable = 1, - - Tcp = 2, } } diff --git a/Hazel/Tcp/StateObject.cs b/Hazel/Tcp/StateObject.cs deleted file mode 100644 index bee3d66..0000000 --- a/Hazel/Tcp/StateObject.cs +++ /dev/null @@ -1,43 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; - -namespace Hazel.Tcp -{ - /// - /// Represents the state of the current receive operation for TCP connections. - /// - struct StateObject - { - /// - /// The buffer we're receiving. - /// - internal MessageReader message; - - /// - /// The total number of bytes received so far. - /// - internal int totalBytesReceived; - - /// - /// The callback to invoke once the buffer has been filled. - /// - internal Action callback; - - internal readonly int ExpectedSize; - - /// - /// Creates a StateObject with the specified length. - /// - /// The number of bytes expected to be received. - /// The callback to invoke once data has been received. - internal StateObject(int length, Action callback) - { - this.message = MessageReader.GetSized(ushort.MaxValue); - this.totalBytesReceived = 0; - this.callback = callback; - this.ExpectedSize = length; - } - } -} \ No newline at end of file diff --git a/Hazel/Tcp/TcpConnection.cs b/Hazel/Tcp/TcpConnection.cs deleted file mode 100644 index 8103885..0000000 --- a/Hazel/Tcp/TcpConnection.cs +++ /dev/null @@ -1,355 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Net; -using System.Net.Sockets; -using System.Text; -using System.Threading; - -namespace Hazel.Tcp -{ - /// - /// Represents a connection that uses the TCP protocol. - /// - /// - public sealed class TcpConnection : NetworkConnection - { - /// - /// The socket we're managing. - /// - Socket socket; - - /// - /// Creates a TcpConnection from a given TCP Socket. - /// - /// The TCP socket to wrap. - internal TcpConnection(Socket socket) - { - //Check it's a TCP socket - if (socket.ProtocolType != ProtocolType.Tcp) - throw new ArgumentException("A TcpConnection requires a TCP socket."); - - this.EndPoint = (IPEndPoint)socket.RemoteEndPoint; - this.RemoteEndPoint = socket.RemoteEndPoint; - - this.socket = socket; - this.socket.NoDelay = true; - - State = ConnectionState.Connected; - } - - /// - /// Creates a new TCP connection. - /// - /// A to connect to. - public TcpConnection(IPEndPoint remoteEndPoint, IPMode ipMode = IPMode.IPv4) - { - this.EndPoint = remoteEndPoint; - this.RemoteEndPoint = remoteEndPoint; - this.IPMode = ipMode; - - //Create a socket - if (ipMode == IPMode.IPv4) - socket = new Socket(AddressFamily.InterNetwork, SocketType.Stream, System.Net.Sockets.ProtocolType.Tcp); - else - { - if (!Socket.OSSupportsIPv6) - throw new InvalidOperationException("IPV6 not supported!"); - - socket = new Socket(AddressFamily.InterNetworkV6, SocketType.Stream, System.Net.Sockets.ProtocolType.Tcp); - socket.SetSocketOption(SocketOptionLevel.IPv6, (SocketOptionName)27, false); - } - - socket.NoDelay = true; - } - - /// - public override void Connect(byte[] bytes = null, int timeout = 5000) - { - //Connect - State = ConnectionState.Connecting; - - try - { - IAsyncResult result = socket.BeginConnect(RemoteEndPoint, null, null); - - result.AsyncWaitHandle.WaitOne(timeout); - - socket.EndConnect(result); - } - catch (Exception e) - { - throw new HazelException("Could not connect as an exception occured.", e); - } - - //Start receiving data - try - { - var msg = MessageReader.GetSized(ushort.MaxValue); - - ListenForData(msg, InvokeAndListen); - } - catch (Exception e) - { - throw new HazelException("An exception occured while initiating the first receive operation.", e); - } - - //Set connected - State = ConnectionState.Connected; - - //Send handshake - byte[] actualBytes; - if (bytes == null) - { - actualBytes = new byte[1]; - } - else - { - actualBytes = new byte[bytes.Length + 1]; - Buffer.BlockCopy(bytes, 0, actualBytes, 1, bytes.Length); - } - - SendBytes(actualBytes); - } - - public override void ConnectAsync(byte[] bytes = null, int timeout = 5000) - { - throw new NotImplementedException("I don't need this, so I didn't make it."); - } - - public override void Send(MessageWriter msg) - { - if (msg.SendOption != SendOption.Tcp) throw new InvalidOperationException("Sorry, no can do, holmes."); - - if (State != ConnectionState.Connected) - throw new InvalidOperationException("Could not send data as this Connection is not connected. Did you disconnect?"); - - var fullBytes = PrependLengthHeader(msg.Buffer, msg.Length); - - try - { - socket.BeginSend(fullBytes, 0, fullBytes.Length, SocketFlags.None, FinishSend, null); - } - catch (Exception e) - { - Disconnect("Could not send data as an occured: " + e.Message); - } - - Statistics.LogFragmentedSend(msg.Length, fullBytes.Length); - } - - /// - /// - /// - /// - /// The sendOption parameter is ignored by the TcpConnection as TCP only supports FragmentedReliable - /// communication, specifying anything else will have no effect. - /// - /// - public override void SendBytes(byte[] bytes, SendOption sendOption = SendOption.Tcp) - { - if (State != ConnectionState.Connected) - throw new InvalidOperationException("Could not send data as this Connection is not connected. Did you disconnect?"); - - var fullBytes = PrependLengthHeader(bytes); - - try - { - socket.BeginSend(fullBytes, 0, fullBytes.Length, SocketFlags.None, FinishSend, null); - } - catch (Exception e) - { - Disconnect("Could not send data as an occured: " + e.Message); - } - - Statistics.LogFragmentedSend(bytes.Length, fullBytes.Length); - } - - private void FinishSend(IAsyncResult ar) - { - try - { - this.socket.EndSend(ar); - } - catch { } - } - - /// - /// Starts waiting for a first handshake packet to be received. - /// - /// The callback to invoke when the handshake has been received. - internal void StartWaitingForHandshake(Action callback) - { - this.State = ConnectionState.Connected; - - var buffer = MessageReader.GetSized(ushort.MaxValue); - try - { - buffer.Offset = 0; - buffer.Length = 4; - buffer.Position = 0; - - ListenForData( - buffer, - m => ReadHeader(m, - delegate (MessageReader msg) - { - ListenForData(); - - //Remove version byte - msg.Offset = 1; - msg.Length -= 1; - msg.Position = 0; - - callback.Invoke(msg); - }) - ); - } - catch (Exception e) - { - buffer.Recycle(); - Disconnect("An exception occured while initiating the first receive operation: " + e.Message); - } - } - - private void InvokeAndListen(MessageReader msg) - { - this.ListenForData(); - - try - { - this.InvokeDataReceived(msg, SendOption.Tcp); - } - catch { } - } - - private void ListenForData() - { - var msg = MessageReader.GetSized(ushort.MaxValue); - msg.Offset = 0; - msg.Length = 4; - msg.Position = 0; - - ListenForData(msg, m => ReadHeader(m, null)); - } - - private void ReadHeader(MessageReader msg, Action callback) - { - msg.Length = GetLengthFromBytes(msg.Buffer); - msg.Position = 0; - - ListenForData(msg, callback ?? InvokeAndListen); - } - - private void ListenForData(MessageReader msg, Action callback) - { - if (State == ConnectionState.Disconnecting || State == ConnectionState.NotConnected) - throw new HazelException("Not connected"); - - try - { - socket.BeginReceive(msg.Buffer, msg.Position, msg.Length, SocketFlags.None, o => ReadUntilFull(callback, o), msg); - } - catch (SocketException s) - { - msg.Recycle(); - Disconnect("SocketException while reading header: " + s.Message); - } - } - - private void ReadUntilFull(Action callback, IAsyncResult result) - { - int bytesRead; - try - { - bytesRead = socket.EndReceive(result); - if (bytesRead == 0) - { - Disconnect("Received 0 bytes"); - return; - } - } - catch (ObjectDisposedException) { return; } - catch (SocketException s) - { - Disconnect("SocketException while reading body: " + s.Message); - return; - } - - var msg = (MessageReader)result.AsyncState; - msg.Position += bytesRead; - - Statistics.LogFragmentedReceive(bytesRead, 0); - - if (msg.Position < msg.Length) - { - ListenForData(msg, callback); - } - else - { - try - { - msg.Position = 0; - callback(msg); - } - catch { } - } - } - - protected override void SendDisconnect() - { - // Just dispose the connection, it's inherent to TCP. - } - - /// - /// Appends the length header to the bytes. - /// - /// The source bytes. - /// The new bytes. - private static byte[] PrependLengthHeader(byte[] bytes, int length = -1) - { - length = length > -1 ? length : bytes.Length; - - byte[] fullBytes = new byte[length + 4]; - Buffer.BlockCopy(bytes, 0, fullBytes, 4, length); - - fullBytes[0] = (byte)(length >> 24); - fullBytes[1] = (byte)(length >> 16); - fullBytes[2] = (byte)(length >> 8); - fullBytes[3] = (byte)length; - - return fullBytes; - } - - /// - /// Returns the length from a length header. - /// - /// The bytes received. - /// The number of bytes. - static int GetLengthFromBytes(byte[] bytes) - { - if (bytes.Length < 4) - throw new IndexOutOfRangeException("Not enough bytes passed to calculate length."); - - return (bytes[0] << 24) | (bytes[1] << 16) | (bytes[2] << 8) | bytes[3]; - } - - /// - protected override void Dispose(bool disposing) - { - if (disposing) - { - lock (this) - { - State = ConnectionState.NotConnected; - - try { this.socket.Shutdown(SocketShutdown.Both); } catch { } - try { this.socket.Close(); } catch { } - try { this.socket.Dispose(); } catch { } - } - } - - base.Dispose(disposing); - } - } -} \ No newline at end of file diff --git a/Hazel/Tcp/TcpConnectionListener.cs b/Hazel/Tcp/TcpConnectionListener.cs deleted file mode 100644 index 4b9fcd2..0000000 --- a/Hazel/Tcp/TcpConnectionListener.cs +++ /dev/null @@ -1,92 +0,0 @@ -using System; -using System.Net; -using System.Net.Sockets; - -namespace Hazel.Tcp -{ - public sealed class TcpConnectionListener : NetworkConnectionListener - { - private Socket listener; - - /// - /// Creates a new TcpConnectionListener for the given , port and . - /// - /// The end point to listen on. - public TcpConnectionListener(IPEndPoint endPoint, IPMode ipMode = IPMode.IPv4) - { - this.EndPoint = endPoint; - this.IPMode = ipMode; - - if (this.IPMode == IPMode.IPv4) - this.listener = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp); - else - { - if (!Socket.OSSupportsIPv6) - throw new InvalidOperationException("IPV6 not supported!"); - - this.listener = new Socket(AddressFamily.InterNetworkV6, SocketType.Stream, ProtocolType.Tcp); - this.listener.SetSocketOption(SocketOptionLevel.IPv6, (SocketOptionName)27, false); - } - } - - /// - public override void Start() - { - try - { - listener.Bind(EndPoint); - listener.Listen(1000); - - listener.BeginAccept(AcceptConnection, null); - } - catch (SocketException e) - { - throw new HazelException("Could not start listening as a SocketException occured", e); - } - } - - /// - /// Called when a new connection has been accepted by the listener. - /// - /// The asyncronous operation's result. - void AcceptConnection(IAsyncResult result) - { - //Accept Tcp socket - Socket tcpSocket; - try - { - tcpSocket = listener.EndAccept(result); - } - catch (ObjectDisposedException) - { - //If the socket's been disposed then we can just end there. - return; - } - - //Start listening for the next connection - listener.BeginAccept(AcceptConnection, null); - - //Sort the event out - TcpConnection tcpConnection = new TcpConnection(tcpSocket); - - //Wait for handshake - tcpConnection.StartWaitingForHandshake( - delegate (MessageReader msg) - { - InvokeNewConnection(msg, tcpConnection); - } - ); - } - - /// - protected override void Dispose(bool disposing) - { - if (disposing) - { - listener.Dispose(); - } - - base.Dispose(disposing); - } - } -} \ No newline at end of file diff --git a/Hazel/Udp/UdpClientConnection.cs b/Hazel/Udp/UdpClientConnection.cs index ee678fb..9ada44a 100644 --- a/Hazel/Udp/UdpClientConnection.cs +++ b/Hazel/Udp/UdpClientConnection.cs @@ -55,7 +55,11 @@ namespace Hazel.Udp private void ManageReliablePacketsInternal(object state) { base.ManageReliablePackets(); - reliablePacketTimer.Change(100, Timeout.Infinite); + try + { + reliablePacketTimer.Change(100, Timeout.Infinite); + } + catch { } } ///