//******************************************************************************************************* // TcpServer.cs - Gbtc // // Tennessee Valley Authority, 2009 // No copyright is claimed pursuant to 17 USC § 105. All Other Rights Reserved. // // This software is made freely available under the TVA Open Source Agreement (see below). // Code in this file licensed to GSF under one or more contributor license agreements listed below. // // 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. // //******************************************************************************************************* #region [ TVA Open Source Agreement ] /* THIS OPEN SOURCE AGREEMENT ("AGREEMENT") DEFINES THE RIGHTS OF USE,REPRODUCTION, DISTRIBUTION, MODIFICATION AND REDISTRIBUTION OF CERTAIN COMPUTER SOFTWARE ORIGINALLY RELEASED BY THE TENNESSEE VALLEY AUTHORITY, A CORPORATE AGENCY AND INSTRUMENTALITY OF THE UNITED STATES GOVERNMENT ("GOVERNMENT AGENCY"). GOVERNMENT AGENCY IS AN INTENDED THIRD-PARTY BENEFICIARY OF ALL SUBSEQUENT DISTRIBUTIONS OR REDISTRIBUTIONS OF THE SUBJECT SOFTWARE. ANYONE WHO USES, REPRODUCES, DISTRIBUTES, MODIFIES OR REDISTRIBUTES THE SUBJECT SOFTWARE, AS DEFINED HEREIN, OR ANY PART THEREOF, IS, BY THAT ACTION, ACCEPTING IN FULL THE RESPONSIBILITIES AND OBLIGATIONS CONTAINED IN THIS AGREEMENT. Original Software Designation: openPDC Original Software Title: The GSF Open Source Phasor Data Concentrator User Registration Requested. Please Visit https://naspi.tva.com/Registration/ Point of Contact for Original Software: J. Ritchie Carroll 1. DEFINITIONS A. "Contributor" means Government Agency, as the developer of the Original Software, and any entity that makes a Modification. B. "Covered Patents" mean patent claims licensable by a Contributor that are necessarily infringed by the use or sale of its Modification alone or when combined with the Subject Software. C. "Display" means the showing of a copy of the Subject Software, either directly or by means of an image, or any other device. D. "Distribution" means conveyance or transfer of the Subject Software, regardless of means, to another. E. "Larger Work" means computer software that combines Subject Software, or portions thereof, with software separate from the Subject Software that is not governed by the terms of this Agreement. F. "Modification" means any alteration of, including addition to or deletion from, the substance or structure of either the Original Software or Subject Software, and includes derivative works, as that term is defined in the Copyright Statute, 17 USC § 101. However, the act of including Subject Software as part of a Larger Work does not in and of itself constitute a Modification. G. "Original Software" means the computer software first released under this Agreement by Government Agency entitled openPDC, including source code, object code and accompanying documentation, if any. H. "Recipient" means anyone who acquires the Subject Software under this Agreement, including all Contributors. I. "Redistribution" means Distribution of the Subject Software after a Modification has been made. J. "Reproduction" means the making of a counterpart, image or copy of the Subject Software. K. "Sale" means the exchange of the Subject Software for money or equivalent value. L. "Subject Software" means the Original Software, Modifications, or any respective parts thereof. M. "Use" means the application or employment of the Subject Software for any purpose. 2. GRANT OF RIGHTS A. Under Non-Patent Rights: Subject to the terms and conditions of this Agreement, each Contributor, with respect to its own contribution to the Subject Software, hereby grants to each Recipient a non-exclusive, world-wide, royalty-free license to engage in the following activities pertaining to the Subject Software: 1. Use 2. Distribution 3. Reproduction 4. Modification 5. Redistribution 6. Display B. Under Patent Rights: Subject to the terms and conditions of this Agreement, each Contributor, with respect to its own contribution to the Subject Software, hereby grants to each Recipient under Covered Patents a non-exclusive, world-wide, royalty-free license to engage in the following activities pertaining to the Subject Software: 1. Use 2. Distribution 3. Reproduction 4. Sale 5. Offer for Sale C. The rights granted under Paragraph B. also apply to the combination of a Contributor's Modification and the Subject Software if, at the time the Modification is added by the Contributor, the addition of such Modification causes the combination to be covered by the Covered Patents. It does not apply to any other combinations that include a Modification. D. The rights granted in Paragraphs A. and B. allow the Recipient to sublicense those same rights. Such sublicense must be under the same terms and conditions of this Agreement. 3. OBLIGATIONS OF RECIPIENT A. Distribution or Redistribution of the Subject Software must be made under this Agreement except for additions covered under paragraph 3H. 1. Whenever a Recipient distributes or redistributes the Subject Software, a copy of this Agreement must be included with each copy of the Subject Software; and 2. If Recipient distributes or redistributes the Subject Software in any form other than source code, Recipient must also make the source code freely available, and must provide with each copy of the Subject Software information on how to obtain the source code in a reasonable manner on or through a medium customarily used for software exchange. B. Each Recipient must ensure that the following copyright notice appears prominently in the Subject Software: No copyright is claimed pursuant to 17 USC § 105. All Other Rights Reserved. C. Each Contributor must characterize its alteration of the Subject Software as a Modification and must identify itself as the originator of its Modification in a manner that reasonably allows subsequent Recipients to identify the originator of the Modification. In fulfillment of these requirements, Contributor must include a file (e.g., a change log file) that describes the alterations made and the date of the alterations, identifies Contributor as originator of the alterations, and consents to characterization of the alterations as a Modification, for example, by including a statement that the Modification is derived, directly or indirectly, from Original Software provided by Government Agency. Once consent is granted, it may not thereafter be revoked. D. A Contributor may add its own copyright notice to the Subject Software. Once a copyright notice has been added to the Subject Software, a Recipient may not remove it without the express permission of the Contributor who added the notice. E. A Recipient may not make any representation in the Subject Software or in any promotional, advertising or other material that may be construed as an endorsement by Government Agency or by any prior Recipient of any product or service provided by Recipient, or that may seek to obtain commercial advantage by the fact of Government Agency's or a prior Recipient's participation in this Agreement. F. In an effort to track usage and maintain accurate records of the Subject Software, each Recipient, upon receipt of the Subject Software, is requested to register with Government Agency by visiting the following website: https://naspi.tva.com/Registration/. Recipient's name and personal information shall be used for statistical purposes only. Once a Recipient makes a Modification available, it is requested that the Recipient inform Government Agency at the web site provided above how to access the Modification. G. Each Contributor represents that that its Modification does not violate any existing agreements, regulations, statutes or rules, and further that Contributor has sufficient rights to grant the rights conveyed by this Agreement. H. A Recipient may choose to offer, and to charge a fee for, warranty, support, indemnity and/or liability obligations to one or more other Recipients of the Subject Software. A Recipient may do so, however, only on its own behalf and not on behalf of Government Agency or any other Recipient. Such a Recipient must make it absolutely clear that any such warranty, support, indemnity and/or liability obligation is offered by that Recipient alone. Further, such Recipient agrees to indemnify Government Agency and every other Recipient for any liability incurred by them as a result of warranty, support, indemnity and/or liability offered by such Recipient. I. A Recipient may create a Larger Work by combining Subject Software with separate software not governed by the terms of this agreement and distribute the Larger Work as a single product. In such case, the Recipient must make sure Subject Software, or portions thereof, included in the Larger Work is subject to this Agreement. J. Notwithstanding any provisions contained herein, Recipient is hereby put on notice that export of any goods or technical data from the United States may require some form of export license from the U.S. Government. Failure to obtain necessary export licenses may result in criminal liability under U.S. laws. Government Agency neither represents that a license shall not be required nor that, if required, it shall be issued. Nothing granted herein provides any such export license. 4. DISCLAIMER OF WARRANTIES AND LIABILITIES; WAIVER AND INDEMNIFICATION A. No Warranty: THE SUBJECT SOFTWARE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY OF ANY KIND, EITHER EXPRESSED, IMPLIED, OR STATUTORY, INCLUDING, BUT NOT LIMITED TO, ANY WARRANTY THAT THE SUBJECT SOFTWARE WILL CONFORM TO SPECIFICATIONS, ANY IMPLIED WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE, OR FREEDOM FROM INFRINGEMENT, ANY WARRANTY THAT THE SUBJECT SOFTWARE WILL BE ERROR FREE, OR ANY WARRANTY THAT DOCUMENTATION, IF PROVIDED, WILL CONFORM TO THE SUBJECT SOFTWARE. THIS AGREEMENT DOES NOT, IN ANY MANNER, CONSTITUTE AN ENDORSEMENT BY GOVERNMENT AGENCY OR ANY PRIOR RECIPIENT OF ANY RESULTS, RESULTING DESIGNS, HARDWARE, SOFTWARE PRODUCTS OR ANY OTHER APPLICATIONS RESULTING FROM USE OF THE SUBJECT SOFTWARE. FURTHER, GOVERNMENT AGENCY DISCLAIMS ALL WARRANTIES AND LIABILITIES REGARDING THIRD-PARTY SOFTWARE, IF PRESENT IN THE ORIGINAL SOFTWARE, AND DISTRIBUTES IT "AS IS." B. Waiver and Indemnity: RECIPIENT AGREES TO WAIVE ANY AND ALL CLAIMS AGAINST GOVERNMENT AGENCY, ITS AGENTS, EMPLOYEES, CONTRACTORS AND SUBCONTRACTORS, AS WELL AS ANY PRIOR RECIPIENT. IF RECIPIENT'S USE OF THE SUBJECT SOFTWARE RESULTS IN ANY LIABILITIES, DEMANDS, DAMAGES, EXPENSES OR LOSSES ARISING FROM SUCH USE, INCLUDING ANY DAMAGES FROM PRODUCTS BASED ON, OR RESULTING FROM, RECIPIENT'S USE OF THE SUBJECT SOFTWARE, RECIPIENT SHALL INDEMNIFY AND HOLD HARMLESS GOVERNMENT AGENCY, ITS AGENTS, EMPLOYEES, CONTRACTORS AND SUBCONTRACTORS, AS WELL AS ANY PRIOR RECIPIENT, TO THE EXTENT PERMITTED BY LAW. THE FOREGOING RELEASE AND INDEMNIFICATION SHALL APPLY EVEN IF THE LIABILITIES, DEMANDS, DAMAGES, EXPENSES OR LOSSES ARE CAUSED, OCCASIONED, OR CONTRIBUTED TO BY THE NEGLIGENCE, SOLE OR CONCURRENT, OF GOVERNMENT AGENCY OR ANY PRIOR RECIPIENT. RECIPIENT'S SOLE REMEDY FOR ANY SUCH MATTER SHALL BE THE IMMEDIATE, UNILATERAL TERMINATION OF THIS AGREEMENT. 5. GENERAL TERMS A. Termination: This Agreement and the rights granted hereunder will terminate automatically if a Recipient fails to comply with these terms and conditions, and fails to cure such noncompliance within thirty (30) days of becoming aware of such noncompliance. Upon termination, a Recipient agrees to immediately cease use and distribution of the Subject Software. All sublicenses to the Subject Software properly granted by the breaching Recipient shall survive any such termination of this Agreement. B. Severability: If any provision of this Agreement is invalid or unenforceable under applicable law, it shall not affect the validity or enforceability of the remainder of the terms of this Agreement. C. Applicable Law: This Agreement shall be subject to United States federal law only for all purposes, including, but not limited to, determining the validity of this Agreement, the meaning of its provisions and the rights, obligations and remedies of the parties. D. Entire Understanding: This Agreement constitutes the entire understanding and agreement of the parties relating to release of the Subject Software and may not be superseded, modified or amended except by further written agreement duly executed by the parties. E. Binding Authority: By accepting and using the Subject Software under this Agreement, a Recipient affirms its authority to bind the Recipient to all terms and conditions of this Agreement and that Recipient hereby agrees to all terms and conditions herein. F. Point of Contact: Any Recipient contact with Government Agency is to be directed to the designated representative as follows: J. Ritchie Carroll . */ #endregion #region [ Contributor License Agreements ] //****************************************************************************************************** // // Copyright © 2011, Grid Protection Alliance. All Rights Reserved. // // The GPA licenses this file to you under the Eclipse Public License -v 1.0 (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/eclipse-1.0.php // // 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. // //****************************************************************************************************** #endregion using GSF.Configuration; using System; using System.Collections.Concurrent; using System.Collections.Generic; using System.ComponentModel; using System.Linq; using System.Net; using System.Net.Security; using System.Net.Sockets; using System.Security.Principal; using System.Text; using System.Threading; namespace GSF.Communication { /// /// Represents a TCP-based communication server. /// /// /// The socket can be bound to a specified interface on a machine with multiple interfaces by /// specifying the interface in the (Example: "Port=8888; Interface=127.0.0.1") /// /// /// 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 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))); /// } /// } /// /// public class TcpServer : ServerBase { #region [ Members ] // Nested Types private class TcpClientInfo { public TransportProvider Client; public SocketAsyncEventArgs SendArgs; public SpinLock SendLock; public ConcurrentQueue SendQueue; public int Sending; } 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 /// /// Specifies the default value for the property. /// public const bool DefaultPayloadAware = false; /// /// Specifies the default value for the property. /// public const bool DefaultIntegratedSecurity = false; /// /// Specifies the default value for the property. /// public const bool DefaultAllowDualStackSocket = true; /// /// Specifies the default value for the property. /// public const int DefaultMaxSendQueueSize = -1; /// /// Specifies the default value for the property. /// public const string DefaultConfigurationString = "Port=8888"; // Fields private bool m_payloadAware; private byte[] m_payloadMarker; private bool m_integratedSecurity; private IPStack m_ipStack; private bool m_allowDualStackSocket; private int m_maxSendQueueSize; private Socket m_tcpServer; private SocketAsyncEventArgs m_acceptArgs; private ConcurrentDictionary m_clientInfoLookup; private Dictionary m_configData; private EventHandler m_acceptHandler; private EventHandler m_sendHandler; private EventHandler m_receivePayloadAwareHandler; private EventHandler m_receivePayloadUnawareHandler; #endregion #region [ Constructors ] /// /// Initializes a new instance of the class. /// public TcpServer() : this(DefaultConfigurationString) { } /// /// Initializes a new instance of the class. /// /// Config string of the . See for format. public TcpServer(string configString) : base(TransportProtocol.Tcp, configString) { m_payloadAware = DefaultPayloadAware; m_payloadMarker = Payload.DefaultMarker; m_integratedSecurity = DefaultIntegratedSecurity; m_allowDualStackSocket = DefaultAllowDualStackSocket; m_maxSendQueueSize = DefaultMaxSendQueueSize; m_clientInfoLookup = new ConcurrentDictionary(); m_acceptHandler = (sender, args) => ProcessAccept(); m_sendHandler = (sender, args) => ProcessSend(args); m_receivePayloadAwareHandler = (sender, args) => ProcessReceivePayloadAware(args); m_receivePayloadUnawareHandler = (sender, args) => ProcessReceivePayloadUnaware(args); } /// /// Initializes a new instance of the class. /// /// object that contains the . public TcpServer(IContainer container) : this() { if (container != null) container.Add(this); } #endregion #region [ Properties ] /// /// Gets or sets a boolean value that indicates whether the payload boundaries are to be preserved during transmission. /// [Category("Data"), DefaultValue(DefaultPayloadAware), Description("Indicates whether the payload boundaries are to be preserved during transmission.")] public bool PayloadAware { get { return m_payloadAware; } set { m_payloadAware = value; } } /// /// Gets or sets the byte sequence used to mark the beginning of a payload in a transmission. /// /// The value being assigned is null or empty buffer. [Browsable(false), DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)] public byte[] PayloadMarker { get { return m_payloadMarker; } set { if (value == null || value.Length == 0) throw new ArgumentNullException("value"); m_payloadMarker = value; } } /// /// Gets or sets a boolean value that indicates whether the client Windows account credentials are used for authentication. /// /// /// This option is ignored under Mono deployments. /// [Category("Security"), DefaultValue(DefaultIntegratedSecurity), Description("Indicates whether the client Windows account credentials are used for authentication.")] public bool IntegratedSecurity { get { return m_integratedSecurity; } set { m_integratedSecurity = value; } } /// /// 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 { return m_allowDualStackSocket; } set { m_allowDualStackSocket = 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 { return m_maxSendQueueSize; } set { m_maxSendQueueSize = value; } } /// /// Gets the object for the . /// [Browsable(false)] public Socket Server { get { return m_tcpServer; } } /// /// Gets the receive handler used to handle data received on the connected sockets. /// private EventHandler ReceiveHandler { get { return m_payloadAware ? m_receivePayloadAwareHandler : m_receivePayloadUnawareHandler; } } /// /// 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(); } statusBuilder.AppendFormat(" Wait handle pool size: {0}", ReusableObjectPool.Default.GetPoolSize()); statusBuilder.AppendLine(); statusBuilder.AppendFormat(" Payload pool size: {0}", ReusableObjectPool.Default.GetPoolSize()); 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); TcpClientInfo clientInfo; TransportProvider tcpClient; if (m_clientInfoLookup.TryGetValue(clientID, out clientInfo)) { tcpClient = clientInfo.Client; if ((object)tcpClient.ReceiveBuffer != null) { 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; } throw new InvalidOperationException("No received data buffer has been defined to read."); } throw new InvalidOperationException("Specified client ID does not exist, cannot read buffer."); } /// /// Saves settings to the config file if the property is set to true. /// public override void SaveSettings() { base.SaveSettings(); //if (PersistSettings) //{ // // Save settings under the specified category. // ConfigurationFile config = ConfigurationFile.Current; // CategorizedSettingsElementCollection settings = config.Settings[SettingsCategory]; // settings["PayloadAware", true].Update(m_payloadAware); // settings["IntegratedSecurity", true].Update(m_integratedSecurity); // settings["AllowDualStackSocket", true].Update(m_allowDualStackSocket); // settings["MaxSendQueueSize", true].Update(m_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) //{ // // Load settings from the specified category. // ConfigurationFile config = ConfigurationFile.Current; // CategorizedSettingsElementCollection settings = config.Settings[SettingsCategory]; // settings.Add("PayloadAware", m_payloadAware, "True if payload boundaries are to be preserved during transmission, otherwise False."); // settings.Add("IntegratedSecurity", m_integratedSecurity, "True if the client Windows account credentials are used for authentication, otherwise False."); // settings.Add("AllowDualStackSocket", m_allowDualStackSocket, "True if dual-mode socket is allowed when IP address is IPv6, otherwise False."); // settings.Add("MaxSendQueueSize", m_allowDualStackSocket, "The maximum size of the send queue before payloads are dumped from the queue."); // PayloadAware = settings["PayloadAware"].ValueAs(m_payloadAware); // IntegratedSecurity = settings["IntegratedSecurity"].ValueAs(m_integratedSecurity); // AllowDualStackSocket = settings["AllowDualStackSocket"].ValueAs(m_allowDualStackSocket); // MaxSendQueueSize = settings["MaxSendQueueSize"].ValueAs(m_maxSendQueueSize); //} } /// /// Stops the synchronously and disconnects all connected clients. /// public override void Stop() { SocketAsyncEventArgs acceptArgs = m_acceptArgs; m_acceptArgs = null; if (CurrentState == ServerState.Running) { DisconnectAll(); // Disconnection all clients. m_tcpServer.Close(); // Stop accepting new connections. // Clean up accept args. acceptArgs.Dispose(); OnServerStopped(); } } /// /// 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) { int maxClientConnections, maxSendQueueSize; // Initialize if unitialized. if (!Initialized) Initialize(); // Overwrite config file if max client connections exists in connection string. if (m_configData.ContainsKey("maxClientConnections") && int.TryParse(m_configData["maxClientConnections"], out maxClientConnections)) MaxClientConnections = maxClientConnections; // Overwrite config file if max send queue size exists in connection string. if (m_configData.ContainsKey("maxSendQueueSize") && int.TryParse(m_configData["maxSendQueueSize"], out maxSendQueueSize)) m_maxSendQueueSize = maxSendQueueSize; // Bind server socket to local end-point and listen. m_tcpServer = Transport.CreateSocket(m_configData["interface"], int.Parse(m_configData["port"]), ProtocolType.Tcp, m_ipStack, m_allowDualStackSocket); m_tcpServer.Listen(1); // Begin accepting incoming connection asynchronously. m_acceptArgs = FastObjectFactory.CreateObjectFunction(); m_acceptArgs.AcceptSocket = null; m_acceptArgs.SetBuffer(null, 0, 0); m_acceptArgs.SocketFlags = SocketFlags.None; m_acceptArgs.Completed += m_acceptHandler; if (!m_tcpServer.AcceptAsync(m_acceptArgs)) ThreadPool.QueueUserWorkItem(state => ProcessAccept()); // Notify that the server has been started successfully. OnServerStarted(); } } /// /// 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) { TransportProvider client; if (!TryGetClient(clientID, out client)) return; try { if ((object)client.Provider != null && client.Provider.Connected) client.Provider.Disconnect(false); OnClientDisconnected(clientID); client.Reset(); } catch (Exception ex) { OnSendClientDataException(client.ID, new InvalidOperationException(string.Format("Client disconnection exception: {0}", ex.Message), ex)); } } /// /// Gets the object associated with the specified client ID. /// /// ID of the client. /// The TCP client. /// An object. /// Client does not exist for the specified . public bool TryGetClient(Guid clientID, out TransportProvider tcpClient) { TcpClientInfo clientInfo; bool clientExists = m_clientInfoLookup.TryGetValue(clientID, out clientInfo); if (clientExists) tcpClient = clientInfo.Client; else tcpClient = 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(string.Format("Port property is missing (Example: {0})", DefaultConfigurationString)); if (!Transport.IsPortNumberValid(m_configData["port"])) throw new ArgumentOutOfRangeException("configurationString", string.Format("Port number must be between {0} and {1}", Transport.PortRangeLow, Transport.PortRangeHigh)); } /// /// 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) { TcpClientInfo clientInfo; ConcurrentQueue sendQueue; TcpServerPayload dequeuedPayload; TcpServerPayload payload; ManualResetEventSlim handle; bool lockTaken = false; if (!m_clientInfoLookup.TryGetValue(clientID, out clientInfo)) throw new InvalidOperationException(string.Format("No client found for ID {0}.", clientID)); sendQueue = clientInfo.SendQueue; // Check to see if the client has reached the maximum send queue size. if (m_maxSendQueueSize > 0 && sendQueue.Count >= m_maxSendQueueSize) { for (int i = 0; i < m_maxSendQueueSize; i++) { if (sendQueue.TryDequeue(out payload)) { payload.WaitHandle.Set(); payload.WaitHandle.Dispose(); payload.WaitHandle = null; } } throw new InvalidOperationException(string.Format("Client {0} connected to TCP server reached maximum send queue size. {1} payloads dumped from the queue.", clientID, m_maxSendQueueSize)); } // Prepare for payload-aware transmission. if (m_payloadAware) Payload.AddHeader(ref data, ref offset, ref length, m_payloadMarker); // Create payload and wait handle. payload = ReusableObjectPool.Default.TakeObject(); handle = ReusableObjectPool.Default.TakeObject(); payload.Data = data; payload.Offset = offset; payload.Length = length; payload.WaitHandle = handle; payload.ClientInfo = clientInfo; handle.Reset(); // Queue payload for sending. sendQueue.Enqueue(payload); try { clientInfo.SendLock.Enter(ref lockTaken); // Send next queued payload. if (Interlocked.CompareExchange(ref clientInfo.Sending, 1, 0) == 0) { if (sendQueue.TryDequeue(out dequeuedPayload)) ThreadPool.QueueUserWorkItem(state => SendPayload((TcpServerPayload)state), dequeuedPayload); else Interlocked.Exchange(ref clientInfo.Sending, 0); } } finally { if (lockTaken) clientInfo.SendLock.Exit(); } // 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); } /// /// Raises the event. /// /// ID of client to send to event. /// Exception to send to event. protected virtual void OnReceiveClientDataException(Guid clientID, SocketException ex) { if (m_clientInfoLookup.ContainsKey(clientID) && ex.SocketErrorCode != SocketError.Disconnecting) OnReceiveClientDataException(clientID, (Exception)ex); } /// /// 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); } /// /// Callback method for asynchronous accept operation. /// private void ProcessAccept() { TransportProvider client = new TransportProvider(); SocketAsyncEventArgs receiveArgs = null; TcpClientInfo clientInfo; try { if (CurrentState == ServerState.NotRunning) return; if (m_acceptArgs.SocketError != SocketError.Success) { // Error is unrecoverable. // We need to make sure to restart the // server before we throw the error. SocketError error = m_acceptArgs.SocketError; ThreadPool.QueueUserWorkItem(state => ReStart()); throw new SocketException((int)error); } // Process the newly connected client. client.Provider = m_acceptArgs.AcceptSocket; // Set up SocketAsyncEventArgs for receive operations. receiveArgs = FastObjectFactory.CreateObjectFunction(); receiveArgs.Completed += ReceiveHandler; // Return to accepting new connections. m_acceptArgs.AcceptSocket = null; if (!m_tcpServer.AcceptAsync(m_acceptArgs)) { ThreadPool.QueueUserWorkItem(state => ProcessAccept()); } #if !MONO // Authenticate the connected client Windows credentials. if (m_integratedSecurity) { NetworkStream socketStream = null; NegotiateStream authenticationStream = null; try { socketStream = new NetworkStream(client.Provider); authenticationStream = new NegotiateStream(socketStream); authenticationStream.AuthenticateAsServer(); if (authenticationStream.RemoteIdentity is WindowsIdentity) Thread.CurrentPrincipal = new WindowsPrincipal((WindowsIdentity)authenticationStream.RemoteIdentity); } finally { if (socketStream != null) socketStream.Dispose(); if (authenticationStream != null) authenticationStream.Dispose(); } } #endif if (MaxClientConnections != -1 && ClientIDs.Length >= MaxClientConnections) { // Reject client connection since limit has been reached. TerminateConnection(client, receiveArgs, false); } else { // We can proceed further with receiving data from the client. clientInfo = new TcpClientInfo() { Client = client, SendArgs = FastObjectFactory.CreateObjectFunction(), SendLock = new SpinLock(), SendQueue = new ConcurrentQueue() }; // Set up socket args. client.SetSendBuffer(SendBufferSize); clientInfo.SendArgs.Completed += m_sendHandler; clientInfo.SendArgs.SetBuffer(client.SendBuffer, 0, client.SendBufferSize); m_clientInfoLookup.TryAdd(client.ID, clientInfo); OnClientConnected(client.ID); if (!m_payloadAware) { receiveArgs.UserToken = client; } else { EventArgs, bool> userToken = ReusableObjectPool, bool>>.Default.TakeObject(); userToken.Argument1 = client; receiveArgs.UserToken = userToken; } ReceivePayloadAsync(client, receiveArgs); } } catch (Exception ex) { // Notify of the exception. if ((object)client.Provider != null) { string clientAddress = ((IPEndPoint)client.Provider.RemoteEndPoint).Address.ToString(); string errorMessage = string.Format("Unable to accept connection to client [{0}]: {1}", clientAddress, ex.Message); OnClientConnectingException(new Exception(errorMessage, ex)); } if ((object)receiveArgs != null) { TerminateConnection(client, receiveArgs, false); } } } /// /// Asynchronous loop sends payloads on the socket. /// private void SendPayload(TcpServerPayload payload) { TcpClientInfo clientInfo = null; TransportProvider client = null; SocketAsyncEventArgs args; ManualResetEventSlim handle; int copyLength; try { clientInfo = payload.ClientInfo; client = clientInfo.Client; args = clientInfo.SendArgs; args.UserToken = payload; handle = payload.WaitHandle; // Copy payload into send buffer. 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 (!client.Provider.SendAsync(args)) ProcessSend(args); } catch (Exception ex) { if ((object)client != null) OnSendClientDataException(client.ID, ex); if ((object)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) { TcpServerPayload payload = null; TcpClientInfo clientInfo = null; TransportProvider client = null; ConcurrentQueue sendQueue = null; ManualResetEventSlim handle = null; bool lockTaken = false; try { payload = (TcpServerPayload)args.UserToken; clientInfo = payload.ClientInfo; client = clientInfo.Client; sendQueue = clientInfo.SendQueue; 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 ((object)client != null) OnSendClientDataException(client.ID, ex); } finally { try { if (payload.Length > 0) { // Still more to send for this payload. ThreadPool.QueueUserWorkItem(state => SendPayload((TcpServerPayload)state), payload); } else { payload.WaitHandle = null; payload.ClientInfo = null; // Return payload and wait handle to their respective object pools. ReusableObjectPool.Default.ReturnObject(payload); ReusableObjectPool.Default.ReturnObject(handle); // Begin sending next client payload. if (sendQueue.TryDequeue(out payload)) { ThreadPool.QueueUserWorkItem(state => SendPayload((TcpServerPayload)state), payload); } else { try { clientInfo.SendLock.Enter(ref lockTaken); if (sendQueue.TryDequeue(out payload)) ThreadPool.QueueUserWorkItem(state => SendPayload((TcpServerPayload)state), payload); else Interlocked.Exchange(ref clientInfo.Sending, 0); } finally { if (lockTaken) clientInfo.SendLock.Exit(); } } } } catch (Exception ex) { string errorMessage = string.Format("Exception encountered while attempting to send next payload: {0}", ex.Message); if ((object)client != null) OnSendClientDataException(client.ID, new Exception(errorMessage, ex)); if ((object)clientInfo != null) Interlocked.Exchange(ref clientInfo.Sending, 0); } } } /// /// Initiate method for asynchronous receive operation of payload data. /// private void ReceivePayloadAsync(TransportProvider client, SocketAsyncEventArgs args) { // Initialize bytes received. client.BytesReceived = 0; // Initiate receiving. if (m_payloadAware) { // Set user token to indicate we are waiting for payload header. EventArgs, bool> userToken = (EventArgs, bool>)args.UserToken; userToken.Argument2 = true; // Payload boundaries are to be preserved. client.SetReceiveBuffer(m_payloadMarker.Length + Payload.LengthSegment); ReceivePayloadAwareAsync(client, args); } else { // Payload boundaries are not to be preserved. client.SetReceiveBuffer(ReceiveBufferSize); ReceivePayloadUnawareAsync(client, args); } } /// /// Initiate method for asynchronous receive operation of payload data in "payload-aware" mode. /// private void ReceivePayloadAwareAsync(TransportProvider client, SocketAsyncEventArgs args) { byte[] buffer = client.ReceiveBuffer; int offset = client.BytesReceived; int length = client.ReceiveBufferSize - offset; args.SetBuffer(buffer, offset, length); if (!client.Provider.ReceiveAsync(args)) ThreadPool.QueueUserWorkItem(state => ProcessReceivePayloadAware((SocketAsyncEventArgs)state), args); } /// /// Callback method for asynchronous receive operation of payload data in "payload-aware" mode. /// private void ProcessReceivePayloadAware(SocketAsyncEventArgs args) { EventArgs, bool> userToken = (EventArgs, bool>)args.UserToken; TransportProvider client = userToken.Argument1; try { if (args.SocketError != SocketError.Success) throw new SocketException((int)args.SocketError); if (args.BytesTransferred == 0) throw new SocketException((int)SocketError.Disconnecting); // Update statistics and pointers. client.Statistics.UpdateBytesReceived(args.BytesTransferred); client.BytesReceived += args.BytesTransferred; if (userToken.Argument2) { // We're waiting on the payload length, so we'll check if the received data has this information. int payloadLength = Payload.ExtractLength(client.ReceiveBuffer, client.BytesReceived, m_payloadMarker); // We have the payload length. // If it is set to zero, there is no payload; wait for another header. // Otherwise we'll create a buffer that's big enough to hold the entire payload. if (payloadLength == 0) { client.BytesReceived = 0; } else if (payloadLength != -1) { client.BytesReceived = 0; client.SetReceiveBuffer(payloadLength); userToken.Argument2 = false; } ReceivePayloadAwareAsync(client, args); } else { // We're accumulating the payload in the receive buffer until the entire payload is received. if (client.BytesReceived == client.ReceiveBufferSize) { // We've received the entire payload. OnReceiveClientDataComplete(client.ID, client.ReceiveBuffer, client.BytesReceived); ReceivePayloadAsync(client, args); } else { // We've not yet received the entire payload. ReceivePayloadAwareAsync(client, args); } } } catch (ObjectDisposedException) { // Make sure connection is terminated when server is disposed. TerminateConnection(client, args, true); } catch (SocketException ex) { // Terminate connection when socket exception is encountered. OnReceiveClientDataException(client.ID, ex); TerminateConnection(client, args, true); } catch (Exception ex) { try { // For any other exception, notify and resume receive. OnReceiveClientDataException(client.ID, ex); ReceivePayloadAsync(client, args); } catch { // Terminate connection if resuming receiving fails. TerminateConnection(client, args, true); } } } /// /// Initiate method for asynchronous receive operation of payload data in "payload-unaware" mode. /// private void ReceivePayloadUnawareAsync(TransportProvider client, SocketAsyncEventArgs args) { byte[] buffer = client.ReceiveBuffer; int length = client.ReceiveBufferSize; args.SetBuffer(buffer, 0, length); if (!client.Provider.ReceiveAsync(args)) ThreadPool.QueueUserWorkItem(state => ProcessReceivePayloadUnaware((SocketAsyncEventArgs)state), args); } /// /// Callback method for asynchronous receive operation of payload data in "payload-unaware" mode. /// private void ProcessReceivePayloadUnaware(SocketAsyncEventArgs args) { TransportProvider client = (TransportProvider)args.UserToken; try { if (args.SocketError != SocketError.Success) throw new SocketException((int)args.SocketError); if (args.BytesTransferred == 0) throw new SocketException((int)SocketError.Disconnecting); // Update statistics and pointers. client.Statistics.UpdateBytesReceived(args.BytesTransferred); client.BytesReceived = args.BytesTransferred; // Notify of received data and resume receive operation. OnReceiveClientDataComplete(client.ID, client.ReceiveBuffer, client.BytesReceived); ReceivePayloadUnawareAsync(client, args); } catch (ObjectDisposedException) { // Make sure connection is terminated when server is disposed. TerminateConnection(client, args, true); } catch (SocketException ex) { // Terminate connection when socket exception is encountered. OnReceiveClientDataException(client.ID, ex); TerminateConnection(client, args, true); } catch (Exception ex) { try { // For any other exception, notify and resume receive. OnReceiveClientDataException(client.ID, ex); ReceivePayloadAsync(client, args); } catch { // Terminate connection if resuming receiving fails. TerminateConnection(client, args, true); } } } /// /// Processes the termination of client. /// private void TerminateConnection(TransportProvider client, SocketAsyncEventArgs args, bool raiseEvent) { TcpClientInfo clientInfo; try { if (m_clientInfoLookup.TryRemove(client.ID, out clientInfo)) { client.Reset(); clientInfo.SendArgs.Dispose(); } if (raiseEvent) OnClientDisconnected(client.ID); } finally { DisposeReceiveArgs(args); } } /// /// Returns the used for receiving data on the socket. /// private void DisposeReceiveArgs(SocketAsyncEventArgs receiveArgs) { EventArgs, bool> userToken = receiveArgs.UserToken as EventArgs, bool>; receiveArgs.Dispose(); if ((object)userToken != null) { ReusableObjectPool, bool>>.Default.ReturnObject(userToken); } } #endregion } }