]> git.deb.at Git - rhonda/impostor.hazel.git/commitdiff
Readd TCP capability, this helps deal with the lack of fragmented messaging in UDP...
authorForest <chocozilla@gmail.com>
Sat, 5 Jan 2019 00:57:43 +0000 (16:57 -0800)
committerForest <chocozilla@gmail.com>
Sat, 5 Jan 2019 00:57:43 +0000 (16:57 -0800)
16 files changed:
Hazel/Connection.cs
Hazel/ConnectionListener.cs
Hazel/DataReceivedEventArgs.cs
Hazel/Hazel.csproj
Hazel/MessageReader.cs
Hazel/MessageWriter.cs
Hazel/NetworkConnection.cs
Hazel/NewConnectionEventArgs.cs
Hazel/SendOption.cs
Hazel/Tcp/StateObject.cs [new file with mode: 0644]
Hazel/Tcp/TcpConnection.cs [new file with mode: 0644]
Hazel/Tcp/TcpConnectionListener.cs [new file with mode: 0644]
Hazel/Udp/UdpClientConnection.cs
Hazel/Udp/UdpConnection.Reliable.cs
Hazel/Udp/UdpConnection.cs
Hazel/Udp/UdpServerConnection.cs

index 5e14cc66e40af5a9a067d1a71eaf1a83967b8243..998fe6a9c155b6539f0d3f481901b05d373eff73 100644 (file)
@@ -48,7 +48,7 @@ namespace Hazel
         /// <example>
         ///     <code language="C#" source="DocInclude/TcpClientExample.cs"/>
         /// </example>
-        public Action<DataReceivedEventArgs> DataReceived;
+        public event Action<DataReceivedEventArgs> DataReceived;
 
         public int TestLagMs = -1;
         
@@ -174,24 +174,7 @@ namespace Hazel
         ///     </para>
         /// </remarks>
         public abstract void SendBytes(byte[] bytes, SendOption sendOption = SendOption.None);
-
-        /// <summary>
-        ///     Sends a number of bytes to the end point of the connection using the specified <see cref="SendOption"/>.
-        /// </summary>
-        /// <param name="bytes">The bytes of the message to send.</param>
-        /// <param name="offset"></param>
-        /// <param name="length"></param>
-        /// <param name="sendOption">The option specifying how the message should be sent.</param>
-        /// <remarks>
-        ///     <include file="DocInclude/common.xml" path="docs/item[@name='Connection_SendBytes_General']/*" />
-        ///     <para>
-        ///         The sendOptions parameter is only a request to use those options and the actual method used to send the
-        ///         data is up to the implementation. There are circumstances where this parameter may be ignored but in 
-        ///         general any implementer should aim to always follow the user's request.
-        ///     </para>
-        /// </remarks>
-        public abstract void SendBytes(byte[] bytes, int offset, int length, SendOption sendOption = SendOption.None);
-
+        
         /// <summary>
         ///     Connects the connection to a server and begins listening.
         /// </summary>
@@ -232,15 +215,13 @@ namespace Hazel
         ///     received. The bytes and the send option that the message was sent with should be passed in to give to the
         ///     subscribers.
         /// </remarks>
