/// <summary>
        /// Provides a client for connecting to WebSocket services.
        /// </summary>
        /// <param name="logSourceID">Identifier to be used when calling the Logger service.</param>
        /// <param name="options">Collection of WebSocket options.</param>
        public WebSocketClient(string logSourceID, WSOptions options = null)
        {
            this.loggerID = logSourceID;

            this.options = options;
            if (this.options == null)
                this.options = new WSOptions();

            this.origin = this.options.Origin;
            this.subProtocol = this.options.SubProtocol;
            this.extensions = this.options.Extensions;

            this.activityTimerEnabled = this.options.ActivityTimerEnabled;
            this.sendFramesMasked = this.options.MaskingEnabled;

            this.waitHandshakeTimeout = this.options.HandshakeTimeout;
            this.waitCloseMsgTimeout = this.options.CloseMsgTimeout;
            this.waitReceiveTimeout = this.options.ReceiveTimeout;
            this.waitActivityTimeout = this.options.ActivityTimeout;
            this.waitPingRespTimeout = this.options.PingRespTimeout;

            this.activityTimer = new TimerEx();

            this.state = WebSocketState.Initialized;

            this.eventLock = new object();
            this.sendLock = new object();
            this.isDisposed = false;

            this.receiveBuffer = new byte[this.options.MaxReceiveFrameLength];
            this.lastRcvdFrame = null;

            this.sendQueue = new WSFrameQueue(this.options.MaxSendQueueSize);

            // start state machine
            try
            {
                this.runThreadLoop = true;
                this.runStateMachine = new AutoResetEvent(false);
                this.stateMachineThread = new Thread(new ThreadStart(this.WSStateMachine));
                this.stateMachineThread.Start();
            }
            catch (Exception ex)
            {
                Logger.WriteError(this.loggerID, ex.Message, ex.StackTrace);
            }
        }
        protected virtual void Dispose(bool disposing)
        {
            if (this.isDisposed)
                return;

            if (disposing)
            {
                // disconnect
                this.Disconnect();
                DateTime timeoutTime = DateTime.Now.AddSeconds(20);
                while (this.state != WebSocketState.Disconnected && DateTime.Now < timeoutTime)
                    Thread.Sleep(500);

                // kill thread
                this.runThreadLoop = false;
                this.runStateMachine.Set();
                this.stateMachineThread.Join(1000);

                // clear the message queue
                if (this.sendQueue != null)
                    this.sendQueue.Clear();

                // kill timer
                if (this.activityTimer != null)
                    this.activityTimer.Dispose();

                // cleanup streams
                if (this.socketStream != null)
                    this.socketStream.Close();

                // cleanup sockets
                if (this.socket != null)
                    this.socket.Close();
            }

            // set everything to null
            this.eventLock = null;
            this.sendLock = null;
            this.activityTimer = null;
            this.sendQueue = null;
            this.socket = null;
            this.socketStream = null;
            this.receiveBuffer = null;
            this.lastRcvdFrame = null;
            this.serverUri = null;
            this.loggerID = null;
            this.origin = null;
            this.subProtocol = null;
            this.extensions = null;
            this.securityKey = null;
            this.options = null;

            // make sure we don't dispose again
            this.isDisposed = true;
        }