/// <summary>
        /// Starts read/write communication on the underlaying, connected socket. 
        /// Start must be called after a TcpStreamService has fired the onClientAccepted callback.
        /// </summary>
        /// <param name="onDataReceived">The action is called back (on a threadpool thread), when a message start is received.</param>
        /// <param name="onChannelDisconnected">The action is called back (on a threadpool thread), when the channel is disconnected from remote. May be null.</param>
        /// <param name="bufferSize">Defines size of the receive buffer and minimum size of the send buffer. Default = 1500 bytes (one ethernet frame).</param>
        public void Start(Action<TcpStreamChannel> onDataReceived, Action<TcpStreamChannel> onChannelDisconnected = null, int bufferSize=1500)
        {
            if (onDataReceived == null)
            {
                throw new ArgumentNullException("onDataReceived");
            }

            if (_disposed || !ClientSocket.Connected)
            {
                DisposeSocket(ClientSocket);
                throw new SocketException((int)SocketError.NotConnected);
            }

            if (_onDataReceivedAction != null)
            {
                throw new InvalidOperationException("already started");
            }

            _onDataReceivedAction = onDataReceived;
            _onChannelDisconnectedAction = onChannelDisconnected;
            _bufferSize = bufferSize;

            _receiveEventArgs.SetBuffer(new byte[_bufferSize], 0, _bufferSize);
            _receiveEventArgs.Completed += OnDataReceived;
            _tcpStreamIncoming = new TcpStreamIncoming(StartAsyncRead, ()=> ClientSocket.Available);
            _userReadsMessage = false;

            _sendEventArgs = new SocketAsyncEventArgs();
            _sendEventArgs.Completed += OnDataSent;
            _tcpStreamOutgoing = new TcpStreamOutgoing(SendAsync, _bufferSize);

            if (!StartAsyncRead() && LatestException != null)
            {
                throw LatestException;
            }
        }
        protected virtual void Dispose(bool disposing)
        {
            _userReadsMessage = false;
            if (disposing && !_disposed)
            {
                _disposed = true;
                if (ClientSocket != null)
                {
                    if (ClientSocket.Connected)
                    {
                        try
                        {
                            ClientSocket.Shutdown(SocketShutdown.Both);
                        }
                        catch (Exception shutdownException)
                        {
                            LatestException = shutdownException;
                        }
                    }

                    try
                    {
                        DisposeSocket(ClientSocket);
                    }
                    catch (Exception closeException)
                    {
                        LatestException = closeException;
                    }

                    ClientSocket = null;
                }

                if (_receiveEventArgs != null)
                {
                    _receiveEventArgs.Dispose();
                }

                if (_sendEventArgs != null)
                {
                    _sendEventArgs.Dispose();
                }

                if (_tcpStreamIncoming != null)
                {
                    _tcpStreamIncoming.Dispose();
                    _tcpStreamIncoming = null;
                }

                if (_tcpStreamOutgoing != null)
                {
                    _tcpStreamOutgoing.Dispose();
                    _tcpStreamOutgoing = null;
                }
            }
        }