-        protected void InvokeDataReceived(MessageReader msg, SendOption sendOption, ushort reliableId)
+        protected void InvokeDataReceived(MessageReader msg, SendOption sendOption)
         {
             //Make a copy to avoid race condition between null check and invocation
             Action<DataReceivedEventArgs> handler = DataReceived;
             if (handler != null)
             {
-                DataReceivedEventArgs args = DataReceivedEventArgs.GetObject();
-                args.Set(msg, sendOption, reliableId);
-                handler.Invoke(args);
+                handler(new DataReceivedEventArgs(msg, sendOption));
             }
             else
             {
index fde09c6a691734ed7e32cee75e8fd12536f0c619..20064e35620becd55bbd9eb13e096c9357cdff87 100644 (file)
@@ -46,7 +46,7 @@ namespace Hazel
         /// <example>
         ///     <code language="C#" source="DocInclude/TcpListenerExample.cs"/>
         /// </example>
-        public Action<NewConnectionEventArgs> NewConnection;
+        public event Action<NewConnectionEventArgs> NewConnection;
 
         /// <summary>
         ///     Makes this connection listener begin listening for connections.
@@ -80,9 +80,7 @@ namespace Hazel
             Action<NewConnectionEventArgs> handler = NewConnection;
             if (handler != null)
             {
-                NewConnectionEventArgs args = NewConnectionEventArgs.GetObject();
-                args.Set(msg, connection);
-                handler(args);
+                handler(new NewConnectionEventArgs(msg, connection));
             }
             else
             {
index 1d6ea513d58bbf2d3540871f0abdf5ee3ab980a0..a063852bc25fe94ebc897e453b76f2a23eac84bc 100644 (file)
@@ -5,58 +5,22 @@ using System.Text;
 
 namespace Hazel
 {
-    /// <summary>
-    ///     Event arguments for the <see cref="Connection.DataReceived"/> event.
-    /// </summary>
-    /// <remarks>
-    ///     <para>
-    ///         This contains information about messages received by a connection and is passed to subscribers of the 
-    ///         <see cref="Connection.DataReceived">DataEvent</see>. 
-    ///     </para>
-    ///     <include file="DocInclude/common.xml" path="docs/item[@name='Recyclable']/*" />
-    /// </remarks>
-    /// <threadsafety static="true" instance="true"/>
-    public class DataReceivedEventArgs : EventArgs
+    public struct DataReceivedEventArgs
     {
-        /// <summary>
-        ///     Returns an instance of this object from the pool.
-        /// </summary>
-        /// <returns>A new or recycled DataEventArgs object.</returns>
-        internal static DataReceivedEventArgs GetObject()
-        {
-            return new DataReceivedEventArgs();
-        }
-
         /// <summary>
         ///     The bytes received from the client.
         /// </summary>
-        public MessageReader Message { get; private set; }
+        public readonly MessageReader Message;
 
         /// <summary>
         ///     The <see cref="SendOption"/> the data was sent with.
         /// </summary>
-        public SendOption SendOption { get; private set; }
-
-        public ushort ReliableId { get; private set; }
-
-        /// <summary>
-        ///     Private constructor for object pool.
-        /// </summary>
-        DataReceivedEventArgs()
-        {
-
-        }
-
-        /// <summary>
-        ///     Sets the members of the arguments.
-        /// </summary>
-        /// <param name="bytes">The bytes received.</param>
-        /// <param name="sendOption">The send option used to send the data.</param>
-        internal void Set(MessageReader msg, SendOption sendOption, ushort reliableId)
+        public readonly SendOption SendOption;
+        
+        public DataReceivedEventArgs(MessageReader msg, SendOption sendOption)
         {
             this.Message = msg;
             this.SendOption = sendOption;
-            this.ReliableId = reliableId;
         }
     }
 }
index b8027be789aee1c7155d598ffe0f0a1a7bf65044..3cdfc7b5aed03e6d630497773802d8ee82fb3537 100644 (file)
@@ -70,6 +70,9 @@
     <Compile Include="ObjectPool.cs" />
     <Compile Include="Properties\AssemblyInfo.cs" />
     <Compile Include="SendOption.cs" />
+    <Compile Include="Tcp\StateObject.cs" />
+    <Compile Include="Tcp\TcpConnection.cs" />
+    <Compile Include="Tcp\TcpConnectionListener.cs" />
     <Compile Include="Udp\SendOptionInternal.cs" />
     <Compile Include="ConnectionStatistics.cs" />
     <Compile Include="Udp\UdpBroadcaster.cs" />
index f7d0d76ac2e3f1aa0b5fee6c29c70738fb2bffc3..8ac01bf4ee28b63590d204a2a5de53ca812799e8 100644 (file)
@@ -165,6 +165,16 @@ namespace Hazel
             return output;
         }
 
+        public uint ReadUInt32()
+        {
+            uint output = this.FastByte()
+                | (uint)this.FastByte() << 8
+                | (uint)this.FastByte() << 16
+                | (uint)this.FastByte() << 24;
+
+            return output;
+        }
+
         public int ReadInt32()
         {
             int output = this.FastByte()
index bbe12c28ac481e2e43037d47b07b8687a962e3d9..6f0c7b3d0a82a5ecb14c3ab350f9b0e90ab03495 100644 (file)
@@ -55,7 +55,12 @@ namespace Hazel
                             System.Buffer.BlockCopy(this.Buffer, 1, output, 0, this.Length - 1);
                             return output;
                         }
-
+                    case SendOption.Tcp:
+                        {
+                            byte[] output = new byte[this.Length - 4];
+                            System.Buffer.BlockCopy(this.Buffer, 4, output, 0, this.Length - 4);
+                            return output;
+                        }
                 }
             }
 
