1237 lines · 53.8 KB · C#
RawDownload
//******************************************************************************************************//  TcpServer.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://  ----------------------------------------------------------------------------------------------------//  06/02/2006 - Pinal C. Patel//       Original version of source code generated.//  09/06/2006 - J. Ritchie Carroll//       Added bypass optimizations for high-speed socket access.//  12/01/2006 - Pinal C. Patel//       Modified code for handling "PayloadAware" transmissions.//  01/28/3008 - J. Ritchie Carroll//       Placed accepted TCP socket connections on their own threads instead of thread pool.//  09/29/2008 - J. Ritchie Carroll//       Converted to C#.//  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//       Added null reference check to DisconnectOne() for safety.//  02/11/2011 - Pinal C. Patel//       Added IntegratedSecurity property to enable integrated windows authentication.//  09/21/2011 - J. Ritchie Carroll//       Added Mono implementation exception regions.//  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.////****************************************************************************************************** using System;using System.Collections.Concurrent;using System.Collections.Generic;using System.ComponentModel;using System.Diagnostics.CodeAnalysis;using System.Linq;using System.Net;using System.Net.Sockets;using System.Security.Principal;using System.Text;using System.Threading;#if !MONOusing System.Net.Security;using System.Security.Authentication;#endifusing GSF.Configuration;using GSF.Threading; namespace GSF.Communication{    /// <summary>    /// Represents a TCP-based communication server.    /// </summary>    /// <remarks>    /// The <see cref="TcpServer.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; Interface=127.0.0.1")    /// </remarks>    /// <example>    /// This example shows how to use the <see cref="TcpServer"/> component:    /// <code>    /// using System;    /// using GSF;    /// using GSF.Communication;    /// using GSF.Security.Cryptography;    /// using GSF.IO.Compression;    ///     /// class Program    /// {    ///     static TcpServer m_server;    ///     ///     static void Main(string[] args)    ///     {    ///         // Initialize the server.    ///         m_server = new TcpServer("Port=8888");    ///         m_server.Handshake = false;    ///         m_server.PayloadAware = 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 TcpServer : ServerBase    {        #region [ Members ]         // Nested Types        private class TcpClientInfo        {            public TransportProvider<Socket> Client;            public SocketAsyncEventArgs SendArgs;            public object SendLock;            public ConcurrentQueue<TcpServerPayload> SendQueue;            public ShortSynchronizedOperation DumpPayloadsOperation;            public int Sending;             public WindowsPrincipal ClientPrincipal;        }         private class TcpServerPayload        {            // Per payload state            public byte[] Data;            public int Offset;            public int Length;            public ManualResetEventSlim WaitHandle;             // Per client state            public TcpClientInfo ClientInfo;        }         // Constants         /// <summary>        /// Specifies the default value for the <see cref="PayloadAware"/> property.        /// </summary>        public const bool DefaultPayloadAware = false;         /// <summary>        /// Specifies the default value for the <see cref="IntegratedSecurity"/> property.        /// </summary>        public const bool DefaultIntegratedSecurity = false;         /// <summary>        /// Specifies the default value for the <see cref="IgnoreInvalidCredentials"/> property.        /// </summary>        public const bool DefaultIgnoreInvalidCredentials = false;         /// <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="MaxSendQueueSize"/> property.        /// </summary>        public const int DefaultMaxSendQueueSize = 500000;         /// <summary>        /// Specifies the default value for the <see cref="NoDelay"/> property.        /// </summary>        public const bool DefaultNoDelay = false;         /// <summary>        /// Specifies the default value for the <see cref="ServerBase.ConfigurationString"/> property.        /// </summary>        public const string DefaultConfigurationString = "Port=8888";         // Fields        private byte[] m_payloadMarker;        private EndianOrder m_payloadEndianOrder;        private IPStack m_ipStack;        private SocketAsyncEventArgs m_acceptArgs;        private readonly ConcurrentDictionary<Guid, TcpClientInfo> m_clientInfoLookup;        private Dictionary<string, string> m_configData;         private readonly EventHandler<SocketAsyncEventArgs> m_acceptHandler;        private readonly EventHandler<SocketAsyncEventArgs> m_sendHandler;        private readonly EventHandler<SocketAsyncEventArgs> m_receivePayloadAwareHandler;        private readonly EventHandler<SocketAsyncEventArgs> m_receivePayloadUnawareHandler;         #endregion         #region [ Constructors ]         /// <summary>        /// Initializes a new instance of the <see cref="TcpServer"/> class.        /// </summary>        public TcpServer() : this(DefaultConfigurationString)        {        }         /// <summary>        /// Initializes a new instance of the <see cref="TcpServer"/> class.        /// </summary>        /// <param name="configString">Config string of the <see cref="TcpServer"/>. See <see cref="DefaultConfigurationString"/> for format.</param>        public TcpServer(string configString) : base(TransportProtocol.Tcp, configString)        {            PayloadAware = DefaultPayloadAware;            m_payloadMarker = Payload.DefaultMarker;            m_payloadEndianOrder = EndianOrder.LittleEndian;            IntegratedSecurity = DefaultIntegratedSecurity;            IgnoreInvalidCredentials = DefaultIgnoreInvalidCredentials;            AllowDualStackSocket = DefaultAllowDualStackSocket;            MaxSendQueueSize = DefaultMaxSendQueueSize;            NoDelay = DefaultNoDelay;            m_clientInfoLookup = new ConcurrentDictionary<Guid, TcpClientInfo>();             m_acceptHandler = (sender, args) => ProcessAccept(args);            m_sendHandler = (sender, args) => ProcessSend(args);            m_receivePayloadAwareHandler = (sender, args) => ProcessReceivePayloadAware(args);            m_receivePayloadUnawareHandler = (sender, args) => ProcessReceivePayloadUnaware(args);        }         /// <summary>        /// Initializes a new instance of the <see cref="TcpServer"/> class.        /// </summary>        /// <param name="container"><see cref="IContainer"/> object that contains the <see cref="TcpServer"/>.</param>        public TcpServer(IContainer container) : this() => container?.Add(this);         #endregion         #region [ Properties ]         /// <summary>        /// Gets or sets a boolean value that indicates whether the payload boundaries are to be preserved during transmission.        /// </summary>        [Category("Data")]        [DefaultValue(DefaultPayloadAware)]        [Description("Indicates whether the payload boundaries are to be preserved during transmission.")]        public bool PayloadAware { get; set; }         /// <summary>        /// Gets or sets the byte sequence used to mark the beginning of a payload in a <see cref="PayloadAware"/> transmission.        /// </summary>        /// <remarks>        /// Setting property to <c>null</c> will create a zero-length payload marker.        /// </remarks>        [Browsable(false)]        [DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)]        public byte[] PayloadMarker        {            get => m_payloadMarker;            set => m_payloadMarker = value ?? Array.Empty<byte>();        }         /// <summary>        /// Gets or sets the endian order to apply for encoding and decoding payload size in a <see cref="PayloadAware"/> transmission.        /// </summary>        /// <remarks>        /// Setting property to <c>null</c> will force use of little-endian encoding.        /// </remarks>        [Browsable(false)]        [DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)]        public EndianOrder PayloadEndianOrder        {            get => m_payloadEndianOrder;            set => m_payloadEndianOrder = value ?? EndianOrder.LittleEndian;        }         /// <summary>        /// Gets or sets a boolean value that indicates whether the client Windows account credentials are used for authentication.        /// </summary>        /// <remarks>           /// This option is ignored under Mono deployments.        /// </remarks>        [Category("Security")]        [DefaultValue(DefaultIntegratedSecurity)]        [Description("Indicates whether the client Windows account credentials are used for authentication.")]        public bool IntegratedSecurity { get; set; }         /// <summary>        /// Gets or sets a boolean value that indicates whether the server        /// should ignore errors when the client's credentials are invalid.        /// </summary>        /// <remarks>        /// This property should only be set to true if there is an alternative by which        /// to authenticate the client when integrated security fails. When this is set        /// to true, if the client's credentials are invalid, the <see cref="TryGetClientPrincipal"/>        /// method will return true for that client, but the principal will still be null.        /// </remarks>        [Category("Security")]        [DefaultValue(DefaultIgnoreInvalidCredentials)]        [Description("Indicates whether the client Windows account credentials are validated during authentication.")]        public bool IgnoreInvalidCredentials { 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 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 or sets a boolean value that determines if small packets are delivered to the remote host without delay.        /// </summary>        [Category("Settings")]        [DefaultValue(DefaultNoDelay)]        [Description("Determines if small packets are delivered to the remote host without delay.")]        public bool NoDelay { get; set; }         /// <summary>        /// Gets the <see cref="Socket"/> object for the <see cref="TcpServer"/>.        /// </summary>        [Browsable(false)]        public Socket Server { get; private set; }         /// <summary>        /// Gets the receive handler used to handle data received on the connected sockets.        /// </summary>        private EventHandler<SocketAsyncEventArgs> ReceiveHandler => PayloadAware ? m_receivePayloadAwareHandler : m_receivePayloadUnawareHandler;         /// <summary>        /// Gets the descriptive status of the server.        /// </summary>        public override string Status        {            get            {                StringBuilder statusBuilder = new StringBuilder(base.Status);                int count = 0;                 foreach (ConcurrentQueue<TcpServerPayload> 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 TcpClientInfo clientInfo))                throw new InvalidOperationException("Specified client ID does not exist, cannot read buffer.");             TransportProvider<Socket> tcpClient = clientInfo.Client;             if (tcpClient.ReceiveBuffer == null)                throw new InvalidOperationException("No received data buffer has been defined to read.");             int readIndex = ReadIndicies[clientID];            int sourceLength = tcpClient.BytesReceived - readIndex;            int readBytes = length > sourceLength ? sourceLength : length;            Buffer.BlockCopy(tcpClient.ReceiveBuffer, readIndex, buffer, startIndex, readBytes);             // Update read index for next call            readIndex += readBytes;             if (readIndex >= tcpClient.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["PayloadAware", true].Update(PayloadAware);            settings["IntegratedSecurity", true].Update(IntegratedSecurity);            settings["AllowDualStackSocket", true].Update(AllowDualStackSocket);            settings["MaxSendQueueSize", true].Update(MaxSendQueueSize);            settings["NoDelay", true].Update(NoDelay);                        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("PayloadAware", PayloadAware, "True if payload boundaries are to be preserved during transmission, otherwise False.");            settings.Add("IntegratedSecurity", IntegratedSecurity, "True if the client Windows account credentials are used for authentication, otherwise False.");            settings.Add("AllowDualStackSocket", AllowDualStackSocket, "True if dual-mode socket is allowed when IP address is IPv6, otherwise False.");            settings.Add("MaxSendQueueSize", MaxSendQueueSize, "The maximum size of the send queue before payloads are dumped from the queue.");            settings.Add("NoDelay", NoDelay, "True to disable Nagle so that small packets are delivered to the remote host without delay, otherwise False.");                        PayloadAware = settings["PayloadAware"].ValueAs(PayloadAware);            IntegratedSecurity = settings["IntegratedSecurity"].ValueAs(IntegratedSecurity);            AllowDualStackSocket = settings["AllowDualStackSocket"].ValueAs(AllowDualStackSocket);            MaxSendQueueSize = settings["MaxSendQueueSize"].ValueAs(MaxSendQueueSize);            NoDelay = settings["NoDelay"].ValueAs(NoDelay);        }         /// <summary>        /// Stops the <see cref="TcpServer"/> synchronously and disconnects all connected clients.        /// </summary>        public override void Stop()        {            SocketAsyncEventArgs acceptArgs = m_acceptArgs;             m_acceptArgs = null;             if (CurrentState != ServerState.Running)                return;             DisconnectAll();   // Disconnection all clients.

Lines 1–500 of 1237.

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