using System; using System.Net.Sockets; namespace Hazel.Udp { /// /// Represents a connection that uses the UDP protocol. /// /// 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(() => 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; } /// /// Writes the given bytes to the connection. /// /// The bytes to write. protected abstract void WriteBytesToConnection(byte[] bytes, int length); /// 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); // 写入之前留空的用于可靠传输的ID 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; } /// /// Handles the reliable/fragmented sending from this connection. /// /// The data being sent. /// The specified as its byte value. /// The callback to invoke when this packet is acknowledged. /// The bytes that should actually be sent. 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; } } /// /// Handles the receiving of data. /// /// The buffer containing the bytes received. protected internal virtual void HandleReceive(MessageReader message, int bytesReceived) { ushort id; switch (message.Buffer[0]) { //Handle reliable receives case (byte)SendOption.Reliable: //c //! 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: //c //! 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; } } /// /// Sends bytes using the unreliable UDP protocol. /// /// The SendOption to attach. /// The data. void UnreliableSend(byte sendOption, byte[] data) { this.UnreliableSend(sendOption, data, 0, data.Length); } /// /// Sends bytes using the unreliable UDP protocol. /// /// The data. /// The SendOption to attach. /// /// 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); } /// /// Helper method to invoke the data received event. /// /// The send option the message was received with. /// The buffer received. /// The offset of data in the buffer. void InvokeDataReceived(SendOption sendOption, MessageReader buffer, int dataOffset, int bytesReceived) { buffer.Offset = dataOffset; buffer.Length = bytesReceived - dataOffset; buffer.Position = 0; InvokeDataReceived(buffer, sendOption); } /// /// Sends a hello packet to the remote endpoint. /// /// The callback to invoke when the hello packet is acknowledged. 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); } /// protected override void Dispose(bool disposing) { if (disposing) { DisposeKeepAliveTimer(); DisposeReliablePackets(); } base.Dispose(disposing); } } }