//******************************************************************************************************
// UdpServer.cs - Gbtc
//
// Copyright © 2012, Grid Protection Alliance. All Rights Reserved.
//
// Licensed to the Grid Protection Alliance (GPA) under one or more contributor license agreements. See
// the NOTICE file distributed with this work for additional information regarding copyright ownership.
// The GPA licenses this file to you under the MIT License (MIT), the "License"; you may
// not use this file except in compliance with the License. You may obtain a copy of the License at:
//
// http://www.opensource.org/licenses/MIT
//
// Unless agreed to in writing, the subject software distributed under the License is distributed on an
// "AS-IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. Refer to the
// License for the specific language governing permissions and limitations.
//
// Code Modification History:
// ----------------------------------------------------------------------------------------------------
// 07/06/2006 - Pinal C. Patel
// Original version of source code generated.
// 09/06/2006 - J. Ritchie Carroll
// Added bypass optimizations for high-speed socket access.
// 09/29/2008 - J. Ritchie Carroll
// Converted to C#.
// 07/09/2009 - Pinal C. Patel
// Modified to attempt resuming reception on SocketException for non-Handshake enabled connection.
// 07/17/2009 - Pinal C. Patel
// Added support to specify a specific interface address on a multiple interface machine.
// 09/14/2009 - Stephen C. Wills
// Added new header and license agreement.
// 10/14/2009 - Pinal C. Patel
// Fixed issue in the processing of Handshake messages.
// Added null reference checks to Stop() and DisconnectOne() for safety.
// 10/30/2009 - Pinal C. Patel
// Added support for one-way communication by specifying Port=-1 in ConfigurationString.
// 04/29/2010 - Pinal C. Patel
// Modified Start() to parse client endpoint strings correctly to address IPv6 IP parsing issue.
// 02/13/2011 - Pinal C. Patel
// Modified Start() to use "interface" in the creation of client endpoint.
// 03/10/2011 - Pinal C. Patel
// Fixed a issue reported by Jeffrey Martin at Areva-TD (jeffrey.martin-econ@areva-td.com) that
// prevented the ServerStopped event from being raised under certain configuration.
// 12/04/2011 - J. Ritchie Carroll
// Modified to use concurrent dictionary.
// 07/23/2012 - Stephen C. Wills
// Performed a full refactor to use the SocketAsyncEventArgs API calls.
// 10/31/2012 - Stephen C. Wills
// Replaced single-threaded BlockingCollection pattern with asynchronous loop pattern.
// 12/13/2012 - Starlynn Danyelle Gilliam
// Modified Header.
// 09/24/2015 - Allan V. Scheid
// Fixed Mono socket error with System.Net.Sockets.Socket.IOControl method and SIO_UDP_CONNRESET
// inside Start().
//
//******************************************************************************************************
using System;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.ComponentModel;
using System.Linq;
using System.Net;
using System.Net.Sockets;
using System.Text;
using System.Text.RegularExpressions;
using System.Threading;
using GSF.Configuration;
using GSF.Diagnostics;
using GSF.IO;
using GSF.Threading;
namespace GSF.Communication
{
///
/// Defines modes by which the UDP server will identify its clients when receiving messages.
///
public enum ClientIdentificationMode
{
///
/// Identify clients by their IP address.
///
IP,
///
/// Identify clients by the port they are bound to.
///
Port,
///
/// Identify clients by both their IP address and the port they are bound to.
///
EndPoint
}
///
/// Represents a UDP-based communication server.
///
///
///
/// Use when the primary purpose is to transmit data.
///
///
/// The socket can be bound to a specified interface on a machine with multiple interfaces by
/// specifying the interface in the (Example: "Port=8888; Clients=localhost:8989; Interface=127.0.0.1")
///
///
/// The socket can be used just for transmitting data without being bound to a local interface
/// by specifying -1 for the port number in the (Example: "Port=-1; Clients=localhost:8989")
///
///
///
/// This example shows how to use the component:
///
/// using System;
/// using GSF;
/// using GSF.Communication;
/// using GSF.Security.Cryptography;
/// using GSF.IO.Compression;
///
/// class Program
/// {
/// static UdpServer m_server;
///
/// static void Main(string[] args)
/// {
/// // Initialize the server.
/// m_server = new UdpServer("Port=8888; Clients=localhost:8989");
/// m_server.Handshake = false;
/// m_server.ReceiveTimeout = -1;
/// m_server.Encryption = CipherStrength.None;
/// m_server.Compression = CompressionStrength.NoCompression;
/// m_server.SecureSession = false;
/// m_server.Initialize();
/// // Register event handlers.
/// m_server.ServerStarted += m_server_ServerStarted;
/// m_server.ServerStopped += m_server_ServerStopped;
/// m_server.ClientConnected += m_server_ClientConnected;
/// m_server.ClientDisconnected += m_server_ClientDisconnected;
/// m_server.ReceiveClientDataComplete += m_server_ReceiveClientDataComplete;
/// // Start the server.
/// m_server.Start();
///
/// // Multicast user input to all connected clients.
/// string input;
/// while (string.Compare(input = Console.ReadLine(), "Exit", true) != 0)
/// {
/// m_server.Multicast(input);
/// }
///
/// // Stop the server on shutdown.
/// m_server.Stop();
/// }
///
/// static void m_server_ServerStarted(object sender, EventArgs e)
/// {
/// Console.WriteLine("Server has been started!");
/// }
///
/// static void m_server_ServerStopped(object sender, EventArgs e)
/// {
/// Console.WriteLine("Server has been stopped!");
/// }
///
/// static void m_server_ClientConnected(object sender, EventArgs<Guid> e)
/// {
/// Console.WriteLine(string.Format("Client connected - {0}.", e.Argument));
/// }
///
/// static void m_server_ClientDisconnected(object sender, EventArgs<Guid> e)
/// {
/// Console.WriteLine(string.Format("Client disconnected - {0}.", e.Argument));
/// }
///
/// static void m_server_ReceiveClientDataComplete(object sender, EventArgs<Guid, byte[], int> e)
/// {
/// Console.WriteLine(string.Format("Received data from {0} - {1}.", e.Argument1, m_server.TextEncoding.GetString(e.Argument2, 0, e.Argument3)));
/// }
/// }
///
///
public class UdpServer : ServerBase
{
#region [ Members ]
// Nested Types
private class UdpClientInfo
{
public TransportProvider Client;
public SocketAsyncEventArgs SendArgs;
public object SendLock;
public ConcurrentQueue SendQueue;
public ShortSynchronizedOperation DumpPayloadsOperation;
public int Sending;
}
private class UdpServerPayload
{
// Per payload state
public byte[] Data;
public int Offset;
public int Length;
public ManualResetEventSlim WaitHandle;
// Per client state
public UdpClientInfo ClientInfo;
}
// Constants
///
/// Specifies the default value for the property.
///
public const ClientIdentificationMode DefaultClientIdentificationMode = ClientIdentificationMode.EndPoint;
///
/// Specifies the default value for the property.
///
public const bool DefaultAllowDualStackSocket = true;
///
/// Specifies the default value for the property.
///
public const bool DefaultDynamicClientEndPoints = false;
///
/// Specifies the default value for the property.
///
public const int DefaultMaxSendQueueSize = 500000;
///
/// Specifies the default value for the property.
///
public const string DefaultConfigurationString = "Port=8888; Clients=localhost:8989";
///
/// Specifies the constant to be used for disabling when endpoint is not listening.
///
private const int SIO_UDP_CONNRESET = -1744830452;
// Fields
private TransportProvider m_udpServer;
private SocketAsyncEventArgs m_receiveArgs;
private readonly ConcurrentDictionary m_clientInfoLookup;
private IPStack m_ipStack;
private bool m_dynamicClientEndPoints;
private bool m_dynamicClientList;
private Dictionary m_configData;
private readonly EventHandler m_sendHandler;
private readonly EventHandler m_receiveHandler;
#endregion
#region [ Constructors ]
///
/// Initializes a new instance of the class.
///
public UdpServer() : this(DefaultConfigurationString)
{
}
///
/// Initializes a new instance of the class.
///
/// Configuration string of the . See for format.
public UdpServer(string configString) : base(TransportProtocol.Udp, configString)
{
ClientIdentificationMode = DefaultClientIdentificationMode;
AllowDualStackSocket = DefaultAllowDualStackSocket;
MaxSendQueueSize = DefaultMaxSendQueueSize;
m_clientInfoLookup = new ConcurrentDictionary();
m_sendHandler = (sender, args) => ProcessSend(args);
m_receiveHandler = (sender, args) => ProcessReceive(args);
}
///
/// Initializes a new instance of the class.
///
/// object that contains the .
public UdpServer(IContainer container) : this() => container?.Add(this);
#endregion
#region [ Properties ]
///
/// Gets or sets the mode by which the UDP server will identify its clients when receiving messages.
///
[Category("Settings")]
[DefaultValue(DefaultClientIdentificationMode)]
[Description("Mode by which the UDP server will identify its clients when receiving messages.")]
public ClientIdentificationMode ClientIdentificationMode { get; set; }
///
/// Gets or sets a boolean value that determines if dual-mode socket is allowed when endpoint address is IPv6.
///
[Category("Settings")]
[DefaultValue(DefaultAllowDualStackSocket)]
[Description("Determines if dual-mode socket is allowed when endpoint address is IPv6.")]
public bool AllowDualStackSocket { get; set; }
///
/// Gets or sets a boolean value that determines if UDP server should always
/// send responses to clients on the port that data is received from the client.
///
[Category("Settings")]
[DefaultValue(DefaultDynamicClientEndPoints)]
[Description("Determines if UDP server should always send responses to clients on the port that data is received from the client.")]
public bool DynamicClientEndPoints
{
get => m_dynamicClientEndPoints;
set
{
if (!m_dynamicClientList)
m_dynamicClientEndPoints = value;
}
}
///
/// Gets or sets the maximum size for the send queue before payloads are dumped from the queue.
///
[Category("Settings")]
[DefaultValue(DefaultMaxSendQueueSize)]
[Description("The maximum size for the send queue before payloads are dumped from the queue.")]
public int MaxSendQueueSize { get; set; }
///
/// Gets the object for the .
///
[Browsable(false)]
public Socket Server => m_udpServer.Provider;
///
/// Gets the descriptive status of the client.
///
public override string Status
{
get
{
StringBuilder statusBuilder = new StringBuilder(base.Status);
int count = 0;
foreach (ConcurrentQueue sendQueue in m_clientInfoLookup.Values.Select(clientInfo => clientInfo.SendQueue))
{
statusBuilder.AppendFormat(" Queued payloads: {0} for client {1}", sendQueue.Count, ++count);
statusBuilder.AppendLine();
}
return statusBuilder.ToString();
}
}
#endregion
#region [ Methods ]
///
/// Reads a number of bytes from the current received data buffer and writes those bytes into a byte array at the specified offset.
///
/// ID of the client from which data buffer should be read.
/// Destination buffer used to hold copied bytes.
/// 0-based starting index into destination to begin writing data.
/// The number of bytes to read from current received data buffer and write into .
/// The number of bytes read.
///
/// This function should only be called from within the event handler. Calling this method
/// outside this event will have unexpected results.
///
///
/// No received data buffer has been defined to read -or-
/// Specified does not exist, cannot read buffer.
///
/// is null.
///
/// or is less than 0 -or-
/// and will exceed length.
///
public override int Read(Guid clientID, byte[] buffer, int startIndex, int length)
{
buffer.ValidateParameters(startIndex, length);
if (!m_clientInfoLookup.TryGetValue(clientID, out UdpClientInfo clientInfo))
throw new InvalidOperationException("Specified client ID does not exist, cannot read buffer.");
TransportProvider udpClient = clientInfo.Client;
if (udpClient.ReceiveBuffer == null)
throw new InvalidOperationException("No received data buffer has been defined to read.");
int readIndex = ReadIndicies[clientID];
int sourceLength = udpClient.BytesReceived - readIndex;
int readBytes = length > sourceLength ? sourceLength : length;
Buffer.BlockCopy(udpClient.ReceiveBuffer, readIndex, buffer, startIndex, readBytes);
// Update read index for next call
readIndex += readBytes;
if (readIndex >= udpClient.BytesReceived)
readIndex = 0;
ReadIndicies[clientID] = readIndex;
return readBytes;
}
///
/// Saves settings to the config file if the property is set to true.
///
public override void SaveSettings()
{
base.SaveSettings();
if (!PersistSettings)
return;
// Save settings under the specified category.
ConfigurationFile config = ConfigurationFile.Current;
CategorizedSettingsElementCollection settings = config.Settings[SettingsCategory];
settings["IdentifyClientsBy", true].Update(ClientIdentificationMode);
settings["AllowDualStackSocket", true].Update(AllowDualStackSocket);
settings["DynamicClientEndPoints", true].Update(m_dynamicClientEndPoints);
settings["MaxSendQueueSize", true].Update(MaxSendQueueSize);
config.Save();
}
///
/// Loads saved settings from the config file if the property is set to true.
///
public override void LoadSettings()
{
base.LoadSettings();
if (!PersistSettings)
return;
// Load settings from the specified category.
ConfigurationFile config = ConfigurationFile.Current;
CategorizedSettingsElementCollection settings = config.Settings[SettingsCategory];
settings.Add("IdentifyClientsBy", ClientIdentificationMode, "Specifies how to identify clients when receiving data. Can be IP, Port, or EndPoint.");
settings.Add("AllowDualStackSocket", AllowDualStackSocket, "True if dual-mode socket is allowed when IP address is IPv6, otherwise False.");
settings.Add("DynamicClientEndPoints", m_dynamicClientEndPoints, "True if UDP server sends data to the same port it receives data from.");
settings.Add("MaxSendQueueSize", MaxSendQueueSize, "The maximum size of the send queue before payloads are dumped from the queue.");
ClientIdentificationMode = settings["IdentifyClientsBy"].ValueAs(ClientIdentificationMode);
AllowDualStackSocket = settings["AllowDualStackSocket"].ValueAs(AllowDualStackSocket);
DynamicClientEndPoints = settings["DynamicClientEndPoints"].ValueAs(m_dynamicClientEndPoints);
MaxSendQueueSize = settings["MaxSendQueueSize"].ValueAs(MaxSendQueueSize);
}
///
/// Stops the synchronously and disconnects all connected clients.
///
public override void Stop()
{
if (CurrentState != ServerState.Running)
return;
try
{
// Disconnect all clients.
DisconnectAll();
m_udpServer.Reset();
OnServerStopped();
}
finally
{
if (m_receiveArgs != null)
{
m_receiveArgs.Dispose();
m_receiveArgs = null;
}
}
}
///
/// Starts the synchronously and begins accepting client connections asynchronously.
///
/// Attempt is made to the when it is running.
public override void Start()
{
if (CurrentState != ServerState.NotRunning)
throw new InvalidOperationException("Server is currently running");
// Initialize if uninitialized
if (!Initialized)
Initialize();
// Overwrite config file if client identification mode exists in connection string.
if (m_configData.ContainsKey("identifyClientsBy") && Enum.TryParse(m_configData["identifyClientsBy"], true, out ClientIdentificationMode clientIdentificationMode))
ClientIdentificationMode = clientIdentificationMode;
// Overwrite config file if client end points are dynamic
if (m_configData.ContainsKey("dynamicClientEndPoints"))
m_dynamicClientEndPoints = m_configData["dynamicClientEndPoints"].ParseBoolean();
// Overwrite config file if max send queue size exists in connection string.
if (m_configData.ContainsKey("maxSendQueueSize") && int.TryParse(m_configData["maxSendQueueSize"], out int maxSendQueueSize))
MaxSendQueueSize = maxSendQueueSize;
// Bind server socket to local end-point
m_udpServer = new TransportProvider();
m_udpServer.SetReceiveBuffer(ReceiveBufferSize);
m_udpServer.Provider = Transport.CreateSocket(m_configData["interface"], int.Parse(m_configData["port"]), ProtocolType.Udp, m_ipStack, AllowDualStackSocket);
m_udpServer.Provider.ReceiveBufferSize = ReceiveBufferSize;
// Disable SocketError.ConnectionReset exception from being thrown when the endpoint is not listening
// Fixes MONO issue with SIO_UDP_CONNRESET
try
{
m_udpServer.Provider.IOControl(SIO_UDP_CONNRESET, new[] { Convert.ToByte(false) }, null);
}
catch (Exception ex)
{
Logger.SwallowException(ex, "Failed to set SIO_UDP_CONNRESET operating mode on UDP socket");
}
// Notify that the server has been started successfully
OnServerStarted();
if (m_udpServer.Provider.LocalEndPoint != null)
{
m_receiveArgs = FastObjectFactory.CreateObjectFunction();
m_receiveArgs.SocketFlags = SocketFlags.None;
m_receiveArgs.Completed += m_receiveHandler;
ReceivePayloadAsync(m_receiveArgs);
}
// Determine whether we have a static or dynamic client list
m_dynamicClientList = !m_configData.ContainsKey("clients");
if (m_dynamicClientList)
{
m_dynamicClientEndPoints = true;
}
else
{
// We process the static list of clients.
foreach (string clientString in m_configData["clients"].Replace(" ", "").Split(','))
{
try
{
Match endpoint = Regex.Match(clientString, Transport.EndpointFormatRegex);
if (endpoint != Match.Empty)
AddUdpClient(endpoint.Groups["host"].Value, int.Parse(endpoint.Groups["port"].Value));
else if (int.TryParse(clientString, out int port))
AddUdpClient(null, port);
else
AddUdpClient(clientString, 0);
}
catch (Exception ex)
{
string errorMessage = $"Unable to connect to client {clientString}: {ex.Message}";
OnClientConnectingException(new Exception(errorMessage, ex));
}
}
}
}
///
/// Disconnects the specified connected client.
///
/// ID of the client to be disconnected.
/// Client does not exist for the specified .
public override void DisconnectOne(Guid clientID)
{
UdpClientInfo clientInfo = null;
TransportProvider client = null;
try
{
if (!m_clientInfoLookup.TryRemove(clientID, out clientInfo))
return;
client = clientInfo.Client;
if (client.Provider != null)
{
// If the IP specified for the client is a multicast IP, unsubscribe from the specified multicast group.
IPEndPoint clientEndpoint = (IPEndPoint)client.Provider;
if (Transport.IsMulticastIP(clientEndpoint.Address))
{
if (client.MulticastMembershipAddresses != null)
{
// Execute multicast unsubscribe for specific source
m_udpServer.Provider.SetSocketOption(clientEndpoint.AddressFamily == AddressFamily.InterNetworkV6 ? SocketOptionLevel.IPv6 : SocketOptionLevel.IP, SocketOptionName.DropSourceMembership, client.MulticastMembershipAddresses);
}
else
{
// Execute multicast unsubscribe for any source
m_udpServer.Provider.SetSocketOption(clientEndpoint.AddressFamily == AddressFamily.InterNetworkV6 ? SocketOptionLevel.IPv6 : SocketOptionLevel.IP, SocketOptionName.DropMembership, new MulticastOption(clientEndpoint.Address));
}
}
}
}
catch (Exception ex)
{
if (client != null)
OnSendClientDataException(client.ID, new InvalidOperationException($"Failed to drop multicast membership: {ex.Message}", ex));
else
Logger.SwallowException(ex, "UdpServer.cs: The client state was null while attempting to drop multicast membership");
}
finally
{
clientInfo?.SendArgs?.Dispose();
}
client?.Reset();
OnClientDisconnected(clientID);
}
///
/// Gets the object associated with the specified client ID.
///
/// ID of the client.
/// The UDP client.
/// An object.
/// Client does not exist for the specified .
public bool TryGetClient(Guid clientID, out TransportProvider udpClient)
{
bool clientExists = m_clientInfoLookup.TryGetValue(clientID, out UdpClientInfo clientInfo);
udpClient = clientExists ? clientInfo.Client : null;
return clientExists;
}
///
/// Validates the specified .
///
/// Configuration string to be validated.
/// Port property is missing.
/// Port property value is not between and .
protected override void ValidateConfigurationString(string configurationString)
{
m_configData = configurationString.ParseKeyValuePairs();
// Derive desired IP stack based on specified "interface" setting, adding setting if it's not defined
m_ipStack = Transport.GetInterfaceIPStack(m_configData);
if (!m_configData.ContainsKey("port"))
throw new ArgumentException($"Port is missing (Example: {DefaultConfigurationString})");
if (!Transport.IsPortNumberValid(m_configData["port"]) && int.Parse(m_configData["port"]) != -1)
throw new ArgumentOutOfRangeException(nameof(configurationString), $"Port number must be {-1} or between {Transport.PortRangeLow} and {Transport.PortRangeHigh}");
if (!m_configData.ContainsKey("multicastTimeToLive"))
m_configData.Add("multicastTimeToLive", "10");
// Make sure a valid multi-cast time-to-live value is defined in the configuration data
if (!(m_configData.TryGetValue("multicastTimeToLive", out string setting) && int.TryParse(setting, out int _)))
m_configData["multicastTimeToLive"] = "10";
}
///
/// Sends data to the specified client asynchronously.
///
/// ID of the client to which the data is to be sent.
/// The buffer that contains the binary data to be sent.
/// The zero-based position in the at which to begin sending data.
/// The number of bytes to be sent from starting at the .
/// for the asynchronous operation.
protected override WaitHandle SendDataToAsync(Guid clientID, byte[] data, int offset, int length)
{
if (!m_clientInfoLookup.TryGetValue(clientID, out UdpClientInfo clientInfo))
throw new InvalidOperationException($"No client found for ID {clientID}.");
ConcurrentQueue sendQueue = clientInfo.SendQueue;
// Execute operation to see if the client has reached the maximum send queue size.
clientInfo.DumpPayloadsOperation.TryRun();
// Create payload and wait handle.
UdpServerPayload payload = FastObjectFactory.CreateObjectFunction();
ManualResetEventSlim handle = FastObjectFactory.CreateObjectFunction();
payload.Data = data;
payload.Offset = offset;
payload.Length = length;
payload.WaitHandle = handle;
payload.ClientInfo = clientInfo;
handle.Reset();
// Queue payload for sending.
sendQueue.Enqueue(payload);
lock (clientInfo.SendLock)
{
// Send next queued payload.
if (Interlocked.CompareExchange(ref clientInfo.Sending, 1, 0) == 0)
{
if (sendQueue.TryDequeue(out UdpServerPayload dequeuedPayload))
ThreadPool.QueueUserWorkItem(state => SendPayload((UdpServerPayload)state), dequeuedPayload);
else
Interlocked.Exchange(ref clientInfo.Sending, 0);
}
}
// Notify that the send operation has started.
OnSendClientDataStart(clientID);
// Return the async handle that can be used to wait for the async operation to complete.
return handle.WaitHandle;
}
///
/// Raises the event.
///
/// ID of client to send to event.
/// Exception to send to event.
protected override void OnSendClientDataException(Guid clientID, Exception ex)
{
if (m_clientInfoLookup.ContainsKey(clientID) && CurrentState == ServerState.Running)
base.OnSendClientDataException(clientID, ex);
else
Logger.SwallowException(ex, "UdpServer.cs: The client was not found or server state is not running");
}
///
/// Raises the event.
///
/// ID of client to send to event.
/// Exception to send to event.
protected override void OnReceiveClientDataException(Guid clientID, Exception ex)
{
if (m_clientInfoLookup.ContainsKey(clientID) && CurrentState == ServerState.Running)
base.OnReceiveClientDataException(clientID, ex);
else
Logger.SwallowException(ex, "UdpServer.cs: The client was not found or server state is not running");
}
private void AddUdpClient(string host, int port) => AddUdpClient(Transport.CreateEndPoint(host, port, m_ipStack));
private TransportProvider AddUdpClient(EndPoint udpClientEndPoint)
{
TransportProvider udpClient = new TransportProvider();
IPEndPoint udpClientIPEndPoint = udpClientEndPoint as IPEndPoint;
// Set up client
udpClient.SetReceiveBuffer(ReceiveBufferSize);
udpClient.SetSendBuffer(SendBufferSize);
udpClient.Provider = udpClientIPEndPoint;
// If the IP specified for the client is a multicast IP, subscribe to the specified multicast group.
if (udpClientIPEndPoint != null && Transport.IsMulticastIP(udpClientIPEndPoint.Address))
{
SocketOptionLevel level = udpClientIPEndPoint.AddressFamily == AddressFamily.InterNetworkV6 ? SocketOptionLevel.IPv6 : SocketOptionLevel.IP;
if (m_configData.TryGetValue("multicastSource", out string multicastSource))
{
IPAddress sourceAddress = IPAddress.Parse(multicastSource);
IPAddress localAddress = udpClientIPEndPoint.AddressFamily == AddressFamily.InterNetworkV6 ? IPAddress.IPv6Any : IPAddress.Any;
if (sourceAddress.AddressFamily != udpClientIPEndPoint.AddressFamily)
throw new InvalidOperationException($"Source address \"{sourceAddress}\" is not in the same IP format as server address \"{udpClientIPEndPoint.Address}\"");
if (localAddress.AddressFamily != udpClientIPEndPoint.AddressFamily)
throw new InvalidOperationException($"Local address \"{localAddress}\" is not in the same IP format as server address \"{udpClientIPEndPoint.Address}\"");
using (BlockAllocatedMemoryStream membershipAddresses = new BlockAllocatedMemoryStream())
{
byte[] serverAddressBytes = udpClientIPEndPoint.Address.GetAddressBytes();
byte[] sourceAddressBytes = sourceAddress.GetAddressBytes();
byte[] localAddressBytes = localAddress.GetAddressBytes();
membershipAddresses.Write(serverAddressBytes, 0, serverAddressBytes.Length);
membershipAddresses.Write(sourceAddressBytes, 0, sourceAddressBytes.Length);
membershipAddresses.Write(localAddressBytes, 0, localAddressBytes.Length);
udpClient.MulticastMembershipAddresses = membershipAddresses.ToArray();
}
// Execute multicast subscribe for specific source
m_udpServer.Provider.SetSocketOption(level, SocketOptionName.AddSourceMembership, udpClient.MulticastMembershipAddresses);
m_udpServer.Provider.SetSocketOption(level, SocketOptionName.MulticastTimeToLive, int.Parse(m_configData["multicastTimeToLive"]));
}
else
{
// Execute multicast subscribe for any source
m_udpServer.Provider.SetSocketOption(level, SocketOptionName.AddMembership, new MulticastOption(udpClientIPEndPoint.Address));
m_udpServer.Provider.SetSocketOption(level, SocketOptionName.MulticastTimeToLive, int.Parse(m_configData["multicastTimeToLive"]));
}
}
// Create client info object
UdpClientInfo udpClientInfo = new UdpClientInfo
{
Client = udpClient,
SendArgs = FastObjectFactory.CreateObjectFunction(),
SendLock = new object(),
SendQueue = new ConcurrentQueue()
};
udpClientInfo.DumpPayloadsOperation = new ShortSynchronizedOperation(() =>
{
// Check to see if the client has reached the maximum send queue size.
if (MaxSendQueueSize > 0 && udpClientInfo.SendQueue.Count >= MaxSendQueueSize)
{
for (int i = 0; i < MaxSendQueueSize; i++)
{
if (udpClientInfo.SendQueue.TryDequeue(out UdpServerPayload payload))
{
payload.WaitHandle.Set();
payload.WaitHandle.Dispose();
payload.WaitHandle = null;
}
}
throw new InvalidOperationException($"Client {udpClientInfo.Client.ID} connected to UDP server reached maximum send queue size. {MaxSendQueueSize} payloads dumped from the queue.");
}
}, ex => OnSendClientDataException(udpClientInfo.Client.ID, ex));
// Set up SocketAsyncEventArgs
udpClientInfo.SendArgs.RemoteEndPoint = udpClient.Provider;
udpClientInfo.SendArgs.SetBuffer(udpClient.SendBuffer, 0, udpClient.SendBufferSize);
udpClientInfo.SendArgs.Completed += m_sendHandler;
// Add new client to the lookup
m_clientInfoLookup.TryAdd(udpClient.ID, udpClientInfo);
OnClientConnected(udpClient.ID);
return udpClient;
}
///
/// Loops waiting for payloads and sends them on the socket.
///
private void SendPayload(UdpServerPayload payload)
{
UdpClientInfo clientInfo = null;
TransportProvider client = null;
//ManualResetEventSlim handle;
try
{
clientInfo = payload.ClientInfo;
client = clientInfo.Client;
SocketAsyncEventArgs args = clientInfo.SendArgs;
//handle = payload.WaitHandle;
args.UserToken = payload;
// Copy payload into send buffer.
int copyLength = Math.Min(payload.Length, client.SendBufferSize);
Buffer.BlockCopy(payload.Data, payload.Offset, client.SendBuffer, 0, copyLength);
// Set buffer and user token of send args.
args.SetBuffer(0, copyLength);
// Update offset and length.
payload.Offset += copyLength;
payload.Length -= copyLength;
// Send data over socket.
if (!m_udpServer.Provider.SendToAsync(args))
ProcessSend(args);
}
catch (Exception ex)
{
if (client != null)
OnSendClientDataException(client.ID, ex);
else
Logger.SwallowException(ex, "UdpServer.cs: The client state was null while attempting send payload data async");
if (clientInfo != null)
{
// Assume process send was not able
// to continue the asynchronous loop.
Interlocked.Exchange(ref clientInfo.Sending, 0);
}
}
}
///
/// Callback method for asynchronous send operation.
///
private void ProcessSend(SocketAsyncEventArgs args)
{
UdpServerPayload payload = null;
UdpClientInfo clientInfo = null;
TransportProvider client = null;
ConcurrentQueue sendQueue = null;
try
{
payload = (UdpServerPayload)args.UserToken;
clientInfo = payload.ClientInfo;
client = clientInfo.Client;
sendQueue = clientInfo.SendQueue;
ManualResetEventSlim handle = payload.WaitHandle;
// Determine whether we are finished with this
// payload and, if so, set the wait handle.
if (payload.Length <= 0)
handle.Set();
// Check for errors during send operation.
if (args.SocketError != SocketError.Success)
throw new SocketException((int)args.SocketError);
// Update statistics on the client.
client.Statistics.UpdateBytesSent(args.BytesTransferred);
// Send operation is complete.
if (payload.Length <= 0)
OnSendClientDataComplete(client.ID);
}
catch (Exception ex)
{
// Send operation failed to complete.
if (client != null)
OnSendClientDataException(client.ID, ex);
else
Logger.SwallowException(ex, "UdpServer.cs: The client state was null during post process handling of sent payload data");
}
finally
{
if (payload != null)
{
try
{
if (payload.Length > 0)
{
// Still more to send for this payload.
ThreadPool.QueueUserWorkItem(state => SendPayload((UdpServerPayload)state), payload);
}
else if (sendQueue != null)
{
payload.WaitHandle = null;
payload.ClientInfo = null;
// Begin sending next client payload.
if (sendQueue.TryDequeue(out payload))
{
ThreadPool.QueueUserWorkItem(state => SendPayload((UdpServerPayload)state), payload);
}
else if (clientInfo != null)
{
lock (clientInfo.SendLock)
{
if (sendQueue.TryDequeue(out payload))
ThreadPool.QueueUserWorkItem(state => SendPayload((UdpServerPayload)state), payload);
else
Interlocked.Exchange(ref clientInfo.Sending, 0);
}
}
}
}
catch (Exception ex)
{
string errorMessage = $"Exception encountered while attempting to send next payload: {ex.Message}";
if (client != null)
OnSendClientDataException(client.ID, new Exception(errorMessage, ex));
else
Logger.SwallowException(ex, "UdpServer.cs: The client state was null during post process handling of sent payload data");
if (clientInfo != null)
Interlocked.Exchange(ref clientInfo.Sending, 0);
}
}
}
}
///
/// Initiate method for asynchronous receive operation of payload data from any endpoint.
///
private void ReceivePayloadAsync(SocketAsyncEventArgs args)
{
// Attempt to receive data on the socket.
byte[] buffer = m_udpServer.ReceiveBuffer;
int length = m_udpServer.ReceiveBufferSize;
args.SetBuffer(buffer, 0, length);
args.RemoteEndPoint = m_udpServer.Provider.LocalEndPoint;
if (!m_udpServer.Provider.ReceiveFromAsync(args))
ThreadPool.QueueUserWorkItem(state => ProcessReceive((SocketAsyncEventArgs)state), args);
}
///
/// Callback method for asynchronous receive operation of payload data from any endpoint.
///
private void ProcessReceive(SocketAsyncEventArgs args)
{
Guid clientID = default(Guid);
try
{
if (args.SocketError != SocketError.Success)
throw new SocketException((int)args.SocketError);
// Update statistics and pointers.
m_udpServer.Statistics.UpdateBytesReceived(args.BytesTransferred);
m_udpServer.BytesReceived = args.BytesTransferred;
// Search connected clients for a client connected to the end-point from where this data is received.
TransportProvider client = IdentifyClient(args.RemoteEndPoint);
// If the client's endpoint has changed, update the lookup list
if (m_dynamicClientEndPoints && client != null && !client.Provider.Equals(args.RemoteEndPoint))
{
client.Provider = args.RemoteEndPoint;
if (m_clientInfoLookup.TryGetValue(client.ID, out UdpClientInfo clientInfo))
clientInfo.SendArgs.RemoteEndPoint = client.Provider;
}
// If we do not have a static clients list, and if the client could not be found
// or if the client's endpoint has changed, update the clients list dynamically.
if (m_dynamicClientList && client == null)
client = AddUdpClient(args.RemoteEndPoint);
if (client != null)
{
// Notify client of data.
clientID = client.ID;
client.Statistics.UpdateBytesReceived(args.BytesTransferred);
OnReceiveClientDataComplete(client.ID, m_udpServer.ReceiveBuffer, m_udpServer.BytesReceived);
}
// Resume receive operation on the server socket.
ReceivePayloadAsync(args);
}
catch (Exception ex)
{
OnReceiveClientDataException(clientID, ex);
}
}
private TransportProvider IdentifyClient(EndPoint remoteEndPoint)
{
IPEndPoint remoteIPEndPoint = remoteEndPoint as IPEndPoint;
foreach (TransportProvider client in m_clientInfoLookup.Values.Select(clientInfo => clientInfo.Client))
{
IPEndPoint clientIPEndPoint = client.Provider as IPEndPoint;
switch (ClientIdentificationMode)
{
case ClientIdentificationMode.IP:
if (remoteIPEndPoint != null && clientIPEndPoint != null)
{
if (remoteIPEndPoint.Address.Equals(clientIPEndPoint.Address))
return client;
}
break;
case ClientIdentificationMode.Port:
if (remoteIPEndPoint != null && clientIPEndPoint != null)
{
if (remoteIPEndPoint.Port == clientIPEndPoint.Port)
return client;
}
break;
case ClientIdentificationMode.EndPoint:
if (remoteEndPoint.Equals(client.Provider))
return client;
break;
}
}
return null;
}
#endregion
}
}