1077 lines · 48.4 KB · C#
RawDownload
//******************************************************************************************************//  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{    /// <summary>    /// Defines modes by which the UDP server will identify its clients when receiving messages.    /// </summary>    public enum ClientIdentificationMode    {        /// <summary>        /// Identify clients by their IP address.        /// </summary>        IP,         /// <summary>        /// Identify clients by the port they are bound to.        /// </summary>        Port,         /// <summary>        /// Identify clients by both their IP address and the port they are bound to.        /// </summary>        EndPoint    }     /// <summary>    /// Represents a UDP-based communication server.    /// </summary>    /// <remarks>    /// <para>    /// Use <see cref="UdpServer"/> when the primary purpose is to transmit data.    /// </para>    /// <para>    /// The <see cref="UdpServer.Server"/> socket can be bound to a specified interface on a machine with multiple interfaces by     /// specifying the interface in the <see cref="ServerBase.ConfigurationString"/> (Example: "Port=8888; Clients=localhost:8989; Interface=127.0.0.1")    /// </para>    /// <para>    /// The <see cref="UdpServer.Server"/> socket can be used just for transmitting data without being bound to a local interface     /// by specifying -1 for the port number in the <see cref="ServerBase.ConfigurationString"/> (Example: "Port=-1; Clients=localhost:8989")    /// </para>    /// </remarks>    /// <example>    /// This example shows how to use the <see cref="UdpServer"/> component:    /// <code>    /// 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&lt;Guid&gt; e)    ///     {    ///         Console.WriteLine(string.Format("Client connected - {0}.", e.Argument));    ///     }    ///     ///     static void m_server_ClientDisconnected(object sender, EventArgs&lt;Guid&gt; e)    ///     {    ///         Console.WriteLine(string.Format("Client disconnected - {0}.", e.Argument));    ///     }    ///     ///     static void m_server_ReceiveClientDataComplete(object sender, EventArgs&lt;Guid, byte[], int&gt; e)    ///     {    ///         Console.WriteLine(string.Format("Received data from {0} - {1}.", e.Argument1, m_server.TextEncoding.GetString(e.Argument2, 0, e.Argument3)));    ///     }    /// }    /// </code>    /// </example>    public class UdpServer : ServerBase    {        #region [ Members ]         // Nested Types        private class UdpClientInfo        {            public TransportProvider<EndPoint> Client;            public SocketAsyncEventArgs SendArgs;            public object SendLock;            public ConcurrentQueue<UdpServerPayload> 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         /// <summary>        /// Specifies the default value for the <see cref="ClientIdentificationMode"/> property.        /// </summary>        public const ClientIdentificationMode DefaultClientIdentificationMode = ClientIdentificationMode.EndPoint;         /// <summary>        /// Specifies the default value for the <see cref="AllowDualStackSocket"/> property.        /// </summary>        public const bool DefaultAllowDualStackSocket = true;         /// <summary>        /// Specifies the default value for the <see cref="DynamicClientEndPoints"/> property.        /// </summary>        public const bool DefaultDynamicClientEndPoints = false;         /// <summary>        /// Specifies the default value for the <see cref="MaxSendQueueSize"/> property.        /// </summary>        public const int DefaultMaxSendQueueSize = 500000;         /// <summary>        /// Specifies the default value for the <see cref="ServerBase.ConfigurationString"/> property.        /// </summary>        public const string DefaultConfigurationString = "Port=8888; Clients=localhost:8989";         /// <summary>        /// Specifies the constant to be used for disabling <see cref="SocketError.ConnectionReset"/> when endpoint is not listening.        /// </summary>        private const int SIO_UDP_CONNRESET = -1744830452;         // Fields        private TransportProvider<Socket> m_udpServer;        private SocketAsyncEventArgs m_receiveArgs;        private readonly ConcurrentDictionary<Guid, UdpClientInfo> m_clientInfoLookup;        private IPStack m_ipStack;        private bool m_dynamicClientEndPoints;        private bool m_dynamicClientList;        private Dictionary<string, string> m_configData;         private readonly EventHandler<SocketAsyncEventArgs> m_sendHandler;        private readonly EventHandler<SocketAsyncEventArgs> m_receiveHandler;         #endregion         #region [ Constructors ]         /// <summary>        /// Initializes a new instance of the <see cref="UdpServer"/> class.        /// </summary>        public UdpServer() : this(DefaultConfigurationString)        {        }         /// <summary>        /// Initializes a new instance of the <see cref="UdpServer"/> class.        /// </summary>        /// <param name="configString">Configuration string of the <see cref="UdpServer"/>. See <see cref="DefaultConfigurationString"/> for format.</param>        public UdpServer(string configString) : base(TransportProtocol.Udp, configString)        {            ClientIdentificationMode = DefaultClientIdentificationMode;            AllowDualStackSocket = DefaultAllowDualStackSocket;            MaxSendQueueSize = DefaultMaxSendQueueSize;            m_clientInfoLookup = new ConcurrentDictionary<Guid, UdpClientInfo>();             m_sendHandler = (sender, args) => ProcessSend(args);            m_receiveHandler = (sender, args) => ProcessReceive(args);        }         /// <summary>        /// Initializes a new instance of the <see cref="UdpServer"/> class.        /// </summary>        /// <param name="container"><see cref="IContainer"/> object that contains the <see cref="UdpServer"/>.</param>        public UdpServer(IContainer container) : this() => container?.Add(this);         #endregion         #region [ Properties ]         /// <summary>        /// Gets or sets the mode by which the UDP server will identify its clients when receiving messages.        /// </summary>        [Category("Settings")]        [DefaultValue(DefaultClientIdentificationMode)]        [Description("Mode by which the UDP server will identify its clients when receiving messages.")]        public ClientIdentificationMode ClientIdentificationMode { get; set; }         /// <summary>        /// Gets or sets a boolean value that determines if dual-mode socket is allowed when endpoint address is IPv6.        /// </summary>        [Category("Settings")]        [DefaultValue(DefaultAllowDualStackSocket)]        [Description("Determines if dual-mode socket is allowed when endpoint address is IPv6.")]        public bool AllowDualStackSocket { get; set; }         /// <summary>        /// 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.        /// </summary>        [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;            }        }         /// <summary>        /// Gets or sets the maximum size for the send queue before payloads are dumped from the queue.        /// </summary>        [Category("Settings")]        [DefaultValue(DefaultMaxSendQueueSize)]        [Description("The maximum size for the send queue before payloads are dumped from the queue.")]        public int MaxSendQueueSize { get; set; }         /// <summary>        /// Gets the <see cref="Socket"/> object for the <see cref="UdpServer"/>.        /// </summary>        [Browsable(false)]        public Socket Server => m_udpServer.Provider;         /// <summary>        /// Gets the descriptive status of the client.        /// </summary>        public override string Status        {            get            {                StringBuilder statusBuilder = new StringBuilder(base.Status);                int count = 0;                 foreach (ConcurrentQueue<UdpServerPayload> 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 ]         /// <summary>        /// Reads a number of bytes from the current received data buffer and writes those bytes into a byte array at the specified offset.        /// </summary>        /// <param name="clientID">ID of the client from which data buffer should be read.</param>        /// <param name="buffer">Destination buffer used to hold copied bytes.</param>        /// <param name="startIndex">0-based starting index into destination <paramref name="buffer"/> to begin writing data.</param>        /// <param name="length">The number of bytes to read from current received data buffer and write into <paramref name="buffer"/>.</param>        /// <returns>The number of bytes read.</returns>        /// <remarks>        /// This function should only be called from within the <see cref="ServerBase.ReceiveClientData"/> event handler. Calling this method        /// outside this event will have unexpected results.        /// </remarks>        /// <exception cref="InvalidOperationException">        /// No received data buffer has been defined to read -or-        /// Specified <paramref name="clientID"/> does not exist, cannot read buffer.        /// </exception>        /// <exception cref="ArgumentNullException"><paramref name="buffer"/> is null.</exception>        /// <exception cref="ArgumentOutOfRangeException">        /// <paramref name="startIndex"/> or <paramref name="length"/> is less than 0 -or-         /// <paramref name="startIndex"/> and <paramref name="length"/> will exceed <paramref name="buffer"/> length.        /// </exception>        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<EndPoint> 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;        }         /// <summary>        /// Saves <see cref="TcpServer"/> settings to the config file if the <see cref="ServerBase.PersistSettings"/> property is set to true.        /// </summary>        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();        }         /// <summary>        /// Loads saved <see cref="TcpServer"/> settings from the config file if the <see cref="ServerBase.PersistSettings"/> property is set to true.        /// </summary>        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);        }         /// <summary>        /// Stops the <see cref="UdpServer"/> synchronously and disconnects all connected clients.        /// </summary>        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;                }            }        }         /// <summary>        /// Starts the <see cref="UdpServer"/> synchronously and begins accepting client connections asynchronously.        /// </summary>        /// <exception cref="InvalidOperationException">Attempt is made to <see cref="Start()"/> the <see cref="UdpServer"/> when it is running.</exception>        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))

Lines 1–500 of 1077.

Keyboard shortcuts

General

Show keyboard shortcuts
?
Focus the current search
/

Issues and pull requests

Open filters
f

Issues, pull requests and Inbox

Select next item
j↓
Select previous item
k↑
Open selected item
Enter
Dismiss filters or clear selection
Esc

Repository code

Go to file
t