//******************************************************************************************************// 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<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))); /// } /// } /// </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. Load more
//****************************************************************************************************** // 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 !MONO using System.Net.Security; using System.Security.Authentication; #endif using 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<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))); /// } /// } /// </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.