//******************************************************************************************************
// 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("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
}
}