@@ -120,6 +125,9 @@ namespace Hazel
                 case SendOption.Reliable:
                     this.Length = this.Position = 3;
                     break;
+                case SendOption.Tcp:
+                    this.Length = this.Position = 4;
+                    break;
             }
         }
 
@@ -183,6 +191,15 @@ namespace Hazel
             if (this.Position > this.Length) this.Length = this.Position;
         }
 
+        public void Write(uint value)
+        {
+            this.Buffer[this.Position++] = (byte)value;
+            this.Buffer[this.Position++] = (byte)(value >> 8);
+            this.Buffer[this.Position++] = (byte)(value >> 16);
+            this.Buffer[this.Position++] = (byte)(value >> 24);
+            if (this.Position > this.Length) this.Length = this.Position;
+        }
+
         public void Write(int value)
         {
             this.Buffer[this.Position++] = (byte)value;
index 01d13c0b1d37efe1ef3721f80bef508aca4dcbba..8e7bd7efeedf0312d77349a7a14d4d24b0d0dbbd 100644 (file)
@@ -39,5 +39,38 @@ namespace Hazel
                 return BitConverter.ToInt64(bytes, bytes.Length - 8);
             }
         }
+
+        /// <summary>
+        ///     Called when the socket has been disconnected at the remote host.
+        /// </summary>
+        /// <param name="e">The exception if one was the cause.</param>
+        public override void Disconnect(string reason)
+        {
+            this.Disconnect(reason, false);
+        }
+
+        protected void Disconnect(string reason, bool skipSendDisconnect)
+        {
+            bool invoke = false;
+            lock (this)
+            {
+                if (this._state == ConnectionState.Connected)
+                {
+                    this._state = skipSendDisconnect ? ConnectionState.NotConnected : ConnectionState.Disconnecting;
+                    invoke = true;
+                }
+            }
+
+            if (invoke)
+            {
+                try
+                {
+                    InvokeDisconnected(reason);
+                }
+                catch { }
+            }
+
+            this.Dispose();
+        }
     }
 }
