//******************************************************************************************************
// ClientBase.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 client 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 altering properties to reconnect the client when changed.
// 07/08/2009 - J. Ritchie Carroll
// Added WaitHandle return value from asynchronous connection.
// 07/15/2009 - Pinal C. Patel
// Modified Connect() to wait for post-connection processing to complete.
// 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.
// 11/29/2010 - Pinal C. Patel
// Updated the implementation of Connect() method so it blocks correctly after updates made to
// ConnectAsync() method in the derived classes.
// 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/29/2011 - J. Ritchie Carroll
// Updated Status property to show ConnectionString information.
// 04/26/2012 - Pinal C. Patel
// Updated Create() static method to apply settings from the connection string to the created
// client instance using reflection.
// 05/22/2015 - J. Ritchie Carroll
// Added ZeroMQ to the create IClient options.
//
//******************************************************************************************************
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Configuration;
using System.Drawing;
using System.Reflection;
using System.Text;
using System.Threading;
using GSF.Configuration;
using GSF.Units;
namespace GSF.Communication
{
///
/// Base class for a client involved in server-client communication.
///
[ToolboxBitmap(typeof(ClientBase))]
public abstract class ClientBase : Component, IClient, ISupportInitialize, IPersistSettings
{
#region [ Members ]
// Constants
///
/// Specifies the default value for the property.
///
public const int DefaultMaxConnectionAttempts = -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 = "CommunicationClient";
// Events
///
/// Occurs when client is attempting connection to the server.
///
[Category("Connection"),
Description("Occurs when client is attempting connection to the server.")]
public event EventHandler ConnectionAttempt;
///
/// Occurs when client connection to the server is established.
///
[Category("Connection"),
Description("Occurs when client connection to the server is established.")]
public event EventHandler ConnectionEstablished;
///
/// Occurs when client connection to the server is terminated.
///
[Category("Connection"),
Description("Occurs when client connection to the server is terminated")]
public event EventHandler ConnectionTerminated;
///
/// Occurs when an is encountered during connection attempt to the server.
///
[Category("Connection"),
Description("Occurs when an Exception is encountered during connection attempt to the server.")]
public event EventHandler> ConnectionException;
///
/// Occurs when the client begins sending data to the server.
///
[Category("Data"),
Description("Occurs when the client begins sending data to the server.")]
public event EventHandler SendDataStart;
///
/// Occurs when the client has successfully sent data to the server.
///
[Category("Data"),
Description("Occurs when the client has successfully sent data to the server.")]
public event EventHandler SendDataComplete;
///
/// Occurs when an is encountered when sending data to the server.
///
///
/// is the encountered when sending data to the server.
///
[Category("Data"),
Description("Occurs when an Exception is encountered when sending data to the server.")]
public event EventHandler> SendDataException;
///
/// Occurs when unprocessed data has been received from the server.
///
///
///
/// This event can be used to receive a notification that server 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.
///
///
/// is the number of bytes received in the buffer from the server.
///
///
[Category("Data"),
Description("Occurs when unprocessed data has been received from the server.")]
public event EventHandler> ReceiveData;
///
/// Occurs when data received from the server has been processed and is ready for consumption.
///
///
/// is a new buffer containing post-processed data received from the server starting at index zero.
/// is the number of post-processed bytes received in the buffer from the server.
///
[Category("Data"),
Description("Occurs when data received from the server has been processed and is ready for consumption.")]
public event EventHandler> ReceiveDataComplete;
///
/// Occurs when an is encountered when receiving data from the server.
///
///
/// is the encountered when receiving data from the server.
///
[Category("Data"),
Description("Occurs when an Exception is encountered when receiving data from the server.")]
public event EventHandler> ReceiveDataException;
///
/// 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_connectionString;
private int m_maxConnectionAttempts;
private int m_sendBufferSize;
private int m_receiveBufferSize;
private bool m_persistSettings;
private string m_settingsCategory;
private Encoding m_textEncoding;
private ClientState m_currentState;
private readonly TransportProtocol m_transportProtocol;
private readonly TransportStatistics m_transportStatistics;
private Ticks m_connectTime;
private Ticks m_disconnectTime;
private ManualResetEvent m_connectHandle;
private int m_readIndex;
private bool m_initialized;
private bool m_disposed;
private Action m_updateBytesSent;
private Action m_updateBytesReceived;
#endregion
#region [ Constructors ]
///
/// Initializes a new instance of the client.
///
protected ClientBase()
{
m_textEncoding = Encoding.ASCII;
m_currentState = ClientState.Disconnected;
m_maxConnectionAttempts = DefaultMaxConnectionAttempts;
m_sendBufferSize = DefaultSendBufferSize;
m_receiveBufferSize = DefaultReceiveBufferSize;
m_persistSettings = DefaultPersistSettings;
m_settingsCategory = DefaultSettingsCategory;
m_transportStatistics = new TransportStatistics();
m_updateBytesSent = TrackStatistics ? UpdateBytesSent : new Action(bytes => { });
m_updateBytesReceived = TrackStatistics ? UpdateBytesReceived : new Action(bytes => { });
}
///
/// Initializes a new instance of the client.
///
/// One of the values.
/// The data used by the client for connection to a server.
protected ClientBase(TransportProtocol transportProtocol, string connectionString)
: this()
{
m_transportProtocol = transportProtocol;
ConnectionString = connectionString;
}
#endregion
#region [ Properties ]
///
/// Gets the server URI.
///
public abstract string ServerUri
{
get;
}
///
/// Gets or sets the data required by the client to connect to the server.
///
[Category("Settings"),
Description("The data required by the client to connect to the server.")]
public virtual string ConnectionString
{
get
{
return m_connectionString;
}
set
{
ValidateConnectionString(value);
m_connectionString = value;
ReConnect();
}
}
///
/// Gets or sets the maximum number of times the client will attempt to connect to the server.
///
/// Set to -1 for infinite connection attempts.
[Category("Settings"),
DefaultValue(DefaultMaxConnectionAttempts),
Description("The maximum number of times the client will attempt to connect to the server. Set MaxConnectionAttempts to -1 for infinite connection attempts.")]
public virtual int MaxConnectionAttempts
{
get
{
return m_maxConnectionAttempts;
}
set
{
if (value < 1)
m_maxConnectionAttempts = -1;
else
m_maxConnectionAttempts = value;
}
}
///
/// Gets or sets the size of the buffer used by the client for sending data to the server.
///
/// The value being assigned is either zero or negative.
[Category("Data"),
DefaultValue(DefaultSendBufferSize),
Description("The size of the buffer used by the client for receiving data from the server.")]
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 client for receiving data from the server.
///
/// The value being assigned is either zero or negative.
[Category("Data"),
DefaultValue(DefaultReceiveBufferSize),
Description("The size of the buffer used by the client for receiving data from the server.")]
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 client settings are to be saved to the config file.
///
[Category("Persistence"),
DefaultValue(DefaultPersistSettings),
Description("Indicates whether the client 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 client 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 client 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 client is currently enabled.
///
///
/// Setting to true will start connection cycle for the client if it
/// is not connected, setting to false will disconnect the client if it is connected.
///
[Browsable(false),
DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)]
public bool Enabled
{
get
{
return m_currentState == ClientState.Connected;
}
set
{
if (value && !Enabled)
Connect();
else if (!value && Enabled)
Disconnect();
}
}
///
/// 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 server.
///
[Browsable(false),
DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)]
public virtual Encoding TextEncoding
{
get
{
return m_textEncoding;
}
set
{
m_textEncoding = value;
}
}
///
/// Gets the current .
///
[Browsable(false)]
public virtual ClientState CurrentState
{
get
{
return m_currentState;
}
}
///
/// Gets the used by the client for the transportation of data with the server.
///
[Browsable(false)]
public virtual TransportProtocol TransportProtocol
{
get
{
return m_transportProtocol;
}
}
///
/// Gets the for which the client has been connected to the server.
///
[Browsable(false)]
public virtual Time ConnectionTime
{
get
{
Time clientConnectionTime = 0.0D;
if (m_connectTime > 0)
{
if (m_currentState == ClientState.Connected)
{
// Client is connected to the server.
clientConnectionTime = (DateTime.UtcNow.Ticks - m_connectTime).ToSeconds();
}
else
{
// Client is not connected to the server.
clientConnectionTime = (m_disconnectTime - m_connectTime).ToSeconds();
}
}
return clientConnectionTime;
}
}
///
/// Gets or sets current read index for received data buffer incremented at each call.
///
protected int ReadIndex
{
get
{
return m_readIndex;
}
set
{
m_readIndex = value;
}
}
///
/// Gets the unique identifier of the client.
///
[Browsable(false)]
public virtual string Name
{
get
{
return m_settingsCategory;
}
}
///
/// Gets the for the client connection.
///
public TransportStatistics Statistics
{
get
{
return m_transportStatistics;
}
}
///
/// Determines whether the base class should track statistics.
///
protected virtual bool TrackStatistics
{
get
{
return true;
}
}
///
/// Gets the descriptive status of the client.
///
[Browsable(false)]
public virtual string Status
{
get
{
StringBuilder status = new StringBuilder();
status.Append(" Client state: ");
status.Append(m_currentState);
status.AppendLine();
status.Append(" Connection time: ");
status.Append(ConnectionTime.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();
}
}
#endregion
#region [ Methods ]
///
/// 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.
///
/// 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(byte[] buffer, int startIndex, int length);
///
/// When overridden in a derived class, validates the specified .
///
/// The connection string to be validated.
protected abstract void ValidateConnectionString(string connectionString);
///
/// When overridden in a derived class, sends data to the server 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 .
/// for the asynchronous operation.
protected abstract WaitHandle SendDataAsync(byte[] data, int offset, int length);
///
/// Initializes the client.
///
///
/// is to be called by user-code directly only if the client 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 client 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.
///
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 client 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.
///
public void EndInit()
{
if (!DesignMode)
{
try
{
Initialize();
}
catch (Exception)
{
// Prevent the IDE from crashing when component is in design mode.
}
}
}
///
/// Saves client 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["ConnectionString", true].Update(m_connectionString);
settings["MaxConnectionAttempts", true].Update(m_maxConnectionAttempts);
settings["SendBufferSize", true].Update(m_sendBufferSize);
settings["ReceiveBufferSize", true].Update(m_receiveBufferSize);
config.Save();
}
}
///
/// Loads saved client 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("ConnectionString", m_connectionString, "Data required by the client to connect to the server.");
settings.Add("MaxConnectionAttempts", m_maxConnectionAttempts, "Maximum number of times the client will attempt to connect to the server.");
settings.Add("SendBufferSize", m_sendBufferSize, "Size of the buffer used by the client for sending data from the server.");
settings.Add("ReceiveBufferSize", m_receiveBufferSize, "Size of the buffer used by the client for receiving data from the server.");
ConnectionString = settings["ConnectionString"].ValueAs(m_connectionString);
MaxConnectionAttempts = settings["MaxConnectionAttempts"].ValueAs(m_maxConnectionAttempts);
SendBufferSize = settings["SendBufferSize"].ValueAs(m_sendBufferSize);
ReceiveBufferSize = settings["ReceiveBufferSize"].ValueAs(m_receiveBufferSize);
}
}
///
/// Connects the client to the server synchronously.
///
public virtual void Connect()
{
// Start asynchronous connection attempt.
ConnectAsync();
// Block until connection is established.
do
{
Thread.Sleep(100);
}
while (m_currentState == ClientState.Connecting);
}
///
/// Connects the client to the server asynchronously.
///
/// Server property in is invalid.
/// Attempt is made to connect the client when it is not disconnected.
/// for the asynchronous operation.
///
/// Derived classes are expected to override this method with protocol specific connection operations. Call the base class
/// method to obtain an operational wait handle if protocol connection operation doesn't provide one already.
///
public virtual WaitHandle ConnectAsync()
{
if (CurrentState == ClientState.Disconnected)
{
// Initialize if uninitialized.
if (!m_initialized)
Initialize();
// Set up connection event wait handle
m_connectHandle = new ManualResetEvent(false);
return m_connectHandle;
}
throw new InvalidOperationException("Client is currently not disconnected");
}
///
/// Sends data to the server synchronously.
///
/// The plain-text data that is to be sent.
public virtual void Send(string data)
{
Send(m_textEncoding.GetBytes(data));
}
///
/// Sends data to the server synchronously.
///
/// The serializable object that is to be sent.
public virtual void Send(object serializableObject)
{
Send(Serialization.Serialize(serializableObject, SerializationFormat.Binary));
}
///
/// Sends data to the server synchronously.
///
/// The binary data that is to be sent.
public virtual void Send(byte[] data)
{
Send(data, 0, data.Length);
}
///
/// Sends data to the server 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 Send(byte[] data, int offset, int length)
{
SendAsync(data, offset, length).WaitOne();
}
///
/// Sends data to the server asynchronously.
///
/// The plain-text data that is to be sent.
/// for the asynchronous operation.
public virtual WaitHandle SendAsync(string data)
{
return SendAsync(m_textEncoding.GetBytes(data));
}
///
/// Sends data to the server asynchronously.
///
/// The serializable object that is to be sent.
/// for the asynchronous operation.
public virtual WaitHandle SendAsync(object serializableObject)
{
return SendAsync(Serialization.Serialize(serializableObject, SerializationFormat.Binary));
}
///
/// Sends data to the server asynchronously.
///
/// The binary data that is to be sent.
/// for the asynchronous operation.
public virtual WaitHandle SendAsync(byte[] data)
{
return SendAsync(data, 0, data.Length);
}
///
/// Sends data to the server 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 .
/// for the asynchronous operation.
public virtual WaitHandle SendAsync(byte[] data, int offset, int length)
{
if (m_currentState == ClientState.Connected)
{
// Update transport statistics
m_updateBytesSent(length);
// Initiate send operation
return SendDataAsync(data, offset, length);
}
throw new InvalidOperationException("Client is not connected");
}
///
/// When overridden in a derived class, disconnects client from the server synchronously.
///
public virtual void Disconnect()
{
m_currentState = ClientState.Disconnected;
}
///
/// Updates the pertaining to bytes sent.
///
///
protected void UpdateBytesSent(int bytes)
{
m_transportStatistics.LastSend = DateTime.UtcNow;
m_transportStatistics.LastBytesSent = bytes;
m_transportStatistics.TotalBytesSent += bytes;
}
///
/// Updates the pertaining to bytes received.
///
///
protected void UpdateBytesReceived(int bytes)
{
m_transportStatistics.LastReceive = DateTime.UtcNow;
m_transportStatistics.LastBytesReceived = bytes;
m_transportStatistics.TotalBytesReceived += bytes;
}
///
/// Raises the event.
///
protected virtual void OnConnectionAttempt()
{
try
{
m_currentState = ClientState.Connecting;
if (ConnectionAttempt != null)
ConnectionAttempt(this, EventArgs.Empty);
}
catch (Exception userException)
{
OnUnhandledUserException(userException);
}
}
///
/// Raises the event.
///
protected virtual void OnConnectionEstablished()
{
try
{
m_currentState = ClientState.Connected;
m_disconnectTime = 0;
// Save the time when the client connected to the server.
m_connectTime = DateTime.UtcNow.Ticks;
// Signal any waiting threads about successful connection.
if (m_connectHandle != null)
m_connectHandle.Set();
if (ConnectionEstablished != null)
ConnectionEstablished(this, EventArgs.Empty);
}
catch (Exception userException)
{
OnUnhandledUserException(userException);
}
}
///
/// Raises the event.
///
protected virtual void OnConnectionTerminated()
{
try
{
m_currentState = ClientState.Disconnected;
// Save the time when client was disconnected from the server.
m_disconnectTime = DateTime.UtcNow.Ticks;
if (ConnectionTerminated != null)
ConnectionTerminated(this, EventArgs.Empty);
}
catch (Exception userException)
{
OnUnhandledUserException(userException);
}
}
///
/// Raises the event.
///
/// Exception to send to event.
protected virtual void OnConnectionException(Exception ex)
{
try
{
if (!(ex is ObjectDisposedException) && ConnectionException != null)
ConnectionException(this, new EventArgs(ex));
}
catch (Exception userException)
{
OnUnhandledUserException(userException);
}
}
///
/// Raises the event.
///
protected virtual void OnSendDataStart()
{
try
{
if (SendDataStart != null)
SendDataStart(this, EventArgs.Empty);
}
catch (Exception userException)
{
OnUnhandledUserException(userException);
}
}
///
/// Raises the event.
///
protected virtual void OnSendDataComplete()
{
try
{
if (SendDataComplete != null)
SendDataComplete(this, EventArgs.Empty);
}
catch (Exception userException)
{
OnUnhandledUserException(userException);
}
}
///
/// Raises the event.
///
/// Exception to send to event.
protected virtual void OnSendDataException(Exception ex)
{
try
{
if (!(ex is ObjectDisposedException) && SendDataException != null)
SendDataException(this, new EventArgs(ex));
}
catch (Exception userException)
{
OnUnhandledUserException(userException);
}
}
///
/// Raises the event.
///
/// 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 client
/// implementations need to explicitly raise this event.
///
protected virtual void OnReceiveData(int size)
{
try
{
if (ReceiveData != null)
ReceiveData(this, new EventArgs(size));
}
catch (Exception userException)
{
OnUnhandledUserException(userException);
}
}
///
/// Raises the event.
///
/// Data received from the client.
/// Number of bytes received from the client.
protected virtual void OnReceiveDataComplete(byte[] data, int size)
{
try
{
// Update transport statistics
m_updateBytesReceived(size);
// Reset buffer index used by read method
m_readIndex = 0;
// Notify users of data ready
if (ReceiveData != null)
ReceiveData(this, new EventArgs(size));
if (ReceiveDataComplete != 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)
ReceiveDataComplete(this, new EventArgs(data.BlockCopy(0, size), size));
}
}
catch (Exception userException)
{
OnUnhandledUserException(userException);
}
}
///
/// Raises the event.
///
/// Exception to send to event.
protected virtual void OnReceiveDataException(Exception ex)
{
try
{
if (!(ex is ObjectDisposedException) && ReceiveDataException != null)
ReceiveDataException(this, new EventArgs(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 exceptions in user-defined exception handling
// code, as there's nothing we can reasonably do about it.
}
}
///
/// Releases the unmanaged resources used by the client 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().
Disconnect();
SaveSettings();
if ((object)m_connectHandle != null)
m_connectHandle.Dispose();
}
}
finally
{
m_disposed = true; // Prevent duplicate dispose.
base.Dispose(disposing); // Call base class Dispose().
}
}
}
///
/// Re-connects the client if currently connected.
///
private void ReConnect()
{
if (m_currentState == ClientState.Connected)
{
Disconnect();
while (m_currentState != ClientState.Disconnected)
{
Thread.Sleep(100);
}
Connect();
}
}
#endregion
#region [ Static ]
///
/// Create a communications client
///
///
/// Note that typical connection string should be prefixed with a "protocol=tcp", "protocol=udp", "protocol=serial" or "protocol=file"
///
/// A communications client.
/// Connection string for the client.
public static IClient Create(string connectionString)
{
Dictionary connectionSettings = connectionString.ParseKeyValuePairs();
IClient client;
string protocol;
if (connectionSettings.TryGetValue("protocol", out protocol))
{
connectionSettings.Remove("protocol");
StringBuilder settings = new StringBuilder();
foreach (string key in connectionSettings.Keys)
{
settings.Append(key);
settings.Append("=");
settings.Append(connectionSettings[key]);
settings.Append(";");
}
// Create a client instance for the specified protocol.
switch (protocol.Trim().ToLower())
{
case "tls":
client = new TlsClient(settings.ToString());
break;
case "tcp":
client = new TcpClient(settings.ToString());
break;
case "udp":
client = new UdpClient(settings.ToString());
break;
case "file":
client = new FileClient(settings.ToString());
break;
case "serial":
client = new SerialClient(settings.ToString());
break;
case "zeromq":
client = new ZeroMQClient(settings.ToString());
break;
default:
throw new ArgumentException(protocol + " is not a valid transport protocol");
}
// Apply client settings from the connection string to the client.
foreach (KeyValuePair setting in connectionSettings)
{
PropertyInfo property = client.GetType().GetProperty(setting.Key);
if (property != null)
property.SetValue(client, Convert.ChangeType(setting.Value, property.PropertyType), null);
}
}
else
{
throw new ArgumentException("Transport protocol must be specified");
}
return client;
}
#endregion
}
}