using System;
using System.Net;
using System.Net.Security;
using System.Net.Sockets;
using System.Security.Cryptography.X509Certificates;
using System.Text;
using System.Threading;
namespace NetCoreServer
{
///
/// SSL client is used to read/write data from/into the connected SSL server
///
/// Thread-safe
public class SslClient : IDisposable
{
///
/// Initialize SSL client with a given server IP address and port number
///
/// SSL context
/// IP address
/// Port number
public SslClient(SslContext context, IPAddress address, int port) : this(context, new IPEndPoint(address, port)) {}
///
/// Initialize SSL client with a given server IP address and port number
///
/// SSL context
/// IP address
/// Port number
public SslClient(SslContext context, string address, int port) : this(context, new IPEndPoint(IPAddress.Parse(address), port)) {}
///
/// Initialize SSL client with a given DNS endpoint
///
/// SSL context
/// DNS endpoint
public SslClient(SslContext context, DnsEndPoint endpoint) : this(context, endpoint as EndPoint, endpoint.Host, endpoint.Port) {}
///
/// Initialize SSL client with a given IP endpoint
///
/// SSL context
/// IP endpoint
public SslClient(SslContext context, IPEndPoint endpoint) : this(context, endpoint as EndPoint, endpoint.Address.ToString(), endpoint.Port) {}
///
/// Initialize SSL client with a given SSL context, endpoint, address and port
///
/// SSL context
/// Endpoint
/// Server address
/// Server port
private SslClient(SslContext context, EndPoint endpoint, string address, int port)
{
Id = Guid.NewGuid();
Address = address;
Port = port;
Context = context;
Endpoint = endpoint;
}
///
/// Client Id
///
public Guid Id { get; }
///
/// SSL server address
///
public string Address { get; }
///
/// SSL server port
///
public int Port { get; }
///
/// SSL context
///
public SslContext Context { get; }
///
/// Endpoint
///
public EndPoint Endpoint { get; private set; }
///
/// Socket
///
public Socket Socket { get; private set; }
///
/// Number of bytes pending sent by the client
///
public long BytesPending { get; private set; }
///
/// Number of bytes sending by the client
///
public long BytesSending { get; private set; }
///
/// Number of bytes sent by the client
///
public long BytesSent { get; private set; }
///
/// Number of bytes received by the client
///
public long BytesReceived { get; private set; }
///
/// Option: dual mode socket
///
///
/// Specifies whether the Socket is a dual-mode socket used for both IPv4 and IPv6.
/// Will work only if socket is bound on IPv6 address.
///
public bool OptionDualMode { get; set; }
///
/// Option: keep alive
///
///
/// This option will setup SO_KEEPALIVE if the OS support this feature
///
public bool OptionKeepAlive { get; set; }
///
/// Option: TCP keep alive time
///
///
/// The number of seconds a TCP connection will remain alive/idle before keepalive probes are sent to the remote
///
public int OptionTcpKeepAliveTime { get; set; } = -1;
///
/// Option: TCP keep alive interval
///
///
/// The number of seconds a TCP connection will wait for a keepalive response before sending another keepalive probe
///
public int OptionTcpKeepAliveInterval { get; set; } = -1;
///
/// Option: TCP keep alive retry count
///
///
/// The number of TCP keep alive probes that will be sent before the connection is terminated
///
public int OptionTcpKeepAliveRetryCount { get; set; } = -1;
///
/// Option: no delay
///
///
/// This option will enable/disable Nagle's algorithm for SSL protocol
///
public bool OptionNoDelay { get; set; }
///
/// Option: receive buffer limit
///
public int OptionReceiveBufferLimit { get; set; } = 0;
///
/// Option: receive buffer size
///
public int OptionReceiveBufferSize { get; set; } = 8192;
///
/// Option: send buffer limit
///
public int OptionSendBufferLimit { get; set; } = 0;
///
/// Option: send buffer size
///
public int OptionSendBufferSize { get; set; } = 8192;
#region Connect/Disconnect client
private bool _disconnecting;
private SocketAsyncEventArgs _connectEventArg;
private SslStream _sslStream;
private Guid? _sslStreamId;
///
/// Is the client connecting?
///
public bool IsConnecting { get; private set; }
///
/// Is the client connected?
///
public bool IsConnected { get; private set; }
///
/// Is the client handshaking?
///
public bool IsHandshaking { get; private set; }
///
/// Is the client handshaked?
///
public bool IsHandshaked { get; private set; }
///
/// Create a new socket object
///
///
/// Method may be override if you need to prepare some specific socket object in your implementation.
///
/// Socket object
protected virtual Socket CreateSocket()
{
return new Socket(Endpoint.AddressFamily, SocketType.Stream, ProtocolType.Tcp);
}
///
/// Connect the client (synchronous)
///
///
/// Please note that synchronous connect will not receive data automatically!
/// You should use Receive() or ReceiveAsync() method manually after successful connection.
///
/// 'true' if the client was successfully connected, 'false' if the client failed to connect
public virtual bool Connect()
{
if (IsConnected || IsHandshaked || IsConnecting || IsHandshaking)
return false;
// Setup buffers
_receiveBuffer = new Buffer();
_sendBufferMain = new Buffer();
_sendBufferFlush = new Buffer();
// Setup event args
_connectEventArg = new SocketAsyncEventArgs();
_connectEventArg.RemoteEndPoint = Endpoint;
_connectEventArg.Completed += OnAsyncCompleted;
// Create a new client socket
Socket = CreateSocket();
// Update the client socket disposed flag
IsSocketDisposed = false;
// Apply the option: dual mode (this option must be applied before connecting)
if (Socket.AddressFamily == AddressFamily.InterNetworkV6)
Socket.DualMode = OptionDualMode;
// Call the client connecting handler
OnConnecting();
try
{
// Connect to the server
Socket.Connect(Endpoint);
}
catch (SocketException ex)
{
// Call the client error handler
SendError(ex.SocketErrorCode);
// Reset event args
_connectEventArg.Completed -= OnAsyncCompleted;
// Call the client disconnecting handler
OnDisconnecting();
// Close the client socket
Socket.Close();
// Dispose the client socket
Socket.Dispose();
// Dispose event arguments
_connectEventArg.Dispose();
// Call the client disconnected handler
OnDisconnected();
return false;
}
// Apply the option: keep alive
if (OptionKeepAlive)
Socket.SetSocketOption(SocketOptionLevel.Socket, SocketOptionName.KeepAlive, true);
if (OptionTcpKeepAliveTime >= 0)
Socket.SetSocketOption(SocketOptionLevel.Tcp, SocketOptionName.TcpKeepAliveTime, OptionTcpKeepAliveTime);
if (OptionTcpKeepAliveInterval >= 0)
Socket.SetSocketOption(SocketOptionLevel.Tcp, SocketOptionName.TcpKeepAliveInterval, OptionTcpKeepAliveInterval);
if (OptionTcpKeepAliveRetryCount >= 0)
Socket.SetSocketOption(SocketOptionLevel.Tcp, SocketOptionName.TcpKeepAliveRetryCount, OptionTcpKeepAliveRetryCount);
// Apply the option: no delay
if (OptionNoDelay)
Socket.SetSocketOption(SocketOptionLevel.Tcp, SocketOptionName.NoDelay, true);
// Prepare receive & send buffers
_receiveBuffer.Reserve(OptionReceiveBufferSize);
_sendBufferMain.Reserve(OptionSendBufferSize);
_sendBufferFlush.Reserve(OptionSendBufferSize);
// Reset statistic
BytesPending = 0;
BytesSending = 0;
BytesSent = 0;
BytesReceived = 0;
// Update the connected flag
IsConnected = true;
// Call the client connected handler
OnConnected();
try
{
// Create SSL stream
_sslStreamId = Guid.NewGuid();
_sslStream = (Context.CertificateValidationCallback != null) ? new SslStream(new NetworkStream(Socket, false), false, Context.CertificateValidationCallback) : new SslStream(new NetworkStream(Socket, false), false);
// Call the session handshaking handler
OnHandshaking();
// SSL handshake
if (Context.Certificates != null)
_sslStream.AuthenticateAsClient(Address, Context.Certificates, Context.Protocols, true);
else if (Context.Certificate != null)
_sslStream.AuthenticateAsClient(Address, new X509CertificateCollection(new[] { Context.Certificate }), Context.Protocols, true);
else
_sslStream.AuthenticateAsClient(Address);
}
catch (Exception)
{
SendError(SocketError.NotConnected);
DisconnectAsync();
return false;
}
// Update the handshaked flag
IsHandshaked = true;
// Call the session handshaked handler
OnHandshaked();
// Call the empty send buffer handler
if (_sendBufferMain.IsEmpty)
OnEmpty();
return true;
}
///
/// Disconnect the client (synchronous)
///
/// 'true' if the client was successfully disconnected, 'false' if the client is already disconnected
public virtual bool Disconnect()
{
if (!IsConnected && !IsConnecting)
return false;
// Cancel connecting operation
if (IsConnecting)
Socket.CancelConnectAsync(_connectEventArg);
if (_disconnecting)
return false;
// Reset connecting & handshaking flags
IsConnecting = false;
IsHandshaking = false;
// Update the disconnecting flag
_disconnecting = true;
// Reset event args
_connectEventArg.Completed -= OnAsyncCompleted;
// Call the client disconnecting handler
OnDisconnecting();
try
{
try
{
// Shutdown the SSL stream
_sslStream.ShutdownAsync().Wait();
}
catch (Exception) {}
// Dispose the SSL stream & buffer
_sslStream.Dispose();
_sslStreamId = null;
try
{
// Shutdown the socket associated with the client
Socket.Shutdown(SocketShutdown.Both);
}
catch (SocketException) {}
// Close the client socket
Socket.Close();
// Dispose the client socket
Socket.Dispose();
// Dispose event arguments
_connectEventArg.Dispose();
// Update the client socket disposed flag
IsSocketDisposed = true;
}
catch (ObjectDisposedException) {}
// Update the handshaked flag
IsHandshaked = false;
// Update the connected flag
IsConnected = false;
// Update sending/receiving flags
_receiving = false;
_sending = false;
// Clear send/receive buffers
ClearBuffers();
// Call the client disconnected handler
OnDisconnected();
// Reset the disconnecting flag
_disconnecting = false;
return true;
}
///
/// Reconnect the client (synchronous)
///
/// 'true' if the client was successfully reconnected, 'false' if the client is already reconnected
public virtual bool Reconnect()
{
if (!Disconnect())
return false;
return Connect();
}
///
/// Connect the client (asynchronous)
///
/// 'true' if the client was successfully connected, 'false' if the client failed to connect
public virtual bool ConnectAsync()
{
if (IsConnected || IsHandshaked || IsConnecting || IsHandshaking)
return false;
// Setup buffers
_receiveBuffer = new Buffer();
_sendBufferMain = new Buffer();
_sendBufferFlush = new Buffer();
// Setup event args
_connectEventArg = new SocketAsyncEventArgs();
_connectEventArg.RemoteEndPoint = Endpoint;
_connectEventArg.Completed += OnAsyncCompleted;
// Create a new client socket
Socket = CreateSocket();
// Update the client socket disposed flag
IsSocketDisposed = false;
// Apply the option: dual mode (this option must be applied before connecting)
if (Socket.AddressFamily == AddressFamily.InterNetworkV6)
Socket.DualMode = OptionDualMode;
// Update the connecting flag
IsConnecting = true;
// Call the client connecting handler
OnConnecting();
// Async connect to the server
if (!Socket.ConnectAsync(_connectEventArg))
ProcessConnect(_connectEventArg);
return true;
}
///
/// Disconnect the client (asynchronous)
///
/// 'true' if the client was successfully disconnected, 'false' if the client is already disconnected
public virtual bool DisconnectAsync() => Disconnect();
///
/// Reconnect the client (asynchronous)
///
/// 'true' if the client was successfully reconnected, 'false' if the client is already reconnected
public virtual bool ReconnectAsync()
{
if (!DisconnectAsync())
return false;
while (IsConnected)
Thread.Yield();
return ConnectAsync();
}
#endregion
#region Send/Receive data
// Receive buffer
private bool _receiving;
private Buffer _receiveBuffer;
// Send buffer
private readonly object _sendLock = new object();
private bool _sending;
private Buffer _sendBufferMain;
private Buffer _sendBufferFlush;
private long _sendBufferFlushOffset;
///
/// Send data to the server (synchronous)
///
/// Buffer to send
/// Size of sent data
public virtual long Send(byte[] buffer) => Send(buffer.AsSpan());
///
/// Send data to the server (synchronous)
///
/// Buffer to send
/// Buffer offset
/// Buffer size
/// Size of sent data
public virtual long Send(byte[] buffer, long offset, long size) => Send(buffer.AsSpan((int)offset, (int)size));
///
/// Send data to the server (synchronous)
///
/// Buffer to send as a span of bytes
/// Size of sent data
public virtual long Send(ReadOnlySpan buffer)
{
if (!IsHandshaked)
return 0;
if (buffer.IsEmpty)
return 0;
try
{
// Sent data to the server
_sslStream.Write(buffer);
long sent = buffer.Length;
// Update statistic
BytesSent += sent;
// Call the buffer sent handler
OnSent(sent, BytesPending + BytesSending);
return sent;
}
catch (Exception)
{
SendError(SocketError.OperationAborted);
Disconnect();
return 0;
}
}
///
/// Send text to the server (synchronous)
///
/// Text string to send
/// Size of sent text
public virtual long Send(string text) => Send(Encoding.UTF8.GetBytes(text));
///
/// Send text to the server (synchronous)
///
/// Text to send as a span of characters
/// Size of sent text
public virtual long Send(ReadOnlySpan text) => Send(Encoding.UTF8.GetBytes(text.ToArray()));
///
/// Send data to the server (asynchronous)
///
/// Buffer to send
/// 'true' if the data was successfully sent, 'false' if the client is not connected
public virtual bool SendAsync(byte[] buffer) => SendAsync(buffer.AsSpan());
///
/// Send data to the server (asynchronous)
///
/// Buffer to send
/// Buffer offset
/// Buffer size
/// 'true' if the data was successfully sent, 'false' if the client is not connected
public virtual bool SendAsync(byte[] buffer, long offset, long size) => SendAsync(buffer.AsSpan((int)offset, (int)size));
///
/// Send data to the server (asynchronous)
///
/// Buffer to send as a span of bytes
/// 'true' if the data was successfully sent, 'false' if the client is not connected
public virtual bool SendAsync(ReadOnlySpan buffer)
{
if (!IsHandshaked)
return false;
if (buffer.IsEmpty)
return true;
lock (_sendLock)
{
// Check the send buffer limit
if (((_sendBufferMain.Size + buffer.Length) > OptionSendBufferLimit) && (OptionSendBufferLimit > 0))
{
SendError(SocketError.NoBufferSpaceAvailable);
return false;
}
// Fill the main send buffer
_sendBufferMain.Append(buffer);
// Update statistic
BytesPending = _sendBufferMain.Size;
// Avoid multiple send handlers
if (_sending)
return true;
else
_sending = true;
// Try to send the main buffer
TrySend();
}
return true;
}
///
/// Send text to the server (asynchronous)
///
/// Text string to send
/// 'true' if the text was successfully sent, 'false' if the client is not connected
public virtual bool SendAsync(string text) => SendAsync(Encoding.UTF8.GetBytes(text));
///
/// Send text to the server (asynchronous)
///
/// Text to send as a span of characters
/// 'true' if the text was successfully sent, 'false' if the client is not connected
public virtual bool SendAsync(ReadOnlySpan text) => SendAsync(Encoding.UTF8.GetBytes(text.ToArray()));
///
/// Receive data from the server (synchronous)
///
/// Buffer to receive
/// Size of received data
public virtual long Receive(byte[] buffer) { return Receive(buffer, 0, buffer.Length); }
///
/// Receive data from the server (synchronous)
///
/// Buffer to receive
/// Buffer offset
/// Buffer size
/// Size of received data
public virtual long Receive(byte[] buffer, long offset, long size)
{
if (!IsHandshaked)
return 0;
if (size == 0)
return 0;
try
{
// Receive data from the server
long received = _sslStream.Read(buffer, (int)offset, (int)size);
if (received > 0)
{
// Update statistic
BytesReceived += received;
// Call the buffer received handler
OnReceived(buffer, 0, received);
}
return received;
}
catch (Exception)
{
SendError(SocketError.OperationAborted);
Disconnect();
return 0;
}
}
///
/// Receive text from the server (synchronous)
///
/// Text size to receive
/// Received text
public virtual string Receive(long size)
{
var buffer = new byte[size];
var length = Receive(buffer);
return Encoding.UTF8.GetString(buffer, 0, (int)length);
}
///
/// Receive data from the server (asynchronous)
///
public virtual void ReceiveAsync()
{
// Try to receive data from the server
TryReceive();
}
///
/// Try to receive new data
///
private void TryReceive()
{
if (_receiving)
return;
if (!IsHandshaked)
return;
try
{
// Async receive with the receive handler
IAsyncResult result;
do
{
if (!IsHandshaked)
return;
_receiving = true;
result = _sslStream.BeginRead(_receiveBuffer.Data, 0, (int)_receiveBuffer.Capacity, ProcessReceive, _sslStreamId);
} while (result.CompletedSynchronously);
}
catch (ObjectDisposedException) {}
}
///
/// Try to send pending data
///
private void TrySend()
{
if (!IsHandshaked)
return;
bool empty = false;
lock (_sendLock)
{
// Is previous socket send in progress?
if (_sendBufferFlush.IsEmpty)
{
// Swap flush and main buffers
_sendBufferFlush = Interlocked.Exchange(ref _sendBufferMain, _sendBufferFlush);
_sendBufferFlushOffset = 0;
// Update statistic
BytesPending = 0;
BytesSending += _sendBufferFlush.Size;
// Check if the flush buffer is empty
if (_sendBufferFlush.IsEmpty)
{
// Need to call empty send buffer handler
empty = true;
// End sending process
_sending = false;
}
}
else
return;
}
// Call the empty send buffer handler
if (empty)
{
OnEmpty();
return;
}
try
{
// Async write with the write handler
_sslStream.BeginWrite(_sendBufferFlush.Data, (int)_sendBufferFlushOffset, (int)(_sendBufferFlush.Size - _sendBufferFlushOffset), ProcessSend, _sslStreamId);
}
catch (ObjectDisposedException) {}
}
///
/// Clear send/receive buffers
///
private void ClearBuffers()
{
lock (_sendLock)
{
// Clear send buffers
_sendBufferMain.Clear();
_sendBufferFlush.Clear();
_sendBufferFlushOffset= 0;
// Update statistic
BytesPending = 0;
BytesSending = 0;
}
}
#endregion
#region IO processing
///
/// This method is called whenever a receive or send operation is completed on a socket
///
private void OnAsyncCompleted(object sender, SocketAsyncEventArgs e)
{
if (IsSocketDisposed)
return;
// Determine which type of operation just completed and call the associated handler
switch (e.LastOperation)
{
case SocketAsyncOperation.Connect:
ProcessConnect(e);
break;
default:
throw new ArgumentException("The last operation completed on the socket was not a receive or send");
}
}
///
/// This method is invoked when an asynchronous connect operation completes
///
private void ProcessConnect(SocketAsyncEventArgs e)
{
IsConnecting = false;
if (e.SocketError == SocketError.Success)
{
// Apply the option: keep alive
if (OptionKeepAlive)
Socket.SetSocketOption(SocketOptionLevel.Socket, SocketOptionName.KeepAlive, true);
if (OptionTcpKeepAliveTime >= 0)
Socket.SetSocketOption(SocketOptionLevel.Tcp, SocketOptionName.TcpKeepAliveTime, OptionTcpKeepAliveTime);
if (OptionTcpKeepAliveInterval >= 0)
Socket.SetSocketOption(SocketOptionLevel.Tcp, SocketOptionName.TcpKeepAliveInterval, OptionTcpKeepAliveInterval);
if (OptionTcpKeepAliveRetryCount >= 0)
Socket.SetSocketOption(SocketOptionLevel.Tcp, SocketOptionName.TcpKeepAliveRetryCount, OptionTcpKeepAliveRetryCount);
// Apply the option: no delay
if (OptionNoDelay)
Socket.SetSocketOption(SocketOptionLevel.Tcp, SocketOptionName.NoDelay, true);
// Prepare receive & send buffers
_receiveBuffer.Reserve(OptionReceiveBufferSize);
_sendBufferMain.Reserve(OptionSendBufferSize);
_sendBufferFlush.Reserve(OptionSendBufferSize);
// Reset statistic
BytesPending = 0;
BytesSending = 0;
BytesSent = 0;
BytesReceived = 0;
// Update the connected flag
IsConnected = true;
// Call the client connected handler
OnConnected();
try
{
// Create SSL stream
_sslStreamId = Guid.NewGuid();
_sslStream = (Context.CertificateValidationCallback != null) ? new SslStream(new NetworkStream(Socket, false), false, Context.CertificateValidationCallback) : new SslStream(new NetworkStream(Socket, false), false);
// Call the session handshaking handler
OnHandshaking();
// Begin the SSL handshake
IsHandshaking = true;
if (Context.Certificates != null)
_sslStream.BeginAuthenticateAsClient(Address, Context.Certificates, Context.Protocols, true, ProcessHandshake, _sslStreamId);
else if (Context.Certificate != null)
_sslStream.BeginAuthenticateAsClient(Address, new X509CertificateCollection(new[] { Context.Certificate }), Context.Protocols, true, ProcessHandshake, _sslStreamId);
else
_sslStream.BeginAuthenticateAsClient(Address, ProcessHandshake, _sslStreamId);
}
catch (Exception)
{
SendError(SocketError.NotConnected);
DisconnectAsync();
}
}
else
{
// Call the client disconnected handler
SendError(e.SocketError);
OnDisconnected();
}
}
///
/// This method is invoked when an asynchronous handshake operation completes
///
private void ProcessHandshake(IAsyncResult result)
{
try
{
IsHandshaking = false;
if (IsHandshaked)
return;
// Validate SSL stream Id
var sslStreamId = result.AsyncState as Guid?;
if (_sslStreamId != sslStreamId)
return;
// End the SSL handshake
_sslStream.EndAuthenticateAsClient(result);
// Update the handshaked flag
IsHandshaked = true;
// Try to receive something from the server
TryReceive();
// Check the socket disposed state: in some rare cases it might be disconnected while receiving!
if (IsSocketDisposed)
return;
// Call the session handshaked handler
OnHandshaked();
// Call the empty send buffer handler
if (_sendBufferMain.IsEmpty)
OnEmpty();
}
catch (Exception)
{
SendError(SocketError.NotConnected);
DisconnectAsync();
}
}
///
/// This method is invoked when an asynchronous receive operation completes
///
private void ProcessReceive(IAsyncResult result)
{
try
{
if (!IsHandshaked)
return;
// Validate SSL stream Id
var sslStreamId = result.AsyncState as Guid?;
if (_sslStreamId != sslStreamId)
return;
// End the SSL read
long size = _sslStream.EndRead(result);
// Received some data from the server
if (size > 0)
{
// Update statistic
BytesReceived += size;
// Call the buffer received handler
OnReceived(_receiveBuffer.Data, 0, size);
// If the receive buffer is full increase its size
if (_receiveBuffer.Capacity == size)
{
// Check the receive buffer limit
if (((2 * size) > OptionReceiveBufferLimit) && (OptionReceiveBufferLimit > 0))
{
SendError(SocketError.NoBufferSpaceAvailable);
DisconnectAsync();
return;
}
_receiveBuffer.Reserve(2 * size);
}
}
_receiving = false;
// If zero is returned from a read operation, the remote end has closed the connection
if (size > 0)
{
if (!result.CompletedSynchronously)
TryReceive();
}
else
DisconnectAsync();
}
catch (Exception)
{
SendError(SocketError.OperationAborted);
DisconnectAsync();
}
}
///
/// This method is invoked when an asynchronous send operation completes
///
private void ProcessSend(IAsyncResult result)
{
try
{
if (!IsHandshaked)
return;
// Validate SSL stream Id
var sslStreamId = result.AsyncState as Guid?;
if (_sslStreamId != sslStreamId)
return;
// End the SSL write
_sslStream.EndWrite(result);
long size = _sendBufferFlush.Size;
// Send some data to the server
if (size > 0)
{
// Update statistic
BytesSending -= size;
BytesSent += size;
// Increase the flush buffer offset
_sendBufferFlushOffset += size;
// Successfully send the whole flush buffer
if (_sendBufferFlushOffset == _sendBufferFlush.Size)
{
// Clear the flush buffer
_sendBufferFlush.Clear();
_sendBufferFlushOffset = 0;
}
// Call the buffer sent handler
OnSent(size, BytesPending + BytesSending);
}
// Try to send again if the client is valid
TrySend();
}
catch (Exception)
{
SendError(SocketError.OperationAborted);
DisconnectAsync();
}
}
#endregion
#region Session handlers
///
/// Handle client connecting notification
///
protected virtual void OnConnecting() {}
///
/// Handle client connected notification
///
protected virtual void OnConnected() {}
///
/// Handle client handshaking notification
///
protected virtual void OnHandshaking() {}
///
/// Handle client handshaked notification
///
protected virtual void OnHandshaked() {}
///
/// Handle client disconnecting notification
///
protected virtual void OnDisconnecting() {}
///
/// Handle client disconnected notification
///
protected virtual void OnDisconnected() {}
///
/// Handle buffer received notification
///
/// Received buffer
/// Received buffer offset
/// Received buffer size
///
/// Notification is called when another part of buffer was received from the server
///
protected virtual void OnReceived(byte[] buffer, long offset, long size) {}
///
/// Handle buffer sent notification
///
/// Size of sent buffer
/// Size of pending buffer
///
/// Notification is called when another part of buffer was sent to the server.
/// This handler could be used to send another buffer to the server for instance when the pending size is zero.
///
protected virtual void OnSent(long sent, long pending) {}
///
/// Handle empty send buffer notification
///
///
/// Notification is called when the send buffer is empty and ready for a new data to send.
/// This handler could be used to send another buffer to the server.
///
protected virtual void OnEmpty() {}
///
/// Handle error notification
///
/// Socket error code
protected virtual void OnError(SocketError error) {}
#endregion
#region Error handling
///
/// Send error notification
///
/// Socket error code
private void SendError(SocketError error)
{
// Skip disconnect errors
if ((error == SocketError.ConnectionAborted) ||
(error == SocketError.ConnectionRefused) ||
(error == SocketError.ConnectionReset) ||
(error == SocketError.OperationAborted) ||
(error == SocketError.Shutdown))
return;
OnError(error);
}
#endregion
#region IDisposable implementation
///
/// Disposed flag
///
public bool IsDisposed { get; private set; }
///
/// Client socket disposed flag
///
public bool IsSocketDisposed { get; private set; } = true;
// Implement IDisposable.
public void Dispose()
{
Dispose(true);
GC.SuppressFinalize(this);
}
protected virtual void Dispose(bool disposingManagedResources)
{
// The idea here is that Dispose(Boolean) knows whether it is
// being called to do explicit cleanup (the Boolean is true)
// versus being called due to a garbage collection (the Boolean
// is false). This distinction is useful because, when being
// disposed explicitly, the Dispose(Boolean) method can safely
// execute code using reference type fields that refer to other
// objects knowing for sure that these other objects have not been
// finalized or disposed of yet. When the Boolean is false,
// the Dispose(Boolean) method should not execute code that
// refer to reference type fields because those objects may
// have already been finalized."
if (!IsDisposed)
{
if (disposingManagedResources)
{
// Dispose managed resources here...
DisconnectAsync();
}
// Dispose unmanaged resources here...
// Set large fields to null here...
// Mark as disposed.
IsDisposed = true;
}
}
#endregion
}
}