index 64baad528a5591590923429abb83272a4be73230..3c7ea9ca9966b4a4f055fb3f0a8fef3d7c7b6052 100644 (file)
@@ -1,58 +1,20 @@
-using System;
-using System.Collections.Generic;
-using System.Linq;
-using System.Text;
-
-namespace Hazel
+namespace Hazel
 {
-    /// <summary>
-    ///     Event arguments for the <see cref="ConnectionListener.NewConnection"/> event.
-    /// </summary>
-    /// <remarks>
-    ///     <para>
-    ///         This contains the new connection for the client that connection and is passed to subscribers of the
-    ///         <see cref="ConnectionListener.NewConnection"/> event.
-    ///     </para>
-    ///     <include file="DocInclude/common.xml" path="docs/item[@name='Recyclable']/*" />
-    /// </remarks>
-    /// <threadsafety static="true" instance="true"/>
-    public class NewConnectionEventArgs : EventArgs
+    public struct NewConnectionEventArgs
     {
         /// <summary>
-        ///     Returns an instance of this object from the pool.
-        /// </summary>
-        /// <returns>A new or recycled NewConnectionEventArgs object.</returns>
-        internal static NewConnectionEventArgs GetObject()
-        {
-            return new NewConnectionEventArgs();
-        }
-
-        /// <summary>
-        ///     The data received from the client in the handshake.
-        /// </summary>
-        public MessageReader HandshakeData { get; private set; }
-
-        /// <summary>
-        ///     The <see cref="Connection"/> to the new client.
+        /// The data received from the client in the handshake.
         /// </summary>
-        public Connection Connection { get; private set; }
+        public readonly MessageReader HandshakeData;
 
         /// <summary>
-        ///     Private constructor for object pool.
+        /// The <see cref="Connection"/> to the new client.
         /// </summary>
-        NewConnectionEventArgs()
-        {
+        public readonly Connection Connection;
 
-        }
-
-        /// <summary>
-        ///     Sets the members of the arguments.
-        /// </summary>
-        /// <param name="msg">The bytes that were received in the handshake.</param>
-        /// <param name="connection">The new connection</param>
-        internal void Set(MessageReader msg, Connection connection)
+        public NewConnectionEventArgs(MessageReader handshakeData, Connection connection)
         {
-            this.HandshakeData = msg;
+            this.HandshakeData = handshakeData;
             this.Connection = connection;
         }
     }
index c2ffb224716ac0e50928703ff1a22f03e21d6d86..23d92cc995841cb220bfd569d3e01b5cf9d2177f 100644 (file)
@@ -31,5 +31,7 @@ namespace Hazel
         ///     a larger number of protocol bytes and can be slower than unreliable delivery.
         /// </remarks>
         Reliable = 1,
+
+        Tcp = 2,
     }
 }
