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);
}
}
}