diff options
Diffstat (limited to 'Tools/Hazel-Networking/Hazel/Udp/UdpConnection.cs')
-rw-r--r-- | Tools/Hazel-Networking/Hazel/Udp/UdpConnection.cs | 259 |
1 files changed, 259 insertions, 0 deletions
diff --git a/Tools/Hazel-Networking/Hazel/Udp/UdpConnection.cs b/Tools/Hazel-Networking/Hazel/Udp/UdpConnection.cs new file mode 100644 index 0000000..e64576a --- /dev/null +++ b/Tools/Hazel-Networking/Hazel/Udp/UdpConnection.cs @@ -0,0 +1,259 @@ +using System; +using System.Net.Sockets; + +namespace Hazel.Udp +{ + /// <summary> + /// Represents a connection that uses the UDP protocol. + /// </summary> + /// <inheritdoc /> + public abstract partial class UdpConnection : NetworkConnection + { + public static readonly byte[] EmptyDisconnectBytes = new byte[] { (byte)UdpSendOption.Disconnect }; + + public override float AveragePingMs => this._pingMs; + protected readonly ILogger logger; + + + public UdpConnection(ILogger logger) : base() + { + this.logger = logger; + this.PacketPool = new ObjectPool<Packet>(() => new Packet(this)); + } + + internal static Socket CreateSocket(IPMode ipMode) + { + Socket socket; + if (ipMode == IPMode.IPv4) + { + socket = new Socket(AddressFamily.InterNetwork, SocketType.Dgram, ProtocolType.Udp); + } + else + { + if (!Socket.OSSupportsIPv6) + throw new InvalidOperationException("IPV6 not supported!"); + + socket = new Socket(AddressFamily.InterNetworkV6, SocketType.Dgram, ProtocolType.Udp); + socket.SetSocketOption(SocketOptionLevel.IPv6, SocketOptionName.IPv6Only, false); + } + + try + { + socket.DontFragment = false; + } + catch { } + + try + { + const int SIO_UDP_CONNRESET = -1744830452; + socket.IOControl(SIO_UDP_CONNRESET, new byte[1], null); + } + catch { } // Only necessary on Windows + + return socket; + } + + /// <summary> + /// Writes the given bytes to the connection. + /// </summary> + /// <param name="bytes">The bytes to write.</param> + protected abstract void WriteBytesToConnection(byte[] bytes, int length); + + /// <inheritdoc/> + public override SendErrors Send(MessageWriter msg) + { + if (this._state != ConnectionState.Connected) + { + return SendErrors.Disconnected; + } + + try + { + byte[] buffer = new byte[msg.Length]; + Buffer.BlockCopy(msg.Buffer, 0, buffer, 0, msg.Length); + + switch (msg.SendOption) + { + case SendOption.Reliable: + ResetKeepAliveTimer(); + + AttachReliableID(buffer, 1); + WriteBytesToConnection(buffer, buffer.Length); + Statistics.LogReliableSend(buffer.Length - 3); + break; + + default: + WriteBytesToConnection(buffer, buffer.Length); + Statistics.LogUnreliableSend(buffer.Length - 1); + break; + } + } + catch (Exception e) + { + this.logger?.WriteError("Unknown exception while sending: " + e); + return SendErrors.Unknown; + } + + return SendErrors.None; + } + + /// <summary> + /// Handles the reliable/fragmented sending from this connection. + /// </summary> + /// <param name="data">The data being sent.</param> + /// <param name="sendOption">The <see cref="SendOption"/> specified as its byte value.</param> + /// <param name="ackCallback">The callback to invoke when this packet is acknowledged.</param> + /// <returns>The bytes that should actually be sent.</returns> + protected virtual void HandleSend(byte[] data, byte sendOption, Action ackCallback = null) + { + switch (sendOption) + { + case (byte)UdpSendOption.Ping: + case (byte)SendOption.Reliable: + case (byte)UdpSendOption.Hello: + ReliableSend(sendOption, data, ackCallback); + break; + + //Treat all else as unreliable + default: + UnreliableSend(sendOption, data); + break; + } + } + + /// <summary> + /// Handles the receiving of data. + /// </summary> + /// <param name="message">The buffer containing the bytes received.</param> + protected internal virtual void HandleReceive(MessageReader message, int bytesReceived) + { + ushort id; + switch (message.Buffer[0]) + { + //Handle reliable receives + case (byte)SendOption.Reliable: + ReliableMessageReceive(message, bytesReceived); + break; + + //Handle acknowledgments + case (byte)UdpSendOption.Acknowledgement: + AcknowledgementMessageReceive(message.Buffer, bytesReceived); + message.Recycle(); + break; + + //We need to acknowledge hello and ping messages but dont want to invoke any events! + case (byte)UdpSendOption.Ping: + ProcessReliableReceive(message.Buffer, 1, out id); + Statistics.LogHelloReceive(bytesReceived); + message.Recycle(); + break; + case (byte)UdpSendOption.Hello: + ProcessReliableReceive(message.Buffer, 1, out id); + Statistics.LogHelloReceive(bytesReceived); + message.Recycle(); + break; + + case (byte)UdpSendOption.Disconnect: + message.Offset = 1; + message.Position = 0; + DisconnectRemote("The remote sent a disconnect request", message); + message.Recycle(); + break; + + case (byte)SendOption.None: + InvokeDataReceived(SendOption.None, message, 1, bytesReceived); + Statistics.LogUnreliableReceive(bytesReceived - 1, bytesReceived); + break; + + // Treat everything else as garbage + default: + message.Recycle(); + + // TODO: A new stat for unused data + Statistics.LogUnreliableReceive(bytesReceived - 1, bytesReceived); + break; + } + } + + /// <summary> + /// Sends bytes using the unreliable UDP protocol. + /// </summary> + /// <param name="sendOption">The SendOption to attach.</param> + /// <param name="data">The data.</param> + void UnreliableSend(byte sendOption, byte[] data) + { + this.UnreliableSend(sendOption, data, 0, data.Length); + } + + /// <summary> + /// Sends bytes using the unreliable UDP protocol. + /// </summary> + /// <param name="data">The data.</param> + /// <param name="sendOption">The SendOption to attach.</param> + /// <param name="offset"></param> + /// <param name="length"></param> + void UnreliableSend(byte sendOption, byte[] data, int offset, int length) + { + byte[] bytes = new byte[length + 1]; + + //Add message type + bytes[0] = sendOption; + + //Copy data into new array + Buffer.BlockCopy(data, offset, bytes, bytes.Length - length, length); + + //Write to connection + WriteBytesToConnection(bytes, bytes.Length); + + Statistics.LogUnreliableSend(length); + } + + /// <summary> + /// Helper method to invoke the data received event. + /// </summary> + /// <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) + { + buffer.Offset = dataOffset; + buffer.Length = bytesReceived - dataOffset; + buffer.Position = 0; + + InvokeDataReceived(buffer, sendOption); + } + + /// <summary> + /// Sends a hello packet to the remote endpoint. + /// </summary> + /// <param name="acknowledgeCallback">The callback to invoke when the hello packet is acknowledged.</param> + protected void SendHello(byte[] bytes, Action acknowledgeCallback) + { + //First byte of handshake is version indicator so add data after + byte[] actualBytes; + if (bytes == null) + { + actualBytes = new byte[1]; + } + else + { + actualBytes = new byte[bytes.Length + 1]; + Buffer.BlockCopy(bytes, 0, actualBytes, 1, bytes.Length); + } + + HandleSend(actualBytes, (byte)UdpSendOption.Hello, acknowledgeCallback); + } + + /// <inheritdoc/> + protected override void Dispose(bool disposing) + { + if (disposing) + { + DisposeKeepAliveTimer(); + DisposeReliablePackets(); + } + + base.Dispose(disposing); + } + } +} |