diff --git a/Hazel/Tcp/StateObject.cs b/Hazel/Tcp/StateObject.cs
new file mode 100644 (file)
index 0000000..bee3d66
--- /dev/null
@@ -0,0 +1,43 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+
+namespace Hazel.Tcp
+{
+    /// <summary>
+    ///     Represents the state of the current receive operation for TCP connections.
+    /// </summary>
+    struct StateObject
+    {
+        /// <summary>
+        ///     The buffer we're receiving.
+        /// </summary>
+        internal MessageReader message;
+
+        /// <summary>
+        ///     The total number of bytes received so far.
+        /// </summary>
+        internal int totalBytesReceived;
+
+        /// <summary>
+        ///     The callback to invoke once the buffer has been filled.
+        /// </summary>
+        internal Action<MessageReader> callback;
+
+        internal readonly int ExpectedSize;
+
+        /// <summary>
+        ///     Creates a StateObject with the specified length.
+        /// </summary>
+        /// <param name="length">The number of bytes expected to be received.</param>
+        /// <param name="callback">The callback to invoke once data has been received.</param>
+        internal StateObject(int length, Action<MessageReader> 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
new file mode 100644 (file)
index 0000000..b2ed7ee
--- /dev/null
@@ -0,0 +1,310 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Net;
+using System.Net.Sockets;
+using System.Text;
+
+namespace Hazel.Tcp
+{
+    /// <summary>
+    ///     Represents a connection that uses the TCP protocol.
+    /// </summary>
+    /// <inheritdoc />
+    public sealed class TcpConnection : NetworkConnection
+    {
+        /// <summary>
+        ///     The socket we're managing.
+        /// </summary>
+        Socket socket;
+
+        /// <summary>
+        ///     Creates a TcpConnection from a given TCP Socket.
+        /// </summary>
+        /// <param name="socket">The TCP socket to wrap.</param>
+        internal TcpConnection(Socket socket)
+        {
+            //Check it's a TCP socket
+            if (socket.ProtocolType != System.Net.Sockets.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;
+        }
+
+        /// <summary>
+        ///     Creates a new TCP connection.
+        /// </summary>
+        /// <param name="remoteEndPoint">A <see cref="NetworkEndPoint"/> to connect to.</param>
+        public TcpConnection(IPEndPoint remoteEndPoint, IPMode ipMode = IPMode.IPv4)
+        {
+            if (State != ConnectionState.NotConnected)
+                throw new InvalidOperationException("Cannot connect as the Connection is already connected.");
+
+            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;
+        }
+
+        /// <inheritdoc />
+        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
+            {
+                ListenForData(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, null, null);
+            }
+            catch (Exception e)
+            {
+                Disconnect("Could not send data as an occured: " + e.Message);
+            }
+
+            Statistics.LogFragmentedSend(msg.Length, fullBytes.Length);
+        }
+
+        /// <inheritdoc/>
+        /// <remarks>
+        ///     <include file="DocInclude/common.xml" path="docs/item[@name='Connection_SendBytes_General']/*" />
+        ///     <para>
+        ///         The sendOption parameter is ignored by the TcpConnection as TCP only supports FragmentedReliable 
+        ///         communication, specifying anything else will have no effect.
+        ///     </para>
+        /// </remarks>
+        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, null, null);
+            }
+            catch (Exception e)
+            {
+                Disconnect("Could not send data as an occured: " + e.Message);
+            }
+
+            Statistics.LogFragmentedSend(bytes.Length, fullBytes.Length);
+        }
+                
+        /// <summary>
+        ///     Starts waiting for a first handshake packet to be received.
+        /// </summary>
+        /// <param name="callback">The callback to invoke when the handshake has been received.</param>
+        internal void StartWaitingForHandshake(Action<MessageReader> callback)
+        {
+            this.State = ConnectionState.Connected;
+
+            try
+            {
+                ListenForData(
+                    delegate (MessageReader msg)
+                    {
+                        ListenForData(InvokeAndListen);
+
+                        //Remove version byte
+                        msg.Offset = 1;
+                        msg.Length -= 1;
+                        msg.Position = 0;
+
+                        callback.Invoke(msg);
+                    }
+                );
+            }
+            catch (Exception e)
+            {
+                Disconnect("An exception occured while initiating the first receive operation: " + e.Message);
+            }
+        }
+
+        private void InvokeAndListen(MessageReader msg)
+        {
+            this.ListenForData(InvokeAndListen);
+
+            try
+            {
+                this.InvokeDataReceived(msg, SendOption.Tcp);
+            }
+            catch { }
+        }
+
+        private void ListenForData(Action<MessageReader> callback)
+        {
+            if (State == ConnectionState.Disconnecting || State == ConnectionState.NotConnected)
+                throw new HazelException("Not connected");
+
+            var msg = MessageReader.GetSized(ushort.MaxValue);
+            socket.BeginReceive(msg.Buffer, 0, 4, SocketFlags.None, o => HeaderReadCallback(callback, o), msg);
+        }
+
+        private void HeaderReadCallback(Action<MessageReader> callback, IAsyncResult result)
+        {
+            int bytesRead = socket.EndReceive(result);
+            var msg = (MessageReader)result.AsyncState;
+
+            Statistics.LogFragmentedReceive(0, bytesRead);
+
+            // TODO: Could possibly fragment here...
+            msg.Length = GetLengthFromBytes(msg.Buffer);
+
+            socket.BeginReceive(msg.Buffer, 0, msg.Length, SocketFlags.None, o => BodyReadCallback(callback, o), msg);
+        }
+
+        private void BodyReadCallback(Action<MessageReader> callback, IAsyncResult result)
+        {
+            int bytesRead = socket.EndReceive(result);
+            var msg = (MessageReader)result.AsyncState;
+            msg.Position += bytesRead;
+
+            Statistics.LogFragmentedReceive(bytesRead, 0);
+
+            if (msg.Position < bytesRead)
+            {
+                socket.BeginReceive(msg.Buffer, msg.Position, msg.Length - msg.Position, SocketFlags.None, o => BodyReadCallback(callback, o), msg);
+            }
+            else
+            {
+                msg.Position = 0;
+                try
+                {
+                    callback(msg);
+                }
+                catch { }
+            }
+        }
+
+        protected override void SendDisconnect()
+        {
+            // Just dispose the connection, it's inherent to TCP.
+        }
+
+        /// <summary>
+        ///     Appends the length header to the bytes.
+        /// </summary>
+        /// <param name="bytes">The source bytes.</param>
+        /// <returns>The new bytes.</returns>
+        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;
+        }
+
+        /// <summary>
+        ///     Returns the length from a length header.
+        /// </summary>
+        /// <param name="bytes">The bytes received.</param>
+        /// <returns>The number of bytes.</returns>
+        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];
+        }
+
+        /// <inheritdoc />
+        protected override void Dispose(bool disposing)
+        {
+            if (disposing)
+            {
+                lock (this)
+                {
+                    State = ConnectionState.NotConnected;
+
+                    if (socket.Connected)
+                        socket.Shutdown(SocketShutdown.Send);
+                    socket.Close();
+                }
+            }
+
+            base.Dispose(disposing);
+        }
+    }
+}
\ No newline at end of file
diff --git a/Hazel/Tcp/TcpConnectionListener.cs b/Hazel/Tcp/TcpConnectionListener.cs
new file mode 100644 (file)
index 0000000..4b9fcd2
--- /dev/null
@@ -0,0 +1,92 @@
+using System;
+using System.Net;
+using System.Net.Sockets;
+
+namespace Hazel.Tcp
+{
+    public sealed class TcpConnectionListener : NetworkConnectionListener
+    {
+        private Socket listener;
+
+        /// <summary>
+        ///     Creates a new TcpConnectionListener for the given <see cref="IPAddress"/>, port and <see cref="IPMode"/>.
+        /// </summary>
+        /// <param name="endPoint">The end point to listen on.</param>
+        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);
+            }
+        }
+
+        /// <inheritdoc />
+        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);
+            }
+        }
+
+        /// <summary>
+        ///     Called when a new connection has been accepted by the listener.
+        /// </summary>
+        /// <param name="result">The asyncronous operation's result.</param>
+        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);
+                }
+            );
+        }
+
+        /// <inheritdoc/>
+        protected override void Dispose(bool disposing)
+        {
+            if (disposing)
+            {
+                listener.Dispose();
+            }
+
+            base.Dispose(disposing);
+        }
+    }
+}
\ No newline at end of file
index 4dd101ec9c59a27c333ffbb3ac6a0611189a3ba5..3dd3ef9e41a56465419e1a82521962491e5f0d93 100644 (file)
@@ -43,7 +43,7 @@ namespace Hazel.Udp
             else
             {
                 if (!Socket.OSSupportsIPv6)
-                    throw new HazelException("IPV6 not supported!");
+                    throw new InvalidOperationException("IPV6 not supported!");
 
                 socket = new Socket(AddressFamily.InterNetworkV6, SocketType.Dgram, ProtocolType.Udp);
                 socket.SetSocketOption(SocketOptionLevel.IPv6, (SocketOptionName)27, false);    //TODO these lines shouldn't be needed anymore
index 0e1182e288808a269d8dec7828c80290a5e50769..b5e0b4856078327ed293fd0ce073550a27b08dc8 100644 (file)
@@ -311,7 +311,7 @@ namespace Hazel.Udp
             ushort id;
             if (ProcessReliableReceive(message.Buffer, 1, out id))
             {
-                InvokeDataReceived(SendOption.Reliable, message, 3, bytesReceived, id);
+                InvokeDataReceived(SendOption.Reliable, message, 3, bytesReceived);
             }
             else
             {
index 7c1b59260299f072156b542a358133e07016ad4e..ddac37e87d78edadb79d9b03eca3580ca1497e5f 100644 (file)
@@ -78,38 +78,7 @@ namespace Hazel.Udp
             //Add header information and send
             HandleSend(bytes, (byte)sendOption);
         }
-
-        /// <summary>
-        ///     Sends a number of bytes to the end point of the connection using the specified <see cref="SendOption"/>.
-        /// </summary>
-        /// <param name="bytes">The bytes of the message to send.</param>
-        /// <param name="offset"></param>
-        /// <param name="length"></param>
-        /// <param name="sendOption">The option specifying how the message should be sent.</param>
-        /// <remarks>
-        ///     <include file="DocInclude/common.xml" path="docs/item[@name='Connection_SendBytes_General']/*" />
-        ///     <para>
-        ///         The sendOptions parameter is only a request to use those options and the actual method used to send the
-        ///         data is up to the implementation. There are circumstances where this parameter may be ignored but in 
-        ///         general any implementer should aim to always follow the user's request.
-        ///     </para>
-        /// </remarks>
-        public override void SendBytes(byte[] bytes, int offset, int length, SendOption sendOption = SendOption.None)
-        {
-            switch (sendOption)
-            {
-                //Handle reliable header and hellos
-                case SendOption.Reliable:
-                    ReliableSend((byte)sendOption, bytes, offset, length);
-                    break;
-                    
-                //Treat all else as unreliable
-                default:
-                    UnreliableSend((byte)sendOption, bytes, offset, length);
-                    break;
-            }
-        }
-
+        
         /// <summary>
         ///     Handles the reliable/fragmented sending from this connection.
         /// </summary>
@@ -172,7 +141,7 @@ namespace Hazel.Udp
                     
                 //Treat everything else as unreliable
                 default:
-                    InvokeDataReceived(SendOption.None, message, 1, bytesReceived, 0);
+                    InvokeDataReceived(SendOption.None, message, 1, bytesReceived);
                     Statistics.LogUnreliableReceive(message.Length - 1, message.Length);
                     break;
             }
