//****************************************************************************************************** // 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.Linq; using System.Reflection; using System.Text; using System.Threading; using GSF.Configuration; using GSF.Units; namespace GSF.Communication { /// /// Base class for a server involved in server-client communication. /// [ToolboxBitmap(typeof(ServerBase))] public abstract class ServerBase : Component, IServer, ISupportInitialize, IPersistSettings { #region [ Members ] // Constants /// /// Specifies the default value for the property. /// public const int DefaultMaxClientConnections = -1; /// /// Specifies the default value for the property. /// public const int DefaultSendBufferSize = 32768; /// /// Specifies the default value for the property. /// public const int DefaultReceiveBufferSize = 32768; /// /// Specifies the default value for the property. /// public const bool DefaultPersistSettings = false; /// /// Specifies the default value for the property. /// public const string DefaultSettingsCategory = "CommunicationServer"; // Events /// /// Occurs when the server is started. /// [Category("Server"), Description("Occurs when the server is started.")] public event EventHandler ServerStarted; /// /// Occurs when the server is stopped. /// [Category("Server"), Description("Occurs when the server is stopped.")] public event EventHandler ServerStopped; /// /// Occurs when a client connects to the server. /// /// /// is the ID of the client that connected to the server. /// [Category("Client"), Description("Occurs when a client connects to the server.")] public event EventHandler> ClientConnected; /// /// Occurs when a client disconnects from the server. /// /// /// is the ID of the client that disconnected from the server. /// [Category("Client"), Description("Occurs when a client disconnects from the server.")] public event EventHandler> ClientDisconnected; /// /// Occurs when an exception is encountered while a client is connecting. /// /// /// is the encountered when connecting to the client. /// [Category("Client"), Description("Occurs when an Exception is encountered when connecting to a client.")] public event EventHandler> ClientConnectingException; /// /// Occurs when data is being sent to a client. /// /// /// is the ID of the client to which the data is being sent. /// [Category("Data"), Description("Occurs when data is being sent to a client.")] public event EventHandler> SendClientDataStart; /// /// Occurs when data has been sent to a client. /// /// /// is the ID of the client to which the data has been sent. /// [Category("Data"), Description("Occurs when data has been sent to a client.")] public event EventHandler> SendClientDataComplete; /// /// Occurs when an is encountered when sending data to a client. /// /// /// is the ID of the client to which the data was being sent.
/// is the encountered when sending data to a client. ///
[Category("Data"), Description("Occurs when an Exception is encountered when sending data to a client.")] public event EventHandler> SendClientDataException; /// /// Occurs when unprocessed data has been received from a client. /// /// /// /// This event can be used to receive a notification that client data has arrived. The 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 /// event, however, the data that is available after calling the method /// will be the original unprocessed data received by the client, i.e., not optionally decrypted or decompressed data. /// /// /// is the ID of the client from which data is received.
/// is the number of bytes received in the buffer from the client. ///
///
[Category("Data"), Description("Occurs when unprocessed data has been received from a client.")] public event EventHandler> ReceiveClientData; /// /// Occurs when data received from a client has been processed and is ready for consumption. /// /// /// is the ID of the client from which data is received.
/// is a new buffer containing post-processed data received from the client starting at index zero.
/// is the number of post-processed bytes received in the buffer from the client. ///
[Category("Data"), Description("Occurs when data received from a client has been processed and is ready for consumption.")] public event EventHandler> ReceiveClientDataComplete; /// /// Occurs when an is encountered when receiving data from a client. /// /// /// is the ID of the client from which the data was being received.
/// is the encountered when receiving data from a client. ///
[Category("Data"), Description("Occurs when an Exception is encountered when receiving data from a client.")] public event EventHandler> ReceiveClientDataException; /// /// Occurs when an is encountered in a user-defined function via an event dispatch. /// /// /// is the thrown by the user-defined function. /// [Category("User"), Description("Occurs when an Exception is encountered when calling a user-defined function.")] public event EventHandler> UnhandledUserException; // Fields private string m_configurationString; private int m_maxClientConnections; private int m_sendBufferSize; private int m_receiveBufferSize; private bool m_persistSettings; private string m_settingsCategory; private Encoding m_textEncoding; private ServerState m_currentState; private readonly TransportProtocol m_transportProtocol; private readonly Guid m_serverID; private readonly ConcurrentDictionary m_clientIDs; private Ticks m_stopTime; private Ticks m_startTime; private bool m_initialized; private bool m_disposed; #endregion #region [ Constructors ] /// /// Initializes a new instance of the server. /// protected ServerBase() { m_serverID = Guid.NewGuid(); m_clientIDs = new ConcurrentDictionary(); m_textEncoding = Encoding.ASCII; m_currentState = ServerState.NotRunning; m_maxClientConnections = DefaultMaxClientConnections; m_sendBufferSize = DefaultSendBufferSize; m_receiveBufferSize = DefaultReceiveBufferSize; m_persistSettings = DefaultPersistSettings; m_settingsCategory = DefaultSettingsCategory; } /// /// Initializes a new instance of the server. /// /// One of the values. /// The data used by the server for initialization. protected ServerBase(TransportProtocol transportProtocol, string configurationString) : this() { m_transportProtocol = transportProtocol; ConfigurationString = configurationString; } #endregion #region [ Properties ] /// /// Gets or sets the data required by the server to initialize. /// [Category("Settings"), Description("The data that is required by the server to initialize.")] public virtual string ConfigurationString { get { return m_configurationString; } set { ValidateConfigurationString(value); m_configurationString = value; ReStart(); } } /// /// Gets or sets the maximum number of clients that can connect to the server. /// /// /// Set to -1 to allow infinite client connections. /// [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 { return m_maxClientConnections; } set { if (value < 1) m_maxClientConnections = -1; else m_maxClientConnections = value; } } /// /// Gets or sets the size of the buffer used by the server for sending data to the clients. /// /// The value being assigned is either zero or negative. [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 { return m_sendBufferSize; } set { if (value < 1) throw new ArgumentException("Value cannot be zero or negative"); m_sendBufferSize = value; } } /// /// Gets or sets the size of the buffer used by the server for receiving data from the clients. /// /// The value being assigned is either zero or negative. [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 { return m_receiveBufferSize; } set { if (value < 1) throw new ArgumentException("Value cannot be zero or negative"); m_receiveBufferSize = value; } } /// /// Gets or sets a boolean value that indicates whether the server settings are to be saved to the config file. /// [Category("Persistence"), DefaultValue(DefaultPersistSettings), Description("Indicates whether the server settings are to be saved to the config file.")] public bool PersistSettings { get { return m_persistSettings; } set { m_persistSettings = value; } } /// /// Gets or sets the category under which the server settings are to be saved to the config file if the property is set to true. /// /// The value being assigned is a null or empty string. [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 { return m_settingsCategory; } set { if (string.IsNullOrEmpty(value)) throw new ArgumentNullException(nameof(value)); m_settingsCategory = value; } } /// /// Gets or sets a boolean value that indicates whether the server is currently enabled. /// /// /// Setting to true will start the server if it is not running, setting /// to false will stop the server if it is running. /// [Browsable(false), DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)] public bool Enabled { get { return m_currentState == ServerState.Running; } set { if (value && !Enabled) Start(); else if (!value && Enabled) Stop(); } } /// /// Gets a flag that indicates whether the object has been disposed. /// [Browsable(false), DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)] public bool IsDisposed { get { return m_disposed; } } /// /// Gets or sets the to be used for the text sent to the connected clients. /// [Browsable(false), DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)] public virtual Encoding TextEncoding { get { return m_textEncoding; } set { m_textEncoding = value; } } /// /// Gets the current . /// [Browsable(false)] public ServerState CurrentState { get { return m_currentState; } } /// /// Gets the used by the server for the transportation of data with the clients. /// [Browsable(false)] public virtual TransportProtocol TransportProtocol { get { return m_transportProtocol; } } /// /// Gets the server's ID. /// [Browsable(false)] public virtual Guid ServerID { get { return m_serverID; } } /// /// Gets the IDs of clients connected to the server. /// [Browsable(false)] public virtual Guid[] ClientIDs { get { return m_clientIDs.Keys.ToArray(); } } /// /// Gets the for which the server has been running. /// [Browsable(false)] public virtual Time RunTime { get { Time serverRunTime = 0.0D; if (m_startTime > 0) { if (m_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; } } /// /// Gets current read indices for received data buffers incremented at each call. /// protected ConcurrentDictionary ReadIndicies { get { return m_clientIDs; } } /// /// Gets the unique identifier of the server. /// [Browsable(false)] public string Name { get { return m_settingsCategory; } } /// /// Gets the descriptive status of the server. /// [Browsable(false)] public virtual string Status { get { StringBuilder status = new StringBuilder(); status.Append(" Server state: "); status.Append(m_currentState); status.AppendLine(); status.Append(" Server runtime: "); status.Append(RunTime.ToString()); status.AppendLine(); status.Append(" Connected clients: "); status.Append(m_clientIDs.Count); status.AppendLine(); status.Append(" Maximum clients: "); status.Append(m_maxClientConnections == -1 ? "Infinite" : m_maxClientConnections.ToString()); status.AppendLine(); status.Append(" Receive buffer: "); status.Append(m_receiveBufferSize.ToString()); status.AppendLine(); status.Append(" Transport protocol: "); status.Append(m_transportProtocol.ToString()); status.AppendLine(); status.Append(" Text encoding used: "); status.Append(m_textEncoding.EncodingName); status.AppendLine(); return status.ToString(); } } /// /// Gets a boolean value that indicates /// whether the server has been initialized. /// protected bool Initialized { get { return m_initialized; } } #endregion #region [ Methods ] /// /// When overridden in a derived class, starts the server. /// public abstract void Start(); /// /// When overridden in a derived class, stops the server. /// public abstract void Stop(); /// /// When overridden in a derived class, 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. /// public abstract int Read(Guid clientID, byte[] buffer, int startIndex, int length); /// /// When overridden in a derived class, disconnects a connected client. /// /// ID of the client to be disconnected. public abstract void DisconnectOne(Guid clientID); /// /// When overridden in a derived class, validates the specified . /// /// The configuration string to be validated. protected abstract void ValidateConfigurationString(string configurationString); /// /// When overridden in a derived class, 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 abstract WaitHandle SendDataToAsync(Guid clientID, byte[] data, int offset, int length); /// /// Initializes the server. /// /// /// is to be called by user-code directly only if the server is not consumed through the designer surface of the IDE. /// public void Initialize() { if (m_initialized) return; LoadSettings(); // Load settings from the config file. m_initialized = true; // Initialize only once. } /// /// Performs necessary operations before the server properties are initialized. /// /// /// should never be called by user-code directly. This method exists solely for use by the designer if the server is consumed through /// the designer surface of the IDE. /// [EditorBrowsable(EditorBrowsableState.Never)] public void BeginInit() { if (!DesignMode) { try { // Nothing needs to be done before component is initialized. } catch (Exception) { // Prevent the IDE from crashing when component is in design mode. } } } /// /// Performs necessary operations after the server properties are initialized. /// /// /// should never be called by user-code directly. This method exists solely for use by the designer if the server is consumed through the /// designer surface of the IDE. /// [EditorBrowsable(EditorBrowsableState.Never)] public void EndInit() { if (!DesignMode) { try { Initialize(); } catch (Exception) { // Prevent the IDE from crashing when component is in design mode. } } } /// /// Saves server settings to the config file if the property is set to true. /// /// has a value of null or empty string. public virtual void SaveSettings() { if (m_persistSettings) { // Ensure that settings category is specified. if (string.IsNullOrEmpty(m_settingsCategory)) throw new ConfigurationErrorsException("SettingsCategory property has not been set"); // Save settings under the specified category. ConfigurationFile config = ConfigurationFile.Current; CategorizedSettingsElementCollection settings = config.Settings[m_settingsCategory]; settings["ConfigurationString", true].Update(m_configurationString); settings["MaxClientConnections", true].Update(m_maxClientConnections); settings["SendBufferSize", true].Update(m_sendBufferSize); settings["ReceiveBufferSize", true].Update(m_receiveBufferSize); config.Save(); } } /// /// Loads saved server settings from the config file if the property is set to true. /// /// has a value of null or empty string. public virtual void LoadSettings() { if (m_persistSettings) { // Ensure that settings category is specified. if (string.IsNullOrEmpty(m_settingsCategory)) throw new ConfigurationErrorsException("SettingsCategory property has not been set"); // Load settings from the specified category. ConfigurationFile config = ConfigurationFile.Current; CategorizedSettingsElementCollection settings = config.Settings[m_settingsCategory]; settings.Add("ConfigurationString", m_configurationString, "Data required by the server to initialize."); settings.Add("MaxClientConnections", m_maxClientConnections, "Maximum number of clients that can connect to the server."); settings.Add("SendBufferSize", m_sendBufferSize, "Size of the buffer used by the server for sending data to the clients."); settings.Add("ReceiveBufferSize", m_receiveBufferSize, "Size of the buffer used by the server for receiving data from the clients."); ConfigurationString = settings["ConfigurationString"].ValueAs(m_configurationString); MaxClientConnections = settings["MaxClientConnections"].ValueAs(m_maxClientConnections); SendBufferSize = settings["SendBufferSize"].ValueAs(m_sendBufferSize); ReceiveBufferSize = settings["ReceiveBufferSize"].ValueAs(m_receiveBufferSize); } } /// /// Sends data to the specified client synchronously. /// /// ID of the client to which the data is to be sent. /// The plain-text data that is to be sent. public virtual void SendTo(Guid clientID, string data) { SendTo(clientID, m_textEncoding.GetBytes(data)); } /// /// Sends data to the specified client synchronously. /// /// ID of the client to which the data is to be sent. /// The serializable object that is to be sent. public virtual void SendTo(Guid clientID, object serializableObject) { SendTo(clientID, Serialization.Serialize(serializableObject, SerializationFormat.Binary)); } /// /// Sends data to the specified client synchronously. /// /// ID of the client to which the data is to be sent. /// The binary data that is to be sent. public virtual void SendTo(Guid clientID, byte[] data) { SendTo(clientID, data, 0, data.Length); } /// /// Sends data to the specified client synchronously. /// /// 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 . public virtual void SendTo(Guid clientID, byte[] data, int offset, int length) { SendToAsync(clientID, data, offset, length).WaitOne(); } /// /// Sends data to all of the connected clients synchronously. /// /// The plain-text data that is to be sent. public virtual void Multicast(string data) { Multicast(m_textEncoding.GetBytes(data)); } /// /// Sends data to all of the connected clients synchronously. /// /// The serializable object that is to be sent. public virtual void Multicast(object serializableObject) { Multicast(Serialization.Serialize(serializableObject, SerializationFormat.Binary)); } /// /// Sends data to all of the connected clients synchronously. /// /// The binary data that is to be sent. public virtual void Multicast(byte[] data) { Multicast(data, 0, data.Length); } /// /// Sends data to all of the connected clients synchronously. /// /// 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 . public virtual void Multicast(byte[] data, int offset, int length) { // Perform asynchronous transmissions. WaitHandle[] handles = MulticastAsync(data, offset, length); // Wait for transmissions to complete. if (handles.Length > 0) WaitHandle.WaitAll(handles); } /// /// Disconnects all of the connected clients. /// public virtual void DisconnectAll() { foreach (Guid clientID in ClientIDs) { DisconnectOne(clientID); } } /// /// Sends data to the specified client asynchronously. /// /// ID of the client to which the data is to be sent. /// The plain-text data that is to be sent. /// for the asynchronous operation. public virtual WaitHandle SendToAsync(Guid clientID, string data) { return SendToAsync(clientID, m_textEncoding.GetBytes(data)); } /// /// Sends data to the specified client asynchronously. /// /// ID of the client to which the data is to be sent. /// The serializable object that is to be sent. /// for the asynchronous operation. public virtual WaitHandle SendToAsync(Guid clientID, object serializableObject) { return SendToAsync(clientID, Serialization.Serialize(serializableObject, SerializationFormat.Binary)); } /// /// Sends data to the specified client asynchronously. /// /// ID of the client to which the data is to be sent. /// The binary data that is to be sent. /// for the asynchronous operation. public virtual WaitHandle SendToAsync(Guid clientID, byte[] data) { return SendToAsync(clientID, data, 0, data.Length); } /// /// 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. public virtual WaitHandle SendToAsync(Guid clientID, byte[] data, int offset, int length) { if (m_currentState == ServerState.Running) return SendDataToAsync(clientID, data, offset, length); throw new InvalidOperationException("Server is not running"); } /// /// Sends data to all of the connected clients asynchronously. /// /// The plain-text data that is to be sent. /// Array of for the asynchronous operation. public virtual WaitHandle[] MulticastAsync(string data) { return MulticastAsync(m_textEncoding.GetBytes(data)); } /// /// Sends data to all of the connected clients asynchronously. /// /// The serializable object that is to be sent. /// Array of for the asynchronous operation. public virtual WaitHandle[] MulticastAsync(object serializableObject) { return MulticastAsync(Serialization.Serialize(serializableObject, SerializationFormat.Binary)); } /// /// Sends data to all of the connected clients asynchronously. /// /// The binary data that is to be sent. /// Array of for the asynchronous operation. public virtual WaitHandle[] MulticastAsync(byte[] data) { return MulticastAsync(data, 0, data.Length); } /// /// Sends data to all of the connected clients asynchronously. /// /// 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 . /// Array of for the asynchronous operation. public virtual WaitHandle[] MulticastAsync(byte[] data, int offset, int length) { List handles = new List(); foreach (Guid clientID in ClientIDs) { handles.Add(SendToAsync(clientID, data, offset, length)); } return handles.ToArray(); } /// /// Determines whether the given client is currently connected to the server. /// /// The ID of the client. /// True if the client is connected; false otherwise. public bool IsClientConnected(Guid clientID) { return m_clientIDs.ContainsKey(clientID); } /// /// Raises the event. /// protected virtual void OnServerStarted() { try { m_currentState = ServerState.Running; m_stopTime = 0; // Save the time when server is started. m_startTime = DateTime.UtcNow.Ticks; if (ServerStarted != null) ServerStarted(this, EventArgs.Empty); } catch (Exception userException) { OnUnhandledUserException(userException); } } /// /// Raises the event. /// protected virtual void OnServerStopped() { try { m_currentState = ServerState.NotRunning; // Save the time when server is stopped. m_stopTime = DateTime.UtcNow.Ticks; if (ServerStopped != null) ServerStopped(this, EventArgs.Empty); } catch (Exception userException) { OnUnhandledUserException(userException); } } /// /// Raises the event. /// /// ID of client to send to event. protected virtual void OnClientConnected(Guid clientID) { try { m_clientIDs.TryAdd(clientID, 0); if (ClientConnected != null) ClientConnected(this, new EventArgs(clientID)); } catch (Exception userException) { OnUnhandledUserException(userException); } } /// /// Raises the event. /// /// ID of client to send to event. protected virtual void OnClientDisconnected(Guid clientID) { try { int readIndex; m_clientIDs.TryRemove(clientID, out readIndex); if (ClientDisconnected != null) ClientDisconnected(this, new EventArgs(clientID)); } catch (Exception userException) { OnUnhandledUserException(userException); } } /// /// Raises the event. /// /// The encountered when connecting to the client. protected virtual void OnClientConnectingException(Exception ex) { try { if ((object)ClientConnectingException != null) ClientConnectingException(this, new EventArgs(ex)); } catch (Exception userException) { OnUnhandledUserException(userException); } } /// /// Raises the event. /// /// ID of client to send to event. protected virtual void OnSendClientDataStart(Guid clientID) { try { if (SendClientDataStart != null) SendClientDataStart(this, new EventArgs(clientID)); } catch (Exception userException) { OnUnhandledUserException(userException); } } /// /// Raises the event. /// /// ID of client to send to event. protected virtual void OnSendClientDataComplete(Guid clientID) { try { if (SendClientDataComplete != null) SendClientDataComplete(this, new EventArgs(clientID)); } catch (Exception userException) { OnUnhandledUserException(userException); } } /// /// Raises the event. /// /// ID of client to send to event. /// Exception to send to event. protected virtual void OnSendClientDataException(Guid clientID, Exception ex) { try { if (!(ex is ObjectDisposedException) && SendClientDataException != null) SendClientDataException(this, new EventArgs(clientID, ex)); } catch (Exception userException) { OnUnhandledUserException(userException); } } /// /// Raises the event. /// /// ID of the client from which data is received. /// Number of bytes received from the client. /// /// This event is automatically raised by call to so that inheritors /// never need to worry about raising this event. This method is only included here in case any custom server /// implementations need to explicitly raise this event. /// protected virtual void OnReceiveClientData(Guid clientID, int size) { try { if (ReceiveClientData != null) ReceiveClientData(this, new EventArgs(clientID, size)); } catch (Exception userException) { OnUnhandledUserException(userException); } } /// /// Raises the event. /// /// ID of the client from which data is received. /// Data received from the client. /// Number of bytes received from the client. protected virtual void OnReceiveClientDataComplete(Guid clientID, byte[] data, int size) { try { // Reset buffer index used by read method m_clientIDs[clientID] = 0; // Notify users of data ready if (ReceiveClientData != null) ReceiveClientData(this, new EventArgs(clientID, size)); if (ReceiveClientDataComplete != null) { // Most inheritors of this class "reuse" an existing buffer, as such you cannot assume what the user is going to do // with the buffer provided, so we pass in a "copy" of the buffer for the user since they may assume control of and // possibly even cache the provided buffer (e.g., passing the buffer to a process queue) ReceiveClientDataComplete(this, new EventArgs(clientID, data.BlockCopy(0, size), size)); } } catch (Exception userException) { OnUnhandledUserException(userException); } } /// /// Raises the event. /// /// ID of client to send to event. /// Exception to send to event. protected virtual void OnReceiveClientDataException(Guid clientID, Exception ex) { try { if (!(ex is ObjectDisposedException) && ReceiveClientDataException != null) ReceiveClientDataException(this, new EventArgs(clientID, ex)); } catch (Exception userException) { OnUnhandledUserException(userException); } } /// /// Raises the event. /// /// Exception to send to event. protected virtual void OnUnhandledUserException(Exception ex) { try { if ((object)UnhandledUserException != null) UnhandledUserException(this, new EventArgs(ex)); } catch { // Suppress unhandled exceptions in this handler. // Handling them would defeat the purpose of this event. } } /// /// Releases the unmanaged resources used by the server and optionally releases the managed resources. /// /// true to release both managed and unmanaged resources; false to release only unmanaged resources. protected override void Dispose(bool disposing) { if (!m_disposed) { try { // This will be done regardless of whether the object is finalized or disposed. if (disposing) { // This will be done only when the object is disposed by calling Dispose(). Stop(); SaveSettings(); } } finally { m_disposed = true; // Prevent duplicate dispose. base.Dispose(disposing); // Call base class Dispose(). } } } /// /// Re-starts the server if currently running. /// protected void ReStart() { if (m_currentState == ServerState.Running) { Stop(); while (m_currentState != ServerState.NotRunning) { Thread.Sleep(100); } Start(); } } #endregion #region [ Static ] /// /// Create a communications server /// /// /// Note that typical configuration string should be prefixed with a "protocol=tcp" or a "protocol=udp" /// /// The configuration string for the server. /// A communications server. public static IServer Create(string configurationString) { Dictionary configurationSettings = configurationString.ParseKeyValuePairs(); IServer server; string protocol; if (configurationSettings.TryGetValue("protocol", out protocol)) { configurationSettings.Remove("protocol"); StringBuilder settings = new StringBuilder(); foreach (string key in configurationSettings.Keys) { settings.Append(key); settings.Append("="); settings.Append(configurationSettings[key]); settings.Append(";"); } // Create a server instance for the specified protocol. switch (protocol.Trim().ToLower()) { case "tls": server = new TlsServer(settings.ToString()); break; case "tcp": server = new TcpServer(settings.ToString()); break; case "udp": server = new UdpServer(settings.ToString()); break; case "zeromq": server = new ZeroMQServer(settings.ToString()); break; default: throw new ArgumentException("Transport protocol \'" + protocol + "\' is not valid"); } // Apply server settings from the connection string to the client. foreach (KeyValuePair setting in configurationSettings) { PropertyInfo property = server.GetType().GetProperty(setting.Key); if (property != null) property.SetValue(server, Convert.ChangeType(setting.Value, property.PropertyType), null); } } else { throw new ArgumentException("Transport protocol must be specified"); } return server; } #endregion } }