using System;
using System.Diagnostics;
using System.Net;
using System.Net.Sockets;
using System.Text;
namespace NetCoreServer
{
///
/// UDP server is used to send or multicast datagrams to UDP endpoints
///
/// Thread-safe
public class UdpServer : IDisposable
{
///
/// Initialize UDP server with a given IP address and port number
///
/// IP address
/// Port number
public UdpServer(IPAddress address, int port) : this(new IPEndPoint(address, port)) {}
///
/// Initialize UDP server with a given IP address and port number
///
/// IP address
/// Port number
public UdpServer(string address, int port) : this(new IPEndPoint(IPAddress.Parse(address), port)) {}
///
/// Initialize UDP server with a given DNS endpoint
///
/// DNS endpoint
public UdpServer(DnsEndPoint endpoint) : this(endpoint as EndPoint, endpoint.Host, endpoint.Port) {}
///
/// Initialize UDP server with a given IP endpoint
///
/// IP endpoint
public UdpServer(IPEndPoint endpoint) : this(endpoint as EndPoint, endpoint.Address.ToString(), endpoint.Port) {}
///
/// Initialize UDP server with a given endpoint, address and port
///
/// Endpoint
/// Server address
/// Server port
private UdpServer(EndPoint endpoint, string address, int port)
{
Id = Guid.NewGuid();
Address = address;
Port = port;
Endpoint = endpoint;
}
///
/// Server Id
///
public Guid Id { get; }
///
/// UDP server address
///
public string Address { get; }
///
/// UDP server port
///
public int Port { get; }
///
/// Endpoint
///
public EndPoint Endpoint { get; private set; }
///
/// Multicast endpoint
///
public EndPoint MulticastEndpoint { get; private set; }
///
/// Socket
///
public Socket Socket { get; private set; }
///
/// Number of bytes pending sent by the server
///
public long BytesPending { get; private set; }
///
/// Number of bytes sending by the server
///
public long BytesSending { get; private set; }
///
/// Number of bytes sent by the server
///
public long BytesSent { get; private set; }
///
/// Number of bytes received by the server
///
public long BytesReceived { get; private set; }
///
/// Number of datagrams sent by the server
///
public long DatagramsSent { get; private set; }
///
/// Number of datagrams received by the server
///
public long DatagramsReceived { get; private set; }
///
/// Option: dual mode socket
///
///
/// Specifies whether the Socket is a dual-mode socket used for both IPv4 and IPv6.
/// Will work only if socket is bound on IPv6 address.
///
public bool OptionDualMode { get; set; }
///
/// Option: reuse address
///
///
/// This option will enable/disable SO_REUSEADDR if the OS support this feature
///
public bool OptionReuseAddress { get; set; }
///
/// Option: enables a socket to be bound for exclusive access
///
///
/// This option will enable/disable SO_EXCLUSIVEADDRUSE if the OS support this feature
///
public bool OptionExclusiveAddressUse { get; set; }
///
/// Option: receive buffer limit
///
public int OptionReceiveBufferLimit { get; set; } = 0;
///
/// Option: receive buffer size
///
public int OptionReceiveBufferSize { get; set; } = 8192;
///
/// Option: send buffer limit
///
public int OptionSendBufferLimit { get; set; } = 0;
///
/// Option: send buffer size
///
public int OptionSendBufferSize { get; set; } = 8192 * 10;
#region Connect/Disconnect client
///
/// Is the server started?
///
public bool IsStarted { get; private set; }
///
/// Create a new socket object
///
///
/// Method may be override if you need to prepare some specific socket object in your implementation.
///
/// Socket object
protected virtual Socket CreateSocket()
{
return new Socket(Endpoint.AddressFamily, SocketType.Dgram, ProtocolType.Udp);
}
///
/// Start the server (synchronous)
///
/// 'true' if the server was successfully started, 'false' if the server failed to start
public virtual bool Start()
{
Debug.Assert(!IsStarted, "UDP server is already started!");
if (IsStarted)
return false;
// Setup buffers
_receiveBuffer = new Buffer();
_sendBuffer = new Buffer();
// Setup event args
_receiveEventArg = new SocketAsyncEventArgs();
_receiveEventArg.Completed += OnAsyncCompleted;
_sendEventArg = new SocketAsyncEventArgs();
_sendEventArg.Completed += OnAsyncCompleted;
// Create a new server socket
Socket = CreateSocket();
// Update the server socket disposed flag
IsSocketDisposed = false;
// Apply the option: reuse address
Socket.SetSocketOption(SocketOptionLevel.Socket, SocketOptionName.ReuseAddress, OptionReuseAddress);
// Apply the option: exclusive address use
Socket.SetSocketOption(SocketOptionLevel.Socket, SocketOptionName.ExclusiveAddressUse, OptionExclusiveAddressUse);
// Apply the option: dual mode (this option must be applied before recieving)
if (Socket.AddressFamily == AddressFamily.InterNetworkV6)
Socket.DualMode = OptionDualMode;
// Bind the server socket to the endpoint
Socket.Bind(Endpoint);
// Refresh the endpoint property based on the actual endpoint created
Endpoint = Socket.LocalEndPoint;
// Call the server starting handler
OnStarting();
// Prepare receive endpoint
_receiveEndpoint = new IPEndPoint((Endpoint.AddressFamily == AddressFamily.InterNetworkV6) ? IPAddress.IPv6Any : IPAddress.Any, 0);
// Prepare receive & send buffers
_receiveBuffer.Reserve(OptionReceiveBufferSize);
// Reset statistic
BytesPending = 0;
BytesSending = 0;
BytesSent = 0;
BytesReceived = 0;
DatagramsSent = 0;
DatagramsReceived = 0;
// Update the started flag
IsStarted = true;
// Call the server started handler
OnStarted();
return true;
}
///
/// Start the server with a given multicast IP address and port number (synchronous)
///
/// Multicast IP address
/// Multicast port number
/// 'true' if the server was successfully started, 'false' if the server failed to start
public virtual bool Start(IPAddress multicastAddress, int multicastPort) => Start(new IPEndPoint(multicastAddress, multicastPort));
///
/// Start the server with a given multicast IP address and port number (synchronous)
///
/// Multicast IP address
/// Multicast port number
/// 'true' if the server was successfully started, 'false' if the server failed to start
public virtual bool Start(string multicastAddress, int multicastPort) => Start(new IPEndPoint(IPAddress.Parse(multicastAddress), multicastPort));
///
/// Start the server with a given multicast endpoint (synchronous)
///
/// Multicast endpoint
/// 'true' if the server was successfully started, 'false' if the server failed to start
public virtual bool Start(EndPoint multicastEndpoint)
{
MulticastEndpoint = multicastEndpoint;
return Start();
}
///
/// Stop the server (synchronous)
///
/// 'true' if the server was successfully stopped, 'false' if the server is already stopped
public virtual bool Stop()
{
Debug.Assert(IsStarted, "UDP server is not started!");
if (!IsStarted)
return false;
// Reset event args
_receiveEventArg.Completed -= OnAsyncCompleted;
_sendEventArg.Completed -= OnAsyncCompleted;
// Call the server stopping handler
OnStopping();
try
{
// Close the server socket
Socket.Close();
// Dispose the server socket
Socket.Dispose();
// Dispose event arguments
_receiveEventArg.Dispose();
_sendEventArg.Dispose();
// Update the server socket disposed flag
IsSocketDisposed = true;
}
catch (ObjectDisposedException) {}
// Update the started flag
IsStarted = false;
// Update sending/receiving flags
_receiving = false;
_sending = false;
// Clear send/receive buffers
ClearBuffers();
// Call the server stopped handler
OnStopped();
return true;
}
///
/// Restart the server (synchronous)
///
/// 'true' if the server was successfully restarted, 'false' if the server failed to restart
public virtual bool Restart()
{
if (!Stop())
return false;
return Start();
}
#endregion
#region Send/Receive data
// Receive and send endpoints
EndPoint _receiveEndpoint;
EndPoint _sendEndpoint;
// Receive buffer
private bool _receiving;
private Buffer _receiveBuffer;
private SocketAsyncEventArgs _receiveEventArg;
// Send buffer
private bool _sending;
private Buffer _sendBuffer;
private SocketAsyncEventArgs _sendEventArg;
///
/// Multicast datagram to the prepared mulicast endpoint (synchronous)
///
/// Datagram buffer to multicast
/// Size of multicasted datagram
public virtual long Multicast(byte[] buffer) => Multicast(buffer.AsSpan());
///
/// Multicast datagram to the prepared mulicast endpoint (synchronous)
///
/// Datagram buffer to multicast
/// Datagram buffer offset
/// Datagram buffer size
/// Size of multicasted datagram
public virtual long Multicast(byte[] buffer, long offset, long size) => Multicast(buffer.AsSpan((int)offset, (int)size));
///
/// Multicast datagram to the prepared mulicast endpoint (synchronous)
///
/// Datagram buffer to multicast as a span of bytes
/// Size of multicasted datagram
public virtual long Multicast(ReadOnlySpan buffer) => Send(MulticastEndpoint, buffer);
///
/// Multicast text to the prepared mulicast endpoint (synchronous)
///
/// Text string to multicast
/// Size of multicasted datagram
public virtual long Multicast(string text) => Multicast(Encoding.UTF8.GetBytes(text));
///
/// Multicast text to the prepared mulicast endpoint (synchronous)
///
/// Text to multicast as a span of characters
/// Size of multicasted datagram
public virtual long Multicast(ReadOnlySpan text) => Multicast(Encoding.UTF8.GetBytes(text.ToArray()));
///
/// Multicast datagram to the prepared mulicast endpoint (asynchronous)
///
/// Datagram buffer to multicast
/// 'true' if the datagram was successfully multicasted, 'false' if the datagram was not multicasted
public virtual bool MulticastAsync(byte[] buffer) => MulticastAsync(buffer.AsSpan());
///
/// Multicast datagram to the prepared mulicast endpoint (asynchronous)
///
/// Datagram buffer to multicast
/// Datagram buffer offset
/// Datagram buffer size
/// 'true' if the datagram was successfully multicasted, 'false' if the datagram was not multicasted
public virtual bool MulticastAsync(byte[] buffer, long offset, long size) => MulticastAsync(buffer.AsSpan((int)offset, (int)size));
///
/// Multicast datagram to the prepared mulicast endpoint (asynchronous)
///
/// Datagram buffer to multicast as a span of bytes
/// 'true' if the datagram was successfully multicasted, 'false' if the datagram was not multicasted
public virtual bool MulticastAsync(ReadOnlySpan buffer) => SendAsync(MulticastEndpoint, buffer);
///
/// Multicast text to the prepared mulicast endpoint (asynchronous)
///
/// Text string to multicast
/// 'true' if the text was successfully multicasted, 'false' if the text was not multicasted
public virtual bool MulticastAsync(string text) => MulticastAsync(Encoding.UTF8.GetBytes(text));
///
/// Multicast text to the prepared mulicast endpoint (asynchronous)
///
/// Text to multicast as a span of characters
/// 'true' if the text was successfully multicasted, 'false' if the text was not multicasted
public virtual bool MulticastAsync(ReadOnlySpan text) => MulticastAsync(Encoding.UTF8.GetBytes(text.ToArray()));
///
/// Send datagram to the connected server (synchronous)
///
/// Datagram buffer to send
/// Size of sent datagram
public virtual long Send(byte[] buffer) => Send(buffer.AsSpan());
///
/// Send datagram to the connected server (synchronous)
///
/// Datagram buffer to send
/// Datagram buffer offset
/// Datagram buffer size
/// Size of sent datagram
public virtual long Send(byte[] buffer, long offset, long size) => Send(buffer.AsSpan((int)offset, (int)size));
///
/// Send datagram to the connected server (synchronous)
///
/// Datagram buffer to send as a span of bytes
/// Size of sent datagram
public virtual long Send(ReadOnlySpan buffer) => Send(Endpoint, buffer);
///
/// Send text to the connected server (synchronous)
///
/// Text string to send
/// Size of sent datagram
public virtual long Send(string text) => Send(Encoding.UTF8.GetBytes(text));
///
/// Send text to the connected server (synchronous)
///
/// Text to send as a span of characters
/// Size of sent datagram
public virtual long Send(ReadOnlySpan text) => Send(Encoding.UTF8.GetBytes(text.ToArray()));
///
/// Send datagram to the given endpoint (synchronous)
///
/// Endpoint to send
/// Datagram buffer to send
/// Size of sent datagram
public virtual long Send(EndPoint endpoint, byte[] buffer) => Send(endpoint, buffer.AsSpan());
///
/// Send datagram to the given endpoint (synchronous)
///
/// Endpoint to send
/// Datagram buffer to send
/// Datagram buffer offset
/// Datagram buffer size
/// Size of sent datagram
public virtual long Send(EndPoint endpoint, byte[] buffer, long offset, long size) => Send(endpoint, buffer.AsSpan((int)offset, (int)size));
///
/// Send datagram to the given endpoint (synchronous)
///
/// Endpoint to send
/// Datagram buffer to send as a span of bytes
/// Size of sent datagram
public virtual long Send(EndPoint endpoint, ReadOnlySpan buffer)
{
if (!IsStarted)
return 0;
if (buffer.IsEmpty)
return 0;
try
{
// Sent datagram to the client
long sent = Socket.SendTo(buffer, SocketFlags.None, endpoint);
if (sent > 0)
{
// Update statistic
DatagramsSent++;
BytesSent += sent;
// Call the datagram sent handler
OnSent(endpoint, sent);
}
return sent;
}
catch (ObjectDisposedException) { return 0; }
catch (SocketException ex)
{
SendError(ex.SocketErrorCode);
return 0;
}
}
///
/// Send text to the given endpoint (synchronous)
///
/// Endpoint to send
/// Text string to send
/// Size of sent datagram
public virtual long Send(EndPoint endpoint, string text) => Send(endpoint, Encoding.UTF8.GetBytes(text));
///
/// Send text to the given endpoint (synchronous)
///
/// Endpoint to send
/// Text to send as a span of characters
/// Size of sent datagram
public virtual long Send(EndPoint endpoint, ReadOnlySpan text) => Send(endpoint, Encoding.UTF8.GetBytes(text.ToArray()));
///
/// Send datagram to the given endpoint (asynchronous)
///
/// Endpoint to send
/// Datagram buffer to send
/// 'true' if the datagram was successfully sent, 'false' if the datagram was not sent
public virtual bool SendAsync(EndPoint endpoint, byte[] buffer) => SendAsync(endpoint, buffer.AsSpan());
///
/// Send datagram to the given endpoint (asynchronous)
///
/// Endpoint to send
/// Datagram buffer to send
/// Datagram buffer offset
/// Datagram buffer size
/// 'true' if the datagram was successfully sent, 'false' if the datagram was not sent
public virtual bool SendAsync(EndPoint endpoint, byte[] buffer, long offset, long size) => SendAsync(endpoint, buffer.AsSpan((int)offset, (int)size));
///
/// Send datagram to the given endpoint (asynchronous)
///
/// Endpoint to send
/// Datagram buffer to send as a span of bytes
/// 'true' if the datagram was successfully sent, 'false' if the datagram was not sent
public virtual bool SendAsync(EndPoint endpoint, ReadOnlySpan buffer)
{
if (_sending)
return false;
if (!IsStarted)
return false;
if (buffer.IsEmpty)
return true;
// Check the send buffer limit
if (((_sendBuffer.Size + buffer.Length) > OptionSendBufferLimit) && (OptionSendBufferLimit > 0))
{
SendError(SocketError.NoBufferSpaceAvailable);
return false;
}
// Fill the main send buffer
_sendBuffer.Append(buffer);
// Update statistic
BytesSending = _sendBuffer.Size;
// Update send endpoint
_sendEndpoint = endpoint;
// Try to send the main buffer
TrySend();
return true;
}
///
/// Send text to the given endpoint (asynchronous)
///
/// Endpoint to send
/// Text string to send
/// 'true' if the text was successfully sent, 'false' if the text was not sent
public virtual bool SendAsync(EndPoint endpoint, string text) => SendAsync(endpoint, Encoding.UTF8.GetBytes(text));
///
/// Send text to the given endpoint (asynchronous)
///
/// Endpoint to send
/// Text to send as a span of characters
/// 'true' if the text was successfully sent, 'false' if the text was not sent
public virtual bool SendAsync(EndPoint endpoint, ReadOnlySpan text) => SendAsync(endpoint, Encoding.UTF8.GetBytes(text.ToArray()));
///
/// Receive a new datagram from the given endpoint (synchronous)
///
/// Endpoint to receive from
/// Datagram buffer to receive
/// Size of received datagram
public virtual long Receive(ref EndPoint endpoint, byte[] buffer) { return Receive(ref endpoint, buffer, 0, buffer.Length); }
///
/// Receive a new datagram from the given endpoint (synchronous)
///
/// Endpoint to receive from
/// Datagram buffer to receive
/// Datagram buffer offset
/// Datagram buffer size
/// Size of received datagram
public virtual long Receive(ref EndPoint endpoint, byte[] buffer, long offset, long size)
{
if (!IsStarted)
return 0;
if (size == 0)
return 0;
try
{
// Receive datagram from the client
long received = Socket.ReceiveFrom(buffer, (int)offset, (int)size, SocketFlags.None, ref endpoint);
// Update statistic
DatagramsReceived++;
BytesReceived += received;
// Call the datagram received handler
OnReceived(endpoint, buffer, offset, size);
return received;
}
catch (ObjectDisposedException) { return 0; }
catch (SocketException ex)
{
SendError(ex.SocketErrorCode);
return 0;
}
}
///
/// Receive text from the given endpoint (synchronous)
///
/// Endpoint to receive from
/// Text size to receive
/// Received text
public virtual string Receive(ref EndPoint endpoint, long size)
{
var buffer = new byte[size];
var length = Receive(ref endpoint, buffer);
return Encoding.UTF8.GetString(buffer, 0, (int)length);
}
///
/// Receive datagram from the client (asynchronous)
///
public virtual void ReceiveAsync()
{
// Try to receive datagram
TryReceive();
}
///
/// Try to receive new data
///
private void TryReceive()
{
if (_receiving)
return;
if (!IsStarted)
return;
try
{
// Async receive with the receive handler
_receiving = true;
_receiveEventArg.RemoteEndPoint = _receiveEndpoint;
_receiveEventArg.SetBuffer(_receiveBuffer.Data, 0, (int)_receiveBuffer.Capacity);
if (!Socket.ReceiveFromAsync(_receiveEventArg))
ProcessReceiveFrom(_receiveEventArg);
}
catch (ObjectDisposedException) {}
}
///
/// Try to send pending data
///
private void TrySend()
{
if (_sending)
return;
if (!IsStarted)
return;
try
{
// Async write with the write handler
_sending = true;
_sendEventArg.RemoteEndPoint = _sendEndpoint;
_sendEventArg.SetBuffer(_sendBuffer.Data, 0, (int)(_sendBuffer.Size));
if (!Socket.SendToAsync(_sendEventArg))
ProcessSendTo(_sendEventArg);
}
catch (ObjectDisposedException) {}
}
///
/// Clear send/receive buffers
///
private void ClearBuffers()
{
// Clear send buffers
_sendBuffer.Clear();
// Update statistic
BytesPending = 0;
BytesSending = 0;
}
#endregion
#region IO processing
///
/// This method is called whenever a receive or send operation is completed on a socket
///
private void OnAsyncCompleted(object sender, SocketAsyncEventArgs e)
{
if (IsSocketDisposed)
return;
// Determine which type of operation just completed and call the associated handler
switch (e.LastOperation)
{
case SocketAsyncOperation.ReceiveFrom:
ProcessReceiveFrom(e);
break;
case SocketAsyncOperation.SendTo:
ProcessSendTo(e);
break;
default:
throw new ArgumentException("The last operation completed on the socket was not a receive or send");
}
}
///
/// This method is invoked when an asynchronous receive from operation completes
///
private void ProcessReceiveFrom(SocketAsyncEventArgs e)
{
_receiving = false;
if (!IsStarted)
return;
// Check for error
if (e.SocketError != SocketError.Success)
{
SendError(e.SocketError);
// Call the datagram received zero handler
OnReceived(e.RemoteEndPoint, _receiveBuffer.Data, 0, 0);
return;
}
// Received some data from the client
long size = e.BytesTransferred;
// Update statistic
DatagramsReceived++;
BytesReceived += size;
// Call the datagram received handler
OnReceived(e.RemoteEndPoint, _receiveBuffer.Data, 0, size);
// If the receive buffer is full increase its size
if (_receiveBuffer.Capacity == size)
{
// Check the receive buffer limit
if (((2 * size) > OptionReceiveBufferLimit) && (OptionReceiveBufferLimit > 0))
{
SendError(SocketError.NoBufferSpaceAvailable);
// Call the datagram received zero handler
OnReceived(e.RemoteEndPoint, _receiveBuffer.Data, 0, 0);
return;
}
_receiveBuffer.Reserve(2 * size);
}
}
///
/// This method is invoked when an asynchronous send to operation completes
///
private void ProcessSendTo(SocketAsyncEventArgs e)
{
_sending = false;
if (!IsStarted)
return;
// Check for error
if (e.SocketError != SocketError.Success)
{
SendError(e.SocketError);
// Call the buffer sent zero handler
OnSent(_sendEndpoint, 0);
return;
}
long sent = e.BytesTransferred;
// Send some data to the client
if (sent > 0)
{
// Update statistic
BytesSending = 0;
BytesSent += sent;
// Clear the send buffer
_sendBuffer.Clear();
// Call the buffer sent handler
OnSent(_sendEndpoint, sent);
}
}
#endregion
#region Datagram handlers
///
/// Handle server starting notification
///
protected virtual void OnStarting() {}
///
/// Handle server started notification
///
protected virtual void OnStarted() {}
///
/// Handle server stopping notification
///
protected virtual void OnStopping() {}
///
/// Handle server stopped notification
///
protected virtual void OnStopped() {}
///
/// Handle datagram received notification
///
/// Received endpoint
/// Received datagram buffer
/// Received datagram buffer offset
/// Received datagram buffer size
///
/// Notification is called when another datagram was received from some endpoint
///
protected virtual void OnReceived(EndPoint endpoint, byte[] buffer, long offset, long size) {}
///
/// Handle datagram sent notification
///
/// Endpoint of sent datagram
/// Size of sent datagram buffer
///
/// Notification is called when a datagram was sent to the client.
/// This handler could be used to send another datagram to the client for instance when the pending size is zero.
///
protected virtual void OnSent(EndPoint endpoint, long sent) {}
///
/// Handle error notification
///
/// Socket error code
protected virtual void OnError(SocketError error) {}
#endregion
#region Error handling
///
/// Send error notification
///
/// Socket error code
private void SendError(SocketError error)
{
// Skip disconnect errors
if ((error == SocketError.ConnectionAborted) ||
(error == SocketError.ConnectionRefused) ||
(error == SocketError.ConnectionReset) ||
(error == SocketError.OperationAborted) ||
(error == SocketError.Shutdown))
return;
OnError(error);
}
#endregion
#region IDisposable implementation
///
/// Disposed flag
///
public bool IsDisposed { get; private set; }
///
/// Server socket disposed flag
///
public bool IsSocketDisposed { get; private set; } = true;
// Implement IDisposable.
public void Dispose()
{
Dispose(true);
GC.SuppressFinalize(this);
}
protected virtual void Dispose(bool disposingManagedResources)
{
// The idea here is that Dispose(Boolean) knows whether it is
// being called to do explicit cleanup (the Boolean is true)
// versus being called due to a garbage collection (the Boolean
// is false). This distinction is useful because, when being
// disposed explicitly, the Dispose(Boolean) method can safely
// execute code using reference type fields that refer to other
// objects knowing for sure that these other objects have not been
// finalized or disposed of yet. When the Boolean is false,
// the Dispose(Boolean) method should not execute code that
// refer to reference type fields because those objects may
// have already been finalized."
if (!IsDisposed)
{
if (disposingManagedResources)
{
// Dispose managed resources here...
Stop();
}
// Dispose unmanaged resources here...
// Set large fields to null here...
// Mark as disposed.
IsDisposed = true;
}
}
#endregion
}
}