//******************************************************************************************************
// 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.Diagnostics.CodeAnalysis;
using System.Drawing;
using System.Reflection;
using System.Text;
using System.Threading;
using GSF.Configuration;
using GSF.Diagnostics;
using GSF.Units;
// ReSharper disable VirtualMemberCallInConstructor
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 string m_settingsCategory;
private Encoding m_textEncoding;
private ClientState m_currentState;
private readonly TransportProtocol m_transportProtocol;
private Ticks m_connectTime;
private Ticks m_disconnectTime;
private ManualResetEvent m_connectHandle;
private bool m_initialized;
private readonly Action m_updateBytesSent;
private readonly 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;
PersistSettings = DefaultPersistSettings;
m_settingsCategory = DefaultSettingsCategory;
Statistics = 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.
///
[Browsable(false)]
public abstract string ServerUri { get; }
///
/// Gets the current server index, when multiple server end points are defined.
///
[Browsable(false)]
public int ServerIndex { get; internal set; } = 0;
///
/// 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 => 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 => m_maxConnectionAttempts;
set => m_maxConnectionAttempts = value < 1 ? -1 : 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 => 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 => 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; set; }
///
/// 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 => 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 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 => 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; private set; }
///
/// Gets or sets the to be used for the text sent to the server.
///
[Browsable(false)]
[DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)]
public virtual Encoding TextEncoding
{
get => m_textEncoding;
set => m_textEncoding = value;
}
///
/// Gets the current .
///
[Browsable(false)]
public virtual ClientState CurrentState => m_currentState;
///
/// Gets the used by the client for the transportation of data with the server.
///
[Browsable(false)]
public virtual TransportProtocol TransportProtocol => 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; set; }
///
/// Gets the unique identifier of the client.
///
[Browsable(false)]
public virtual string Name => m_settingsCategory;
///
/// Gets the for the client connection.
///
public TransportStatistics Statistics { get; }
///
/// Determines whether the base class should track statistics.
///
protected virtual bool TrackStatistics => 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(3));
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);
///
/// Requests that the client attempt to move to the next .
///
/// true if request succeeded; otherwise, false.
///
/// Return value will only be true if changed.
///
public virtual bool RequestNextServerIndex() => false;
///
/// 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()
{
}
///
/// 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)
return;
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 (!PersistSettings)
return;
// 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 (!PersistSettings)
return;
// 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)
throw new InvalidOperationException("Client is currently not disconnected");
// Initialize if uninitialized.
if (!m_initialized)
Initialize();
// Set up connection event wait handle
m_connectHandle = new ManualResetEvent(false);
return m_connectHandle;
}
///
/// 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) => 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) => 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) => 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)
throw new InvalidOperationException("Client is not connected");
// Update transport statistics
m_updateBytesSent(length);
// Initiate send operation
return SendDataAsync(data, offset, length);
}
///
/// 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)
{
Statistics.LastSend = DateTime.UtcNow;
Statistics.LastBytesSent = bytes;
Statistics.TotalBytesSent += bytes;
}
///
/// Updates the pertaining to bytes received.
///
///
protected void UpdateBytesReceived(int bytes)
{
Statistics.LastReceive = DateTime.UtcNow;
Statistics.LastBytesReceived = bytes;
Statistics.TotalBytesReceived += bytes;
}
///
/// Raises the event.
///
protected virtual void OnConnectionAttempt()
{
try
{
m_currentState = ClientState.Connecting;
ConnectionAttempt?.Invoke(this, EventArgs.Empty);
}
catch (Exception ex)
{
OnUnhandledUserException(ex);
}
}
///
/// 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.
m_connectHandle?.Set();
ConnectionEstablished?.Invoke(this, EventArgs.Empty);
}
catch (Exception ex)
{
OnUnhandledUserException(ex);
}
}
///
/// 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;
ConnectionTerminated?.Invoke(this, EventArgs.Empty);
}
catch (Exception ex)
{
OnUnhandledUserException(ex);
}
}
///
/// Raises the event.
///
/// Exception to send to event.
protected virtual void OnConnectionException(Exception ex)
{
try
{
// Move to next server connection, when multiple server end points are defined
RequestNextServerIndex();
if (!(ex is ObjectDisposedException))
ConnectionException?.Invoke(this, new EventArgs(ex));
}
catch (Exception userEx)
{
OnUnhandledUserException(userEx);
}
}
///
/// Raises the event.
///
protected virtual void OnSendDataStart()
{
try
{
SendDataStart?.Invoke(this, EventArgs.Empty);
}
catch (Exception ex)
{
OnUnhandledUserException(ex);
}
}
///
/// Raises the event.
///
protected virtual void OnSendDataComplete()
{
try
{
SendDataComplete?.Invoke(this, EventArgs.Empty);
}
catch (Exception ex)
{
OnUnhandledUserException(ex);
}
}
///
/// Raises the event.
///
/// Exception to send to event.
protected virtual void OnSendDataException(Exception ex)
{
try
{
if (!(ex is ObjectDisposedException))
SendDataException?.Invoke(this, new EventArgs(ex));
}
catch (Exception userEx)
{
OnUnhandledUserException(userEx);
}
}
///
/// 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
{
ReceiveData?.Invoke(this, new EventArgs(size));
}
catch (Exception ex)
{
OnUnhandledUserException(ex);
}
}
///
/// 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
ReadIndex = 0;
// Notify users of data ready
ReceiveData?.Invoke(this, new EventArgs(size));
// 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?.Invoke(this, new EventArgs(data.BlockCopy(0, size), size));
}
catch (Exception ex)
{
OnUnhandledUserException(ex);
}
}
///
/// Raises the event.
///
/// Exception to send to event.
protected virtual void OnReceiveDataException(Exception ex)
{
try
{
if (!(ex is ObjectDisposedException))
ReceiveDataException?.Invoke(this, new EventArgs(ex));
}
catch (Exception userEx)
{
OnUnhandledUserException(userEx);
}
}
///
/// Raises the event.
///
/// Exception to send to event.
protected virtual void OnUnhandledUserException(Exception ex)
{
try
{
UnhandledUserException?.Invoke(this, new EventArgs(ex));
}
catch (Exception userEx)
{
// Suppress exceptions in user-defined exception handling
// code, as there's nothing we can reasonably do about it.
Logger.SwallowException(userEx);
}
}
///
/// 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.
[SuppressMessage("Microsoft.Usage", "CA2213:DisposableFieldsShouldBeDisposed", MessageId = "m_connectHandle", Justification = "Field is properly disposed")]
protected override void Dispose(bool disposing)
{
if (IsDisposed)
return;
try
{
if (!disposing)
return;
Disconnect();
SaveSettings();
m_connectHandle?.Dispose();
}
finally
{
IsDisposed = 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)
return;
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 settings = connectionString.ParseKeyValuePairs();
IClient client;
if (settings.TryGetValue("protocol", out string protocol))
{
settings.Remove("protocol");
StringBuilder protocolSettings = new StringBuilder();
foreach (string key in settings.Keys)
{
protocolSettings.Append(key);
protocolSettings.Append("=");
protocolSettings.Append(settings[key]);
protocolSettings.Append(";");
}
// Create a client instance for the specified protocol.
switch (protocol.Trim().ToLower())
{
case "tls":
client = new TlsClient(protocolSettings.ToString());
break;
case "tcp":
client = new TcpClient(protocolSettings.ToString());
break;
case "udp":
client = new UdpClient(protocolSettings.ToString());
break;
case "file":
client = new FileClient(protocolSettings.ToString());
break;
case "serial":
client = new SerialClient(protocolSettings.ToString());
break;
case "zeromq":
client = new ZeroMQClient(protocolSettings.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 settings)
{
PropertyInfo property = client.GetType().GetProperty(setting.Key);
property?.SetValue(client, Convert.ChangeType(setting.Value, property.PropertyType), null);
}
}
else
{
throw new ArgumentException("Transport protocol must be specified");
}
return client;
}
#endregion
}
}