//******************************************************************************************************// ServerBase.cs - Gbtc//// Copyright © 2015, 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/01/2006 - Pinal C. Patel// Original version of source code generated.// 09/06/2006 - J. Ritchie Carroll// Added bypass optimizations for high-speed server data access.// 11/30/2007 - Pinal C. Patel// Modified the "design time" check in EndInit() method to use LicenseManager.UsageMode property// instead of DesignMode property as the former is more accurate than the latter.// 02/19/2008 - Pinal C. Patel// Added code to detect and avoid redundant calls to Dispose().// 09/29/2008 - J. Ritchie Carroll// Converted to C#.// 06/18/2009 - Pinal C. Patel// Fixed the implementation of Enabled property.// 07/02/2009 - Pinal C. Patel// Modified state alternating properties to restart the server when changed.// 07/17/2009 - Pinal C. Patel// Modified SharedSecret to be persisted as an encrypted value.// 08/05/2009 - Josh L. Patterson// Edited Comments.// 09/14/2009 - Stephen C. Wills// Added new header and license agreement.// 04/14/2011 - Pinal C. Patel// Updated to use new serialization methods in GSF.Serialization class.// 12/02/2011 - J. Ritchie Carroll// Updated event data publication to provide "copy" of reusable buffer instead of original// buffer since you cannot assume how user will use the buffer (they may cache it).// 12/04/2011 - J. Ritchie Carroll// Modified to use concurrent dictionary.// 04/26/2012 - Pinal C. Patel// Updated Create() static method to apply settings from the configuration string to the created // server instance using reflection.// 12/13/2012 - Starlynn Danyelle Gilliam// Modified Header.// 05/22/2015 - J. Ritchie Carroll// Added ZeroMQ to the create IServer options.////****************************************************************************************************** using System;using System.Collections.Concurrent;using System.Collections.Generic;using System.ComponentModel;using System.Configuration;using System.Drawing;using System.Globalization;using System.Linq;using System.Reflection;using System.Text;using System.Threading;using GSF.Configuration;using GSF.Diagnostics;using GSF.Units; // ReSharper disable VirtualMemberCallInConstructor// ReSharper disable ConvertToAutoPropertyWhenPossiblenamespace GSF.Communication{ /// <summary> /// Base class for a server involved in server-client communication. /// </summary> [ToolboxBitmap(typeof(ServerBase))] public abstract class ServerBase : Component, IServer, ISupportInitialize, IPersistSettings { #region [ Members ] // Constants /// <summary> /// Specifies the default value for the <see cref="MaxClientConnections"/> property. /// </summary> public const int DefaultMaxClientConnections = -1; /// <summary> /// Specifies the default value for the <see cref="SendBufferSize"/> property. /// </summary> public const int DefaultSendBufferSize = 32768; /// <summary> /// Specifies the default value for the <see cref="ReceiveBufferSize"/> property. /// </summary> public const int DefaultReceiveBufferSize = 32768; /// <summary> /// Specifies the default value for the <see cref="PersistSettings"/> property. /// </summary> public const bool DefaultPersistSettings = false; /// <summary> /// Specifies the default value for the <see cref="SettingsCategory"/> property. /// </summary> public const string DefaultSettingsCategory = "CommunicationServer"; // Events /// <summary> /// Occurs when the server is started. /// </summary> [Category("Server")] [Description("Occurs when the server is started.")] public event EventHandler ServerStarted; /// <summary> /// Occurs when the server is stopped. /// </summary> [Category("Server")] [Description("Occurs when the server is stopped.")] public event EventHandler ServerStopped; /// <summary> /// Occurs when a client connects to the server. /// </summary> /// <remarks> /// <see cref="EventArgs{T}.Argument"/> is the ID of the client that connected to the server. /// </remarks> [Category("Client")] [Description("Occurs when a client connects to the server.")] public event EventHandler<EventArgs<Guid>> ClientConnected; /// <summary> /// Occurs when a client disconnects from the server. /// </summary> /// <remarks> /// <see cref="EventArgs{T}.Argument"/> is the ID of the client that disconnected from the server. /// </remarks> [Category("Client")] [Description("Occurs when a client disconnects from the server.")] public event EventHandler<EventArgs<Guid>> ClientDisconnected; /// <summary> /// Occurs when an exception is encountered while a client is connecting. /// </summary> /// <remarks> /// <see cref="EventArgs{T}.Argument"/> is the <see cref="Exception"/> encountered when connecting to the client. /// </remarks> [Category("Client")] [Description("Occurs when an Exception is encountered when connecting to a client.")] public event EventHandler<EventArgs<Exception>> ClientConnectingException; /// <summary> /// Occurs when data is being sent to a client. /// </summary> /// <remarks> /// <see cref="EventArgs{T}.Argument"/> is the ID of the client to which the data is being sent. /// </remarks> [Category("Data")] [Description("Occurs when data is being sent to a client.")] public event EventHandler<EventArgs<Guid>> SendClientDataStart; /// <summary> /// Occurs when data has been sent to a client. /// </summary> /// <remarks> /// <see cref="EventArgs{T}.Argument"/> is the ID of the client to which the data has been sent. /// </remarks> [Category("Data")] [Description("Occurs when data has been sent to a client.")] public event EventHandler<EventArgs<Guid>> SendClientDataComplete; /// <summary> /// Occurs when an <see cref="Exception"/> is encountered when sending data to a client. /// </summary> /// <remarks> /// <see cref="EventArgs{T1,T2}.Argument1"/> is the ID of the client to which the data was being sent.<br/> /// <see cref="EventArgs{T1,T2}.Argument2"/> is the <see cref="Exception"/> encountered when sending data to a client. /// </remarks> [Category("Data")] [Description("Occurs when an Exception is encountered when sending data to a client.")] public event EventHandler<EventArgs<Guid, Exception>> SendClientDataException; /// <summary> /// Occurs when unprocessed data has been received from a client. /// </summary> /// <remarks> /// <para> /// This event can be used to receive a notification that client data has arrived. The <see cref="Read"/> method can then be used /// to copy data to an existing buffer. In many cases it will be optimal to use an existing buffer instead of subscribing to the /// <see cref="ReceiveClientDataComplete"/> event, however, the data that is available after calling the <see cref="Read"/> method /// will be the original unprocessed data received by the client, i.e., not optionally decrypted or decompressed data. /// </para> /// <para> /// <see cref="EventArgs{T1,T2}.Argument1"/> is the ID of the client from which data is received.<br/> /// <see cref="EventArgs{T1,T2}.Argument2"/> is the number of bytes received in the buffer from the client. /// </para> /// </remarks> [Category("Data")] [Description("Occurs when unprocessed data has been received from a client.")] public event EventHandler<EventArgs<Guid, int>> ReceiveClientData; /// <summary> /// Occurs when data received from a client has been processed and is ready for consumption. /// </summary> /// <remarks> /// <see cref="EventArgs{T1,T2,T3}.Argument1"/> is the ID of the client from which data is received.<br/> /// <see cref="EventArgs{T1,T2,T3}.Argument2"/> is a new buffer containing post-processed data received from the client starting at index zero.<br/> /// <see cref="EventArgs{T1,T2,T3}.Argument3"/> is the number of post-processed bytes received in the buffer from the client. /// </remarks> [Category("Data")] [Description("Occurs when data received from a client has been processed and is ready for consumption.")] public event EventHandler<EventArgs<Guid, byte[], int>> ReceiveClientDataComplete; /// <summary> /// Occurs when an <see cref="Exception"/> is encountered when receiving data from a client. /// </summary> /// <remarks> /// <see cref="EventArgs{T1,T2}.Argument1"/> is the ID of the client from which the data was being received.<br/> /// <see cref="EventArgs{T1,T2}.Argument2"/> is the <see cref="Exception"/> encountered when receiving data from a client. /// </remarks> [Category("Data")] [Description("Occurs when an Exception is encountered when receiving data from a client.")] public event EventHandler<EventArgs<Guid, Exception>> ReceiveClientDataException; /// <summary> /// Occurs when an <see cref="Exception"/> is encountered in a user-defined function via an event dispatch. /// </summary> /// <remarks> /// <see cref="EventArgs{T}.Argument"/> is the <see cref="Exception"/> thrown by the user-defined function. /// </remarks> [Category("User")] [Description("Occurs when an Exception is encountered when calling a user-defined function.")] public event EventHandler<EventArgs<Exception>> UnhandledUserException; // Fields private string m_configurationString; private int m_maxClientConnections; private int m_sendBufferSize; private int m_receiveBufferSize; private Encoding m_textEncoding; private readonly TransportProtocol m_transportProtocol; private readonly ConcurrentDictionary<Guid, int> m_clientIDs; private Ticks m_stopTime; private Ticks m_startTime; #endregion #region [ Constructors ] /// <summary> /// Initializes a new instance of the server. /// </summary> protected ServerBase() { ServerID = Guid.NewGuid(); m_clientIDs = new ConcurrentDictionary<Guid, int>(); m_textEncoding = Encoding.ASCII; CurrentState = ServerState.NotRunning; m_maxClientConnections = DefaultMaxClientConnections; m_sendBufferSize = DefaultSendBufferSize; m_receiveBufferSize = DefaultReceiveBufferSize; PersistSettings = DefaultPersistSettings; Name = DefaultSettingsCategory; } /// <summary> /// Initializes a new instance of the server. /// </summary> /// <param name="transportProtocol">One of the <see cref="TransportProtocol"/> values.</param> /// <param name="configurationString">The data used by the server for initialization.</param> protected ServerBase(TransportProtocol transportProtocol, string configurationString) : this() { m_transportProtocol = transportProtocol; ConfigurationString = configurationString; } #endregion #region [ Properties ] /// <summary> /// Gets or sets the data required by the server to initialize. /// </summary> [Category("Settings")] [Description("The data that is required by the server to initialize.")] public virtual string ConfigurationString { get => m_configurationString; set { ValidateConfigurationString(value); m_configurationString = value; ReStart(); } } /// <summary> /// Gets or sets the maximum number of clients that can connect to the server. /// </summary> /// <remarks> /// Set <see cref="MaxClientConnections"/> to -1 to allow infinite client connections. /// </remarks> [Category("Settings")] [DefaultValue(DefaultMaxClientConnections)] [Description("The maximum number of clients that can connect to the server. Set MaxClientConnections to -1 to allow infinite client connections.")] public virtual int MaxClientConnections { get => m_maxClientConnections; set => m_maxClientConnections = value < 1 ? -1 : value; } /// <summary> /// Gets or sets the size of the buffer used by the server for sending data to the clients. /// </summary> /// <exception cref="ArgumentException">The value being assigned is either zero or negative.</exception> [Category("Data")] [DefaultValue(DefaultSendBufferSize)] [Description("The size of the buffer used by the server for receiving data from the clients.")] public virtual int SendBufferSize { get => m_sendBufferSize; set { if (value < 1) throw new ArgumentException("Value cannot be zero or negative"); m_sendBufferSize = value; } } /// <summary> /// Gets or sets the size of the buffer used by the server for receiving data from the clients. /// </summary> /// <exception cref="ArgumentException">The value being assigned is either zero or negative.</exception> [Category("Data")] [DefaultValue(DefaultReceiveBufferSize)] [Description("The size of the buffer used by the server for receiving data from the clients.")] public virtual int ReceiveBufferSize { get => m_receiveBufferSize; set { if (value < 1) throw new ArgumentException("Value cannot be zero or negative"); m_receiveBufferSize = value; } } /// <summary> /// Gets or sets a boolean value that indicates whether the server settings are to be saved to the config file. /// </summary> [Category("Persistence")] [DefaultValue(DefaultPersistSettings)] [Description("Indicates whether the server settings are to be saved to the config file.")] public bool PersistSettings { get; set; } /// <summary> /// Gets or sets the category under which the server settings are to be saved to the config file if the <see cref="PersistSettings"/> property is set to true. /// </summary> /// <exception cref="ArgumentNullException">The value being assigned is a null or empty string.</exception> [Category("Persistence")] [DefaultValue(DefaultSettingsCategory)] [Description("Category under which the server settings are to be saved to the config file if the PersistSettings property is set to true.")] public string SettingsCategory { get => Name; set { if (string.IsNullOrEmpty(value)) throw new ArgumentNullException(nameof(value)); Name = value; } } /// <summary> /// Gets or sets a boolean value that indicates whether the server is currently enabled. /// </summary> /// <remarks> /// Setting <see cref="Enabled"/> to true will start the server if it is not running, setting /// to false will stop the server if it is running. /// </remarks> [Browsable(false)] [DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)] public bool Enabled { get => CurrentState == ServerState.Running; set { if (value && !Enabled) Start(); else if (!value && Enabled) Stop(); } } /// <summary> /// Gets a flag that indicates whether the object has been disposed. /// </summary> [Browsable(false)] [DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)] public bool IsDisposed { get; private set; } /// <summary> /// Gets or sets the <see cref="Encoding"/> to be used for the text sent to the connected clients. /// </summary> [Browsable(false)] [DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)] public virtual Encoding TextEncoding { get => m_textEncoding; set => m_textEncoding = value; } /// <summary> /// Gets the current <see cref="ServerState"/>. /// </summary> [Browsable(false)] public ServerState CurrentState { get; private set; } /// <summary> /// Gets the <see cref="TransportProtocol"/> used by the server for the transportation of data with the clients. /// </summary> [Browsable(false)] public virtual TransportProtocol TransportProtocol => m_transportProtocol; /// <summary> /// Gets the server's ID. /// </summary> [Browsable(false)] public virtual Guid ServerID { get; } /// <summary> /// Gets the IDs of clients connected to the server. /// </summary> [Browsable(false)] public virtual Guid[] ClientIDs => m_clientIDs.Keys.ToArray(); /// <summary> /// Gets the <see cref="Time"/> for which the server has been running. /// </summary> [Browsable(false)] public virtual Time RunTime { get { Time serverRunTime = 0.0D; if (m_startTime > 0) { if (CurrentState == ServerState.Running) { // Server is running. serverRunTime = (DateTime.UtcNow.Ticks - m_startTime).ToSeconds(); } else { // Server is not running. serverRunTime = (m_stopTime - m_startTime).ToSeconds(); } } return serverRunTime; } } /// <summary> /// Gets current read indices for received data buffers incremented at each <see cref="Read"/> call. /// </summary> protected ConcurrentDictionary<Guid, int> ReadIndicies => m_clientIDs; /// <summary> /// Gets the unique identifier of the server. /// </summary> [Browsable(false)] public string Name { get; private set; } /// <summary> /// Gets the descriptive status of the server. /// </summary> [Browsable(false)] public virtual string Status { get { StringBuilder status = new StringBuilder(); status.Append(" Server state: "); status.Append(CurrentState); status.AppendLine(); status.Append(" Server runtime: "); status.Append(RunTime.ToString(3)); status.AppendLine(); status.Append(" Connected clients: ");Lines 1–500 of 1161. Load more
//****************************************************************************************************** // ServerBase.cs - Gbtc // // Copyright © 2015, 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/01/2006 - Pinal C. Patel // Original version of source code generated. // 09/06/2006 - J. Ritchie Carroll // Added bypass optimizations for high-speed server data access. // 11/30/2007 - Pinal C. Patel // Modified the "design time" check in EndInit() method to use LicenseManager.UsageMode property // instead of DesignMode property as the former is more accurate than the latter. // 02/19/2008 - Pinal C. Patel // Added code to detect and avoid redundant calls to Dispose(). // 09/29/2008 - J. Ritchie Carroll // Converted to C#. // 06/18/2009 - Pinal C. Patel // Fixed the implementation of Enabled property. // 07/02/2009 - Pinal C. Patel // Modified state alternating properties to restart the server when changed. // 07/17/2009 - Pinal C. Patel // Modified SharedSecret to be persisted as an encrypted value. // 08/05/2009 - Josh L. Patterson // Edited Comments. // 09/14/2009 - Stephen C. Wills // Added new header and license agreement. // 04/14/2011 - Pinal C. Patel // Updated to use new serialization methods in GSF.Serialization class. // 12/02/2011 - J. Ritchie Carroll // Updated event data publication to provide "copy" of reusable buffer instead of original // buffer since you cannot assume how user will use the buffer (they may cache it). // 12/04/2011 - J. Ritchie Carroll // Modified to use concurrent dictionary. // 04/26/2012 - Pinal C. Patel // Updated Create() static method to apply settings from the configuration string to the created // server instance using reflection. // 12/13/2012 - Starlynn Danyelle Gilliam // Modified Header. // 05/22/2015 - J. Ritchie Carroll // Added ZeroMQ to the create IServer options. // //****************************************************************************************************** using System; using System.Collections.Concurrent; using System.Collections.Generic; using System.ComponentModel; using System.Configuration; using System.Drawing; using System.Globalization; using System.Linq; using System.Reflection; using System.Text; using System.Threading; using GSF.Configuration; using GSF.Diagnostics; using GSF.Units; // ReSharper disable VirtualMemberCallInConstructor // ReSharper disable ConvertToAutoPropertyWhenPossible namespace GSF.Communication { /// <summary> /// Base class for a server involved in server-client communication. /// </summary> [ToolboxBitmap(typeof(ServerBase))] public abstract class ServerBase : Component, IServer, ISupportInitialize, IPersistSettings { #region [ Members ] // Constants /// <summary> /// Specifies the default value for the <see cref="MaxClientConnections"/> property. /// </summary> public const int DefaultMaxClientConnections = -1; /// <summary> /// Specifies the default value for the <see cref="SendBufferSize"/> property. /// </summary> public const int DefaultSendBufferSize = 32768; /// <summary> /// Specifies the default value for the <see cref="ReceiveBufferSize"/> property. /// </summary> public const int DefaultReceiveBufferSize = 32768; /// <summary> /// Specifies the default value for the <see cref="PersistSettings"/> property. /// </summary> public const bool DefaultPersistSettings = false; /// <summary> /// Specifies the default value for the <see cref="SettingsCategory"/> property. /// </summary> public const string DefaultSettingsCategory = "CommunicationServer"; // Events /// <summary> /// Occurs when the server is started. /// </summary> [Category("Server")] [Description("Occurs when the server is started.")] public event EventHandler ServerStarted; /// <summary> /// Occurs when the server is stopped. /// </summary> [Category("Server")] [Description("Occurs when the server is stopped.")] public event EventHandler ServerStopped; /// <summary> /// Occurs when a client connects to the server. /// </summary> /// <remarks> /// <see cref="EventArgs{T}.Argument"/> is the ID of the client that connected to the server. /// </remarks> [Category("Client")] [Description("Occurs when a client connects to the server.")] public event EventHandler<EventArgs<Guid>> ClientConnected; /// <summary> /// Occurs when a client disconnects from the server. /// </summary> /// <remarks> /// <see cref="EventArgs{T}.Argument"/> is the ID of the client that disconnected from the server. /// </remarks> [Category("Client")] [Description("Occurs when a client disconnects from the server.")] public event EventHandler<EventArgs<Guid>> ClientDisconnected; /// <summary> /// Occurs when an exception is encountered while a client is connecting. /// </summary> /// <remarks> /// <see cref="EventArgs{T}.Argument"/> is the <see cref="Exception"/> encountered when connecting to the client. /// </remarks> [Category("Client")] [Description("Occurs when an Exception is encountered when connecting to a client.")] public event EventHandler<EventArgs<Exception>> ClientConnectingException; /// <summary> /// Occurs when data is being sent to a client. /// </summary> /// <remarks> /// <see cref="EventArgs{T}.Argument"/> is the ID of the client to which the data is being sent. /// </remarks> [Category("Data")] [Description("Occurs when data is being sent to a client.")] public event EventHandler<EventArgs<Guid>> SendClientDataStart; /// <summary> /// Occurs when data has been sent to a client. /// </summary> /// <remarks> /// <see cref="EventArgs{T}.Argument"/> is the ID of the client to which the data has been sent. /// </remarks> [Category("Data")] [Description("Occurs when data has been sent to a client.")] public event EventHandler<EventArgs<Guid>> SendClientDataComplete; /// <summary> /// Occurs when an <see cref="Exception"/> is encountered when sending data to a client. /// </summary> /// <remarks> /// <see cref="EventArgs{T1,T2}.Argument1"/> is the ID of the client to which the data was being sent.<br/> /// <see cref="EventArgs{T1,T2}.Argument2"/> is the <see cref="Exception"/> encountered when sending data to a client. /// </remarks> [Category("Data")] [Description("Occurs when an Exception is encountered when sending data to a client.")] public event EventHandler<EventArgs<Guid, Exception>> SendClientDataException; /// <summary> /// Occurs when unprocessed data has been received from a client. /// </summary> /// <remarks> /// <para> /// This event can be used to receive a notification that client data has arrived. The <see cref="Read"/> method can then be used /// to copy data to an existing buffer. In many cases it will be optimal to use an existing buffer instead of subscribing to the /// <see cref="ReceiveClientDataComplete"/> event, however, the data that is available after calling the <see cref="Read"/> method /// will be the original unprocessed data received by the client, i.e., not optionally decrypted or decompressed data. /// </para> /// <para> /// <see cref="EventArgs{T1,T2}.Argument1"/> is the ID of the client from which data is received.<br/> /// <see cref="EventArgs{T1,T2}.Argument2"/> is the number of bytes received in the buffer from the client. /// </para> /// </remarks> [Category("Data")] [Description("Occurs when unprocessed data has been received from a client.")] public event EventHandler<EventArgs<Guid, int>> ReceiveClientData; /// <summary> /// Occurs when data received from a client has been processed and is ready for consumption. /// </summary> /// <remarks> /// <see cref="EventArgs{T1,T2,T3}.Argument1"/> is the ID of the client from which data is received.<br/> /// <see cref="EventArgs{T1,T2,T3}.Argument2"/> is a new buffer containing post-processed data received from the client starting at index zero.<br/> /// <see cref="EventArgs{T1,T2,T3}.Argument3"/> is the number of post-processed bytes received in the buffer from the client. /// </remarks> [Category("Data")] [Description("Occurs when data received from a client has been processed and is ready for consumption.")] public event EventHandler<EventArgs<Guid, byte[], int>> ReceiveClientDataComplete; /// <summary> /// Occurs when an <see cref="Exception"/> is encountered when receiving data from a client. /// </summary> /// <remarks> /// <see cref="EventArgs{T1,T2}.Argument1"/> is the ID of the client from which the data was being received.<br/> /// <see cref="EventArgs{T1,T2}.Argument2"/> is the <see cref="Exception"/> encountered when receiving data from a client. /// </remarks> [Category("Data")] [Description("Occurs when an Exception is encountered when receiving data from a client.")] public event EventHandler<EventArgs<Guid, Exception>> ReceiveClientDataException; /// <summary> /// Occurs when an <see cref="Exception"/> is encountered in a user-defined function via an event dispatch. /// </summary> /// <remarks> /// <see cref="EventArgs{T}.Argument"/> is the <see cref="Exception"/> thrown by the user-defined function. /// </remarks> [Category("User")] [Description("Occurs when an Exception is encountered when calling a user-defined function.")] public event EventHandler<EventArgs<Exception>> UnhandledUserException; // Fields private string m_configurationString; private int m_maxClientConnections; private int m_sendBufferSize; private int m_receiveBufferSize; private Encoding m_textEncoding; private readonly TransportProtocol m_transportProtocol; private readonly ConcurrentDictionary<Guid, int> m_clientIDs; private Ticks m_stopTime; private Ticks m_startTime; #endregion #region [ Constructors ] /// <summary> /// Initializes a new instance of the server. /// </summary> protected ServerBase() { ServerID = Guid.NewGuid(); m_clientIDs = new ConcurrentDictionary<Guid, int>(); m_textEncoding = Encoding.ASCII; CurrentState = ServerState.NotRunning; m_maxClientConnections = DefaultMaxClientConnections; m_sendBufferSize = DefaultSendBufferSize; m_receiveBufferSize = DefaultReceiveBufferSize; PersistSettings = DefaultPersistSettings; Name = DefaultSettingsCategory; } /// <summary> /// Initializes a new instance of the server. /// </summary> /// <param name="transportProtocol">One of the <see cref="TransportProtocol"/> values.</param> /// <param name="configurationString">The data used by the server for initialization.</param> protected ServerBase(TransportProtocol transportProtocol, string configurationString) : this() { m_transportProtocol = transportProtocol; ConfigurationString = configurationString; } #endregion #region [ Properties ] /// <summary> /// Gets or sets the data required by the server to initialize. /// </summary> [Category("Settings")] [Description("The data that is required by the server to initialize.")] public virtual string ConfigurationString { get => m_configurationString; set { ValidateConfigurationString(value); m_configurationString = value; ReStart(); } } /// <summary> /// Gets or sets the maximum number of clients that can connect to the server. /// </summary> /// <remarks> /// Set <see cref="MaxClientConnections"/> to -1 to allow infinite client connections. /// </remarks> [Category("Settings")] [DefaultValue(DefaultMaxClientConnections)] [Description("The maximum number of clients that can connect to the server. Set MaxClientConnections to -1 to allow infinite client connections.")] public virtual int MaxClientConnections { get => m_maxClientConnections; set => m_maxClientConnections = value < 1 ? -1 : value; } /// <summary> /// Gets or sets the size of the buffer used by the server for sending data to the clients. /// </summary> /// <exception cref="ArgumentException">The value being assigned is either zero or negative.</exception> [Category("Data")] [DefaultValue(DefaultSendBufferSize)] [Description("The size of the buffer used by the server for receiving data from the clients.")] public virtual int SendBufferSize { get => m_sendBufferSize; set { if (value < 1) throw new ArgumentException("Value cannot be zero or negative"); m_sendBufferSize = value; } } /// <summary> /// Gets or sets the size of the buffer used by the server for receiving data from the clients. /// </summary> /// <exception cref="ArgumentException">The value being assigned is either zero or negative.</exception> [Category("Data")] [DefaultValue(DefaultReceiveBufferSize)] [Description("The size of the buffer used by the server for receiving data from the clients.")] public virtual int ReceiveBufferSize { get => m_receiveBufferSize; set { if (value < 1) throw new ArgumentException("Value cannot be zero or negative"); m_receiveBufferSize = value; } } /// <summary> /// Gets or sets a boolean value that indicates whether the server settings are to be saved to the config file. /// </summary> [Category("Persistence")] [DefaultValue(DefaultPersistSettings)] [Description("Indicates whether the server settings are to be saved to the config file.")] public bool PersistSettings { get; set; } /// <summary> /// Gets or sets the category under which the server settings are to be saved to the config file if the <see cref="PersistSettings"/> property is set to true. /// </summary> /// <exception cref="ArgumentNullException">The value being assigned is a null or empty string.</exception> [Category("Persistence")] [DefaultValue(DefaultSettingsCategory)] [Description("Category under which the server settings are to be saved to the config file if the PersistSettings property is set to true.")] public string SettingsCategory { get => Name; set { if (string.IsNullOrEmpty(value)) throw new ArgumentNullException(nameof(value)); Name = value; } } /// <summary> /// Gets or sets a boolean value that indicates whether the server is currently enabled. /// </summary> /// <remarks> /// Setting <see cref="Enabled"/> to true will start the server if it is not running, setting /// to false will stop the server if it is running. /// </remarks> [Browsable(false)] [DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)] public bool Enabled { get => CurrentState == ServerState.Running; set { if (value && !Enabled) Start(); else if (!value && Enabled) Stop(); } } /// <summary> /// Gets a flag that indicates whether the object has been disposed. /// </summary> [Browsable(false)] [DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)] public bool IsDisposed { get; private set; } /// <summary> /// Gets or sets the <see cref="Encoding"/> to be used for the text sent to the connected clients. /// </summary> [Browsable(false)] [DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)] public virtual Encoding TextEncoding { get => m_textEncoding; set => m_textEncoding = value; } /// <summary> /// Gets the current <see cref="ServerState"/>. /// </summary> [Browsable(false)] public ServerState CurrentState { get; private set; } /// <summary> /// Gets the <see cref="TransportProtocol"/> used by the server for the transportation of data with the clients. /// </summary> [Browsable(false)] public virtual TransportProtocol TransportProtocol => m_transportProtocol; /// <summary> /// Gets the server's ID. /// </summary> [Browsable(false)] public virtual Guid ServerID { get; } /// <summary> /// Gets the IDs of clients connected to the server. /// </summary> [Browsable(false)] public virtual Guid[] ClientIDs => m_clientIDs.Keys.ToArray(); /// <summary> /// Gets the <see cref="Time"/> for which the server has been running. /// </summary> [Browsable(false)] public virtual Time RunTime { get { Time serverRunTime = 0.0D; if (m_startTime > 0) { if (CurrentState == ServerState.Running) { // Server is running. serverRunTime = (DateTime.UtcNow.Ticks - m_startTime).ToSeconds(); } else { // Server is not running. serverRunTime = (m_stopTime - m_startTime).ToSeconds(); } } return serverRunTime; } } /// <summary> /// Gets current read indices for received data buffers incremented at each <see cref="Read"/> call. /// </summary> protected ConcurrentDictionary<Guid, int> ReadIndicies => m_clientIDs; /// <summary> /// Gets the unique identifier of the server. /// </summary> [Browsable(false)] public string Name { get; private set; } /// <summary> /// Gets the descriptive status of the server. /// </summary> [Browsable(false)] public virtual string Status { get { StringBuilder status = new StringBuilder(); status.Append(" Server state: "); status.Append(CurrentState); status.AppendLine(); status.Append(" Server runtime: "); status.Append(RunTime.ToString(3)); status.AppendLine(); status.Append(" Connected clients: ");