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