@@ -217,13 +186,13 @@ namespace Hazel.Udp
         /// <param name="sendOption">The send option the message was received with.</param>
         /// <param name="buffer">The buffer received.</param>
         /// <param name="dataOffset">The offset of data in the buffer.</param>
-        void InvokeDataReceived(SendOption sendOption, MessageReader buffer, int dataOffset, int bytesReceived, ushort reliableId)
+        void InvokeDataReceived(SendOption sendOption, MessageReader buffer, int dataOffset, int bytesReceived)
         {
             buffer.Offset = dataOffset;
             buffer.Length = bytesReceived - dataOffset;
             buffer.Position = 0;
 
-            InvokeDataReceived(buffer, sendOption, reliableId);
+            InvokeDataReceived(buffer, sendOption);
         }
 
         /// <summary>
@@ -246,40 +215,7 @@ namespace Hazel.Udp
 
             HandleSend(actualBytes, (byte)UdpSendOption.Hello, acknowledgeCallback);
         }
-
-        /// <summary>
-        ///     Called when the socket has been disconnected at the remote host.
-        /// </summary>
-        /// <param name="e">The exception if one was the cause.</param>
-        public override void Disconnect(string reason)
-        {
-            this.Disconnect(reason, false);
-        }
-
-        protected void Disconnect(string reason, bool skipSendDisconnect)
-        {
-            bool invoke = false;
-            lock (this)
-            {
-                if (this._state == ConnectionState.Connected)
-                {
-                    this._state = skipSendDisconnect ? ConnectionState.NotConnected : ConnectionState.Disconnecting;
-                    invoke = true;
-                }
-            }
-
-            if (invoke)
-            {
-                try
-                {
-                    InvokeDisconnected(reason);
-                }
-                catch { }
-            }
-
-            this.Dispose();
-        }
-        
+                
         /// <inheritdoc/>
         protected override void Dispose(bool disposing)
         {
index a9f63c4f29c1392d0a426f885d1d73f58df4f92d..0cdf03e9e94cd2e78d5ad009d0923b878dcf809c 100644 (file)
@@ -59,7 +59,7 @@ namespace Hazel.Udp
         /// </remarks>
         public override void Connect(byte[] bytes = null, int timeout = 5000)
         {
-            throw new HazelException("Cannot manually connect a UdpServerConnection, did you mean to use UdpClientConnection?");
+            throw new InvalidOperationException("Cannot manually connect a UdpServerConnection, did you mean to use UdpClientConnection?");
         }
 
         /// <inheritdoc />
@@ -68,7 +68,7 @@ namespace Hazel.Udp
         /// </remarks>
         public override void ConnectAsync(byte[] bytes = null, int timeout = 5000)
         {
-            throw new HazelException("Cannot manually connect a UdpServerConnection, did you mean to use UdpClientConnection?");
+            throw new InvalidOperationException("Cannot manually connect a UdpServerConnection, did you mean to use UdpClientConnection?");
         }