123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748 |
- using System;
- using System.IO;
- using System.Net;
- using System.Net.Security;
- using System.Net.Sockets;
- using System.Security.Cryptography.X509Certificates;
- using System.Threading;
- using Aitex.Core.RT.Log;
- using MECF.Framework.Common.Communications.Tcp.Buffer;
- using MECF.Framework.Common.Communications.Tcp.Socket.Client.APM.EventArgs;
- namespace MECF.Framework.Common.Communications.Tcp.Socket.Client.APM
- {
- public class TcpSocketClient : IDisposable
- {
- #region Fields
- // private static readonly ILogger _log = LoggerManager.GetLogger(Assembly.GetExecutingAssembly(), "Tcp");//. .Get<TcpSocketClient>();
- private TcpClient _tcpClient;
- private readonly TcpSocketClientConfiguration _configuration;
- private readonly IPEndPoint _remoteEndPoint;
- private readonly IPEndPoint _localEndPoint;
- private Stream _stream;
- private ArraySegment<byte> _receiveBuffer = default(ArraySegment<byte>);
- private int _receiveBufferOffset = 0;
- private int _state;
- private const int _none = 0;
- private const int _connecting = 1;
- private const int _connected = 2;
- private const int _closed = 5;
- #endregion
- #region Constructors
- public TcpSocketClient(IPAddress remoteAddress, int remotePort, IPAddress localAddress, int localPort, TcpSocketClientConfiguration configuration = null)
- : this(new IPEndPoint(remoteAddress, remotePort), new IPEndPoint(localAddress, localPort), configuration)
- {
- }
- public TcpSocketClient(IPAddress remoteAddress, int remotePort, IPEndPoint localEP = null, TcpSocketClientConfiguration configuration = null)
- : this(new IPEndPoint(remoteAddress, remotePort), localEP, configuration)
- {
- }
- public TcpSocketClient(IPEndPoint remoteEP, TcpSocketClientConfiguration configuration = null)
- : this(remoteEP, null, configuration)
- {
- }
- public TcpSocketClient(IPEndPoint remoteEP, IPEndPoint localEP, TcpSocketClientConfiguration configuration = null)
- {
- if (remoteEP == null)
- throw new ArgumentNullException("remoteEP");
- _remoteEndPoint = remoteEP;
- _localEndPoint = localEP;
- _configuration = configuration ?? new TcpSocketClientConfiguration();
- if (_configuration.BufferManager == null)
- throw new InvalidProgramException("The buffer manager in configuration cannot be null.");
- if (_configuration.FrameBuilder == null)
- throw new InvalidProgramException("The frame handler in configuration cannot be null.");
- }
- #endregion
- #region Properties
- public TimeSpan ConnectTimeout { get { return _configuration.ConnectTimeout; } }
- private bool Connected { get { return _tcpClient != null && _tcpClient.Client.Connected; } }
- public IPEndPoint RemoteEndPoint { get { return Connected ? (IPEndPoint)_tcpClient.Client.RemoteEndPoint : _remoteEndPoint; } }
- public IPEndPoint LocalEndPoint { get { return Connected ? (IPEndPoint)_tcpClient.Client.LocalEndPoint : _localEndPoint; } }
- public TcpSocketConnectionState State
- {
- get
- {
- switch (_state)
- {
- case _none:
- return TcpSocketConnectionState.None;
- case _connecting:
- return TcpSocketConnectionState.Connecting;
- case _connected:
- return TcpSocketConnectionState.Connected;
- case _closed:
- return TcpSocketConnectionState.Closed;
- default:
- return TcpSocketConnectionState.Closed;
- }
- }
- }
- public override string ToString()
- {
- return string.Format("RemoteEndPoint[{0}], LocalEndPoint[{1}]",
- this.RemoteEndPoint, this.LocalEndPoint);
- }
- /// <summary>
- /// Checks the connection state
- /// </summary>
- /// <returns>True on connected. False on disconnected.</returns>
- public bool IsConnected()
- {
- if (Connected)
- {
- if ((_tcpClient.Client.Poll(0, SelectMode.SelectWrite)) && (!_tcpClient.Client.Poll(0, SelectMode.SelectError)))
- {
- byte[] buffer = new byte[1];
- if (_tcpClient.Client.Receive(buffer, SocketFlags.Peek) == 0)
- {
- return false;
- }
- else
- {
- return true;
- }
- }
- else
- {
- return false;
- }
- }
- else
- {
- return false;
- }
- }
- #endregion
- #region Connect
- public void Connect()
- {
- int origin = Interlocked.Exchange(ref _state, _connecting);
- if (!(origin == _none || origin == _closed))
- {
- Close(false);
- throw new InvalidOperationException("This tcp socket client is in invalid state when connecting.");
- }
- Clean();
- _tcpClient = _localEndPoint != null ?
- new TcpClient(_localEndPoint) :
- new TcpClient(_remoteEndPoint.Address.AddressFamily);
- SetSocketOptions();
- var ar = _tcpClient.BeginConnect(_remoteEndPoint.Address, _remoteEndPoint.Port, null, _tcpClient);
- if (!ar.AsyncWaitHandle.WaitOne(ConnectTimeout))
- {
- Close(false);
- throw new TimeoutException(string.Format(
- "Connect to [{0}] timeout [{1}].", _remoteEndPoint, ConnectTimeout));
- }
- try
- {
- _tcpClient.EndConnect(ar);
- }
- catch (Exception ex)
- {
- Close(false);
- throw ex;
- }
-
- if (Interlocked.CompareExchange(ref _state, _connected, _connecting) != _connecting)
- {
- Close(false);
- throw new InvalidOperationException("This tcp socket client is in invalid state when connected.");
- }
- if (_receiveBuffer == default(ArraySegment<byte>))
- _receiveBuffer = _configuration.BufferManager.BorrowBuffer();
- _receiveBufferOffset = 0;
- HandleTcpServerConnected();
- }
- public void Close()
- {
- Close(true);
- }
- private void Close(bool shallNotifyUserSide)
- {
- if (Interlocked.Exchange(ref _state, _closed) == _closed)
- {
- return;
- }
- Clean();
- if (shallNotifyUserSide)
- {
- try
- {
- RaiseServerDisconnected();
- }
- catch (Exception ex)
- {
- HandleUserSideError(ex);
- }
- }
- }
- private void Clean()
- {
- try
- {
- try
- {
- if (_stream != null)
- {
- _stream.Dispose();
- }
- }
- catch { }
- try
- {
- if (_tcpClient != null)
- {
- _tcpClient.Close();
- }
- }
- catch { }
- }
- catch { }
- finally
- {
- _stream = null;
- _tcpClient = null;
- }
- if (_receiveBuffer != default(ArraySegment<byte>))
- _configuration.BufferManager.ReturnBuffer(_receiveBuffer);
- _receiveBuffer = default(ArraySegment<byte>);
- _receiveBufferOffset = 0;
- }
- #endregion
- #region Receive
- private void HandleTcpServerConnected()
- {
- try
- {
- _stream = NegotiateStream(_tcpClient.GetStream());
- bool isErrorOccurredInUserSide = false;
- try
- {
- RaiseServerConnected();
- }
- catch (Exception ex)
- {
- isErrorOccurredInUserSide = true;
- HandleUserSideError(ex);
- }
- if (!isErrorOccurredInUserSide)
- {
- ContinueReadBuffer();
- }
- else
- {
- Close();
- }
- }
- catch (Exception ex)
- {
- LOG.WriteExeption(ex);
- Close();
- }
- }
- private void SetSocketOptions()
- {
- _tcpClient.ReceiveBufferSize = _configuration.ReceiveBufferSize;
- _tcpClient.SendBufferSize = _configuration.SendBufferSize;
- _tcpClient.ReceiveTimeout = (int)_configuration.ReceiveTimeout.TotalMilliseconds;
- _tcpClient.SendTimeout = (int)_configuration.SendTimeout.TotalMilliseconds;
- _tcpClient.NoDelay = _configuration.NoDelay;
- _tcpClient.LingerState = _configuration.LingerState;
- if (_configuration.KeepAlive)
- {
- _tcpClient.Client.SetSocketOption(
- SocketOptionLevel.Socket,
- SocketOptionName.KeepAlive,
- (int)_configuration.KeepAliveInterval.TotalMilliseconds);
- }
- _tcpClient.Client.SetSocketOption(SocketOptionLevel.Socket, SocketOptionName.ReuseAddress, _configuration.ReuseAddress);
- }
- private Stream NegotiateStream(Stream stream)
- {
- if (!_configuration.SslEnabled)
- return stream;
- var validateRemoteCertificate = new RemoteCertificateValidationCallback(
- (object sender,
- X509Certificate certificate,
- X509Chain chain,
- SslPolicyErrors sslPolicyErrors)
- =>
- {
- if (sslPolicyErrors == SslPolicyErrors.None)
- return true;
- if (_configuration.SslPolicyErrorsBypassed)
- return true;
- else
- //LOG.Write(string.Format("Error occurred when validating remote certificate: [{0}], [{1}].",
- // this.RemoteEndPoint, sslPolicyErrors));
- return false;
- });
- var sslStream = new SslStream(
- stream,
- false,
- validateRemoteCertificate,
- null,
- _configuration.SslEncryptionPolicy);
- IAsyncResult ar = null;
- if (_configuration.SslClientCertificates == null || _configuration.SslClientCertificates.Count == 0)
- {
- ar = sslStream.BeginAuthenticateAsClient( // No client certificates are used in the authentication. The certificate revocation list is not checked during authentication.
- _configuration.SslTargetHost, // The name of the server that will share this SslStream. The value specified for targetHost must match the name on the server's certificate.
- null, _tcpClient);
- }
- else
- {
- ar = sslStream.BeginAuthenticateAsClient(
- _configuration.SslTargetHost, // The name of the server that will share this SslStream. The value specified for targetHost must match the name on the server's certificate.
- _configuration.SslClientCertificates, // The X509CertificateCollection that contains client certificates.
- _configuration.SslEnabledProtocols, // The SslProtocols value that represents the protocol used for authentication.
- _configuration.SslCheckCertificateRevocation, // A Boolean value that specifies whether the certificate revocation list is checked during authentication.
- null, _tcpClient);
- }
- if (!ar.AsyncWaitHandle.WaitOne(ConnectTimeout))
- {
- Close(false);
- throw new TimeoutException(string.Format(
- "Negotiate SSL/TSL with remote [{0}] timeout [{1}].", this.RemoteEndPoint, ConnectTimeout));
- }
- sslStream.EndAuthenticateAsClient(ar);
- // When authentication succeeds, you must check the IsEncrypted and IsSigned properties
- // to determine what security services are used by the SslStream.
- // Check the IsMutuallyAuthenticated property to determine whether mutual authentication occurred.
- //LOG.Write(string.Format(
- // "Ssl Stream: SslProtocol[{0}], IsServer[{1}], IsAuthenticated[{2}], IsEncrypted[{3}], IsSigned[{4}], IsMutuallyAuthenticated[{5}], "
- // + "HashAlgorithm[{6}], HashStrength[{7}], KeyExchangeAlgorithm[{8}], KeyExchangeStrength[{9}], CipherAlgorithm[{10}], CipherStrength[{11}].",
- // sslStream.SslProtocol,
- // sslStream.IsServer,
- // sslStream.IsAuthenticated,
- // sslStream.IsEncrypted,
- // sslStream.IsSigned,
- // sslStream.IsMutuallyAuthenticated,
- // sslStream.HashAlgorithm,
- // sslStream.HashStrength,
- // sslStream.KeyExchangeAlgorithm,
- // sslStream.KeyExchangeStrength,
- // sslStream.CipherAlgorithm,
- // sslStream.CipherStrength));
- return sslStream;
- }
- private void ContinueReadBuffer()
- {
- try
- {
- _stream.BeginRead(
- _receiveBuffer.Array,
- _receiveBuffer.Offset + _receiveBufferOffset,
- _receiveBuffer.Count - _receiveBufferOffset,
- HandleDataReceived,
- _stream);
- }
- catch (Exception ex)
- {
- if (!CloseIfShould(ex))
- throw;
- }
- }
- private void HandleDataReceived(IAsyncResult ar)
- {
- if (this.State != TcpSocketConnectionState.Connected)
- return;
- try
- {
- // when callback to here the stream may have been closed
- if (_stream == null)
- return;
- int numberOfReadBytes = 0;
- try
- {
- // The EndRead method blocks until data is available. The EndRead method reads
- // as much data as is available up to the number of bytes specified in the size
- // parameter of the BeginRead method. If the remote host shuts down the Socket
- // connection and all available data has been received, the EndRead method
- // completes immediately and returns zero bytes.
- numberOfReadBytes = _stream.EndRead(ar);
- }
- catch (Exception)
- {
- // unable to read data from transport connection,
- // the existing connection was forcibly closed by remote host
- numberOfReadBytes = 0;
- }
- if (numberOfReadBytes == 0)
- {
- // connection has been closed
- Close();
- return;
- }
- ReceiveBuffer(numberOfReadBytes);
- ContinueReadBuffer();
- }
- catch (Exception ex)
- {
- if (!CloseIfShould(ex))
- throw;
- }
- }
- private void ReceiveBuffer(int receiveCount)
- {
- // TCP guarantees delivery of all packets in the correct order.
- // But there is no guarantee that one write operation on the sender-side will result in
- // one read event on the receiving side. One call of write(message) by the sender
- // can result in multiple messageReceived(session, message) events on the receiver;
- // and multiple calls of write(message) can lead to a single messageReceived event.
- // In a stream-based transport such as TCP/IP, received data is stored into a socket receive buffer.
- // Unfortunately, the buffer of a stream-based transport is not a queue of packets but a queue of bytes.
- // It means, even if you sent two messages as two independent packets,
- // an operating system will not treat them as two messages but as just a bunch of bytes.
- // Therefore, there is no guarantee that what you read is exactly what your remote peer wrote.
- // There are three common techniques for splitting the stream of bytes into messages:
- // 1. use fixed length messages
- // 2. use a fixed length header that indicates the length of the body
- // 3. using a delimiter; for example many text-based protocols append
- // a newline (or CR LF pair) after every message.
- int frameLength;
- byte[] payload;
- int payloadOffset;
- int payloadCount;
- int consumedLength = 0;
- SegmentBufferDeflector.ReplaceBuffer(_configuration.BufferManager, ref _receiveBuffer, ref _receiveBufferOffset, receiveCount);
- while (true)
- {
- frameLength = 0;
- payload = null;
- payloadOffset = 0;
- payloadCount = 0;
- if (_configuration.FrameBuilder.Decoder.TryDecodeFrame(
- _receiveBuffer.Array,
- _receiveBuffer.Offset + consumedLength,
- _receiveBufferOffset - consumedLength,
- out frameLength, out payload, out payloadOffset, out payloadCount))
- {
- try
- {
- RaiseServerDataReceived(payload, payloadOffset, payloadCount);
- }
- catch (Exception ex)
- {
- HandleUserSideError(ex);
- }
- finally
- {
- consumedLength += frameLength;
- }
- }
- else
- {
- break;
- }
- }
- try
- {
- SegmentBufferDeflector.ShiftBuffer(_configuration.BufferManager, consumedLength, ref _receiveBuffer, ref _receiveBufferOffset);
- }
- catch (ArgumentOutOfRangeException) { }
- }
- #endregion
- #region Exception Handler
- private bool IsSocketTimeOut(Exception ex)
- {
- return ex is IOException
- && ex.InnerException != null
- && ex.InnerException is SocketException
- && (ex.InnerException as SocketException).SocketErrorCode == SocketError.TimedOut;
- }
- private bool CloseIfShould(Exception ex)
- {
- if (ex is ObjectDisposedException
- || ex is InvalidOperationException
- || ex is SocketException
- || ex is IOException
- || ex is NullReferenceException
- )
- {
- //LOG.Error(ex.Message, ex);
- Close();
- return true;
- }
- return false;
- }
- private void HandleUserSideError(Exception ex)
- {
- //LOG.Error(string.Format("Client [{0}] error occurred in user side [{1}].", this, ex.Message), ex);
- }
- #endregion
- #region Send
- public void Send(byte[] data)
- {
- if (data == null)
- throw new ArgumentNullException("data");
- Send(data, 0, data.Length);
- }
- public void Send(byte[] data, int offset, int count)
- {
- BufferValidator.ValidateBuffer(data, offset, count, "data");
- if (this.State != TcpSocketConnectionState.Connected)
- {
- throw new InvalidProgramException("This client has not connected to server.");
- }
- try
- {
- byte[] frameBuffer;
- int frameBufferOffset;
- int frameBufferLength;
- _configuration.FrameBuilder.Encoder.EncodeFrame(data, offset, count, out frameBuffer, out frameBufferOffset, out frameBufferLength);
- _stream.Write(frameBuffer, frameBufferOffset, frameBufferLength);
- }
- catch (Exception ex)
- {
- if (IsSocketTimeOut(ex))
- {
- //LOG.Error(ex.Message, ex);
- }
- else
- {
- if (!CloseIfShould(ex))
- throw;
- }
- }
- }
- public void BeginSend(byte[] data)
- {
- if (data == null)
- throw new ArgumentNullException("data");
- BeginSend(data, 0, data.Length);
- }
- public void BeginSend(byte[] data, int offset, int count)
- {
- BufferValidator.ValidateBuffer(data, offset, count, "data");
- if (this.State != TcpSocketConnectionState.Connected)
- {
- throw new InvalidProgramException("This client has not connected to server.");
- }
- try
- {
- byte[] frameBuffer;
- int frameBufferOffset;
- int frameBufferLength;
- _configuration.FrameBuilder.Encoder.EncodeFrame(data, offset, count, out frameBuffer, out frameBufferOffset, out frameBufferLength);
- _stream.BeginWrite(frameBuffer, frameBufferOffset, frameBufferLength, HandleDataWritten, _stream);
- }
- catch (Exception ex)
- {
- if (IsSocketTimeOut(ex))
- {
- //LOG.Error(ex.Message, ex);
- }
- else
- {
- if (!CloseIfShould(ex))
- throw;
- }
- }
- }
- private void HandleDataWritten(IAsyncResult ar)
- {
- try
- {
- _stream.EndWrite(ar);
- }
- catch (Exception ex)
- {
- if (!CloseIfShould(ex))
- throw;
- }
- }
- public IAsyncResult BeginSend(byte[] data, AsyncCallback callback, object state)
- {
- if (data == null)
- throw new ArgumentNullException("data");
- return BeginSend(data, 0, data.Length, callback, state);
- }
- public IAsyncResult BeginSend(byte[] data, int offset, int count, AsyncCallback callback, object state)
- {
- BufferValidator.ValidateBuffer(data, offset, count, "data");
- if (this.State != TcpSocketConnectionState.Connected)
- {
- throw new InvalidProgramException("This client has not connected to server.");
- }
- try
- {
- byte[] frameBuffer;
- int frameBufferOffset;
- int frameBufferLength;
- _configuration.FrameBuilder.Encoder.EncodeFrame(data, offset, count, out frameBuffer, out frameBufferOffset, out frameBufferLength);
- return _stream.BeginWrite(frameBuffer, frameBufferOffset, frameBufferLength, callback, state);
- }
- catch (Exception ex)
- {
- if (IsSocketTimeOut(ex))
- {
- //LOG.Error(ex.Message, ex);
- }
- else
- {
- if (!CloseIfShould(ex))
- throw;
- }
- throw;
- }
- }
- public void EndSend(IAsyncResult asyncResult)
- {
- HandleDataWritten(asyncResult);
- }
- #endregion
- #region Events
- public event EventHandler<TcpServerConnectedEventArgs> ServerConnected;
- public event EventHandler<TcpServerDisconnectedEventArgs> ServerDisconnected;
- public event EventHandler<TcpServerDataReceivedEventArgs> ServerDataReceived;
- private void RaiseServerConnected()
- {
- if (ServerConnected != null)
- {
- ServerConnected(this, new TcpServerConnectedEventArgs(this.RemoteEndPoint, this.LocalEndPoint));
- }
- }
- private void RaiseServerDisconnected()
- {
- if (ServerDisconnected != null)
- {
- ServerDisconnected(this, new TcpServerDisconnectedEventArgs(_remoteEndPoint, _localEndPoint));
- }
- }
- private void RaiseServerDataReceived(byte[] data, int dataOffset, int dataLength)
- {
- if (ServerDataReceived != null)
- {
- ServerDataReceived(this, new TcpServerDataReceivedEventArgs(this, data, dataOffset, dataLength));
- }
- }
- #endregion
- #region IDisposable Members
- public void Dispose()
- {
- Dispose(true);
- GC.SuppressFinalize(this);
- }
- protected virtual void Dispose(bool disposing)
- {
- if (disposing)
- {
- try
- {
- Close();
- }
- catch (Exception ex)
- {
- LOG.WriteExeption(ex);
- }
- }
- }
- #endregion
- }
- }
|