SslClient.cs 41 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069107010711072107310741075107610771078107910801081108210831084108510861087108810891090109110921093109410951096109710981099110011011102110311041105110611071108110911101111111211131114111511161117111811191120112111221123112411251126112711281129113011311132113311341135113611371138113911401141114211431144114511461147114811491150115111521153115411551156115711581159116011611162116311641165116611671168116911701171117211731174117511761177117811791180118111821183118411851186118711881189119011911192119311941195119611971198
  1. using System;
  2. using System.Net;
  3. using System.Net.Security;
  4. using System.Net.Sockets;
  5. using System.Security.Cryptography.X509Certificates;
  6. using System.Text;
  7. using System.Threading;
  8. namespace NetCoreServer
  9. {
  10. /// <summary>
  11. /// SSL client is used to read/write data from/into the connected SSL server
  12. /// </summary>
  13. /// <remarks>Thread-safe</remarks>
  14. public class SslClient : IDisposable
  15. {
  16. /// <summary>
  17. /// Initialize SSL client with a given server IP address and port number
  18. /// </summary>
  19. /// <param name="context">SSL context</param>
  20. /// <param name="address">IP address</param>
  21. /// <param name="port">Port number</param>
  22. public SslClient(SslContext context, IPAddress address, int port) : this(context, new IPEndPoint(address, port)) {}
  23. /// <summary>
  24. /// Initialize SSL client with a given server IP address and port number
  25. /// </summary>
  26. /// <param name="context">SSL context</param>
  27. /// <param name="address">IP address</param>
  28. /// <param name="port">Port number</param>
  29. public SslClient(SslContext context, string address, int port) : this(context, new IPEndPoint(IPAddress.Parse(address), port)) {}
  30. /// <summary>
  31. /// Initialize SSL client with a given DNS endpoint
  32. /// </summary>
  33. /// <param name="context">SSL context</param>
  34. /// <param name="endpoint">DNS endpoint</param>
  35. public SslClient(SslContext context, DnsEndPoint endpoint) : this(context, endpoint as EndPoint, endpoint.Host, endpoint.Port) {}
  36. /// <summary>
  37. /// Initialize SSL client with a given IP endpoint
  38. /// </summary>
  39. /// <param name="context">SSL context</param>
  40. /// <param name="endpoint">IP endpoint</param>
  41. public SslClient(SslContext context, IPEndPoint endpoint) : this(context, endpoint as EndPoint, endpoint.Address.ToString(), endpoint.Port) {}
  42. /// <summary>
  43. /// Initialize SSL client with a given SSL context, endpoint, address and port
  44. /// </summary>
  45. /// <param name="context">SSL context</param>
  46. /// <param name="endpoint">Endpoint</param>
  47. /// <param name="address">Server address</param>
  48. /// <param name="port">Server port</param>
  49. private SslClient(SslContext context, EndPoint endpoint, string address, int port)
  50. {
  51. Id = Guid.NewGuid();
  52. Address = address;
  53. Port = port;
  54. Context = context;
  55. Endpoint = endpoint;
  56. }
  57. /// <summary>
  58. /// Client Id
  59. /// </summary>
  60. public Guid Id { get; }
  61. /// <summary>
  62. /// SSL server address
  63. /// </summary>
  64. public string Address { get; }
  65. /// <summary>
  66. /// SSL server port
  67. /// </summary>
  68. public int Port { get; }
  69. /// <summary>
  70. /// SSL context
  71. /// </summary>
  72. public SslContext Context { get; }
  73. /// <summary>
  74. /// Endpoint
  75. /// </summary>
  76. public EndPoint Endpoint { get; private set; }
  77. /// <summary>
  78. /// Socket
  79. /// </summary>
  80. public Socket Socket { get; private set; }
  81. /// <summary>
  82. /// Number of bytes pending sent by the client
  83. /// </summary>
  84. public long BytesPending { get; private set; }
  85. /// <summary>
  86. /// Number of bytes sending by the client
  87. /// </summary>
  88. public long BytesSending { get; private set; }
  89. /// <summary>
  90. /// Number of bytes sent by the client
  91. /// </summary>
  92. public long BytesSent { get; private set; }
  93. /// <summary>
  94. /// Number of bytes received by the client
  95. /// </summary>
  96. public long BytesReceived { get; private set; }
  97. /// <summary>
  98. /// Option: dual mode socket
  99. /// </summary>
  100. /// <remarks>
  101. /// Specifies whether the Socket is a dual-mode socket used for both IPv4 and IPv6.
  102. /// Will work only if socket is bound on IPv6 address.
  103. /// </remarks>
  104. public bool OptionDualMode { get; set; }
  105. /// <summary>
  106. /// Option: keep alive
  107. /// </summary>
  108. /// <remarks>
  109. /// This option will setup SO_KEEPALIVE if the OS support this feature
  110. /// </remarks>
  111. public bool OptionKeepAlive { get; set; }
  112. /// <summary>
  113. /// Option: TCP keep alive time
  114. /// </summary>
  115. /// <remarks>
  116. /// The number of seconds a TCP connection will remain alive/idle before keepalive probes are sent to the remote
  117. /// </remarks>
  118. public int OptionTcpKeepAliveTime { get; set; } = -1;
  119. /// <summary>
  120. /// Option: TCP keep alive interval
  121. /// </summary>
  122. /// <remarks>
  123. /// The number of seconds a TCP connection will wait for a keepalive response before sending another keepalive probe
  124. /// </remarks>
  125. public int OptionTcpKeepAliveInterval { get; set; } = -1;
  126. /// <summary>
  127. /// Option: TCP keep alive retry count
  128. /// </summary>
  129. /// <remarks>
  130. /// The number of TCP keep alive probes that will be sent before the connection is terminated
  131. /// </remarks>
  132. public int OptionTcpKeepAliveRetryCount { get; set; } = -1;
  133. /// <summary>
  134. /// Option: no delay
  135. /// </summary>
  136. /// <remarks>
  137. /// This option will enable/disable Nagle's algorithm for SSL protocol
  138. /// </remarks>
  139. public bool OptionNoDelay { get; set; }
  140. /// <summary>
  141. /// Option: receive buffer limit
  142. /// </summary>
  143. public int OptionReceiveBufferLimit { get; set; } = 0;
  144. /// <summary>
  145. /// Option: receive buffer size
  146. /// </summary>
  147. public int OptionReceiveBufferSize { get; set; } = 8192;
  148. /// <summary>
  149. /// Option: send buffer limit
  150. /// </summary>
  151. public int OptionSendBufferLimit { get; set; } = 0;
  152. /// <summary>
  153. /// Option: send buffer size
  154. /// </summary>
  155. public int OptionSendBufferSize { get; set; } = 8192;
  156. #region Connect/Disconnect client
  157. private bool _disconnecting;
  158. private SocketAsyncEventArgs _connectEventArg;
  159. private SslStream _sslStream;
  160. private Guid? _sslStreamId;
  161. /// <summary>
  162. /// Is the client connecting?
  163. /// </summary>
  164. public bool IsConnecting { get; private set; }
  165. /// <summary>
  166. /// Is the client connected?
  167. /// </summary>
  168. public bool IsConnected { get; private set; }
  169. /// <summary>
  170. /// Is the client handshaking?
  171. /// </summary>
  172. public bool IsHandshaking { get; private set; }
  173. /// <summary>
  174. /// Is the client handshaked?
  175. /// </summary>
  176. public bool IsHandshaked { get; private set; }
  177. /// <summary>
  178. /// Create a new socket object
  179. /// </summary>
  180. /// <remarks>
  181. /// Method may be override if you need to prepare some specific socket object in your implementation.
  182. /// </remarks>
  183. /// <returns>Socket object</returns>
  184. protected virtual Socket CreateSocket()
  185. {
  186. return new Socket(Endpoint.AddressFamily, SocketType.Stream, ProtocolType.Tcp);
  187. }
  188. /// <summary>
  189. /// Connect the client (synchronous)
  190. /// </summary>
  191. /// <remarks>
  192. /// Please note that synchronous connect will not receive data automatically!
  193. /// You should use Receive() or ReceiveAsync() method manually after successful connection.
  194. /// </remarks>
  195. /// <returns>'true' if the client was successfully connected, 'false' if the client failed to connect</returns>
  196. public virtual bool Connect()
  197. {
  198. if (IsConnected || IsHandshaked || IsConnecting || IsHandshaking)
  199. return false;
  200. // Setup buffers
  201. _receiveBuffer = new Buffer();
  202. _sendBufferMain = new Buffer();
  203. _sendBufferFlush = new Buffer();
  204. // Setup event args
  205. _connectEventArg = new SocketAsyncEventArgs();
  206. _connectEventArg.RemoteEndPoint = Endpoint;
  207. _connectEventArg.Completed += OnAsyncCompleted;
  208. // Create a new client socket
  209. Socket = CreateSocket();
  210. // Update the client socket disposed flag
  211. IsSocketDisposed = false;
  212. // Apply the option: dual mode (this option must be applied before connecting)
  213. if (Socket.AddressFamily == AddressFamily.InterNetworkV6)
  214. Socket.DualMode = OptionDualMode;
  215. // Call the client connecting handler
  216. OnConnecting();
  217. try
  218. {
  219. // Connect to the server
  220. Socket.Connect(Endpoint);
  221. }
  222. catch (SocketException ex)
  223. {
  224. // Call the client error handler
  225. SendError(ex.SocketErrorCode);
  226. // Reset event args
  227. _connectEventArg.Completed -= OnAsyncCompleted;
  228. // Call the client disconnecting handler
  229. OnDisconnecting();
  230. // Close the client socket
  231. Socket.Close();
  232. // Dispose the client socket
  233. Socket.Dispose();
  234. // Dispose event arguments
  235. _connectEventArg.Dispose();
  236. // Call the client disconnected handler
  237. OnDisconnected();
  238. return false;
  239. }
  240. // Apply the option: keep alive
  241. if (OptionKeepAlive)
  242. Socket.SetSocketOption(SocketOptionLevel.Socket, SocketOptionName.KeepAlive, true);
  243. if (OptionTcpKeepAliveTime >= 0)
  244. Socket.SetSocketOption(SocketOptionLevel.Tcp, SocketOptionName.TcpKeepAliveTime, OptionTcpKeepAliveTime);
  245. if (OptionTcpKeepAliveInterval >= 0)
  246. Socket.SetSocketOption(SocketOptionLevel.Tcp, SocketOptionName.TcpKeepAliveInterval, OptionTcpKeepAliveInterval);
  247. if (OptionTcpKeepAliveRetryCount >= 0)
  248. Socket.SetSocketOption(SocketOptionLevel.Tcp, SocketOptionName.TcpKeepAliveRetryCount, OptionTcpKeepAliveRetryCount);
  249. // Apply the option: no delay
  250. if (OptionNoDelay)
  251. Socket.SetSocketOption(SocketOptionLevel.Tcp, SocketOptionName.NoDelay, true);
  252. // Prepare receive & send buffers
  253. _receiveBuffer.Reserve(OptionReceiveBufferSize);
  254. _sendBufferMain.Reserve(OptionSendBufferSize);
  255. _sendBufferFlush.Reserve(OptionSendBufferSize);
  256. // Reset statistic
  257. BytesPending = 0;
  258. BytesSending = 0;
  259. BytesSent = 0;
  260. BytesReceived = 0;
  261. // Update the connected flag
  262. IsConnected = true;
  263. // Call the client connected handler
  264. OnConnected();
  265. try
  266. {
  267. // Create SSL stream
  268. _sslStreamId = Guid.NewGuid();
  269. _sslStream = (Context.CertificateValidationCallback != null) ? new SslStream(new NetworkStream(Socket, false), false, Context.CertificateValidationCallback) : new SslStream(new NetworkStream(Socket, false), false);
  270. // Call the session handshaking handler
  271. OnHandshaking();
  272. // SSL handshake
  273. if (Context.Certificates != null)
  274. _sslStream.AuthenticateAsClient(Address, Context.Certificates, Context.Protocols, true);
  275. else if (Context.Certificate != null)
  276. _sslStream.AuthenticateAsClient(Address, new X509CertificateCollection(new[] { Context.Certificate }), Context.Protocols, true);
  277. else
  278. _sslStream.AuthenticateAsClient(Address);
  279. }
  280. catch (Exception)
  281. {
  282. SendError(SocketError.NotConnected);
  283. DisconnectAsync();
  284. return false;
  285. }
  286. // Update the handshaked flag
  287. IsHandshaked = true;
  288. // Call the session handshaked handler
  289. OnHandshaked();
  290. // Call the empty send buffer handler
  291. if (_sendBufferMain.IsEmpty)
  292. OnEmpty();
  293. return true;
  294. }
  295. /// <summary>
  296. /// Disconnect the client (synchronous)
  297. /// </summary>
  298. /// <returns>'true' if the client was successfully disconnected, 'false' if the client is already disconnected</returns>
  299. public virtual bool Disconnect()
  300. {
  301. if (!IsConnected && !IsConnecting)
  302. return false;
  303. // Cancel connecting operation
  304. if (IsConnecting)
  305. Socket.CancelConnectAsync(_connectEventArg);
  306. if (_disconnecting)
  307. return false;
  308. // Reset connecting & handshaking flags
  309. IsConnecting = false;
  310. IsHandshaking = false;
  311. // Update the disconnecting flag
  312. _disconnecting = true;
  313. // Reset event args
  314. _connectEventArg.Completed -= OnAsyncCompleted;
  315. // Call the client disconnecting handler
  316. OnDisconnecting();
  317. try
  318. {
  319. try
  320. {
  321. // Shutdown the SSL stream
  322. _sslStream.ShutdownAsync().Wait();
  323. }
  324. catch (Exception) {}
  325. // Dispose the SSL stream & buffer
  326. _sslStream.Dispose();
  327. _sslStreamId = null;
  328. try
  329. {
  330. // Shutdown the socket associated with the client
  331. Socket.Shutdown(SocketShutdown.Both);
  332. }
  333. catch (SocketException) {}
  334. // Close the client socket
  335. Socket.Close();
  336. // Dispose the client socket
  337. Socket.Dispose();
  338. // Dispose event arguments
  339. _connectEventArg.Dispose();
  340. // Update the client socket disposed flag
  341. IsSocketDisposed = true;
  342. }
  343. catch (ObjectDisposedException) {}
  344. // Update the handshaked flag
  345. IsHandshaked = false;
  346. // Update the connected flag
  347. IsConnected = false;
  348. // Update sending/receiving flags
  349. _receiving = false;
  350. _sending = false;
  351. // Clear send/receive buffers
  352. ClearBuffers();
  353. // Call the client disconnected handler
  354. OnDisconnected();
  355. // Reset the disconnecting flag
  356. _disconnecting = false;
  357. return true;
  358. }
  359. /// <summary>
  360. /// Reconnect the client (synchronous)
  361. /// </summary>
  362. /// <returns>'true' if the client was successfully reconnected, 'false' if the client is already reconnected</returns>
  363. public virtual bool Reconnect()
  364. {
  365. if (!Disconnect())
  366. return false;
  367. return Connect();
  368. }
  369. /// <summary>
  370. /// Connect the client (asynchronous)
  371. /// </summary>
  372. /// <returns>'true' if the client was successfully connected, 'false' if the client failed to connect</returns>
  373. public virtual bool ConnectAsync()
  374. {
  375. if (IsConnected || IsHandshaked || IsConnecting || IsHandshaking)
  376. return false;
  377. // Setup buffers
  378. _receiveBuffer = new Buffer();
  379. _sendBufferMain = new Buffer();
  380. _sendBufferFlush = new Buffer();
  381. // Setup event args
  382. _connectEventArg = new SocketAsyncEventArgs();
  383. _connectEventArg.RemoteEndPoint = Endpoint;
  384. _connectEventArg.Completed += OnAsyncCompleted;
  385. // Create a new client socket
  386. Socket = CreateSocket();
  387. // Update the client socket disposed flag
  388. IsSocketDisposed = false;
  389. // Apply the option: dual mode (this option must be applied before connecting)
  390. if (Socket.AddressFamily == AddressFamily.InterNetworkV6)
  391. Socket.DualMode = OptionDualMode;
  392. // Update the connecting flag
  393. IsConnecting = true;
  394. // Call the client connecting handler
  395. OnConnecting();
  396. // Async connect to the server
  397. if (!Socket.ConnectAsync(_connectEventArg))
  398. ProcessConnect(_connectEventArg);
  399. return true;
  400. }
  401. /// <summary>
  402. /// Disconnect the client (asynchronous)
  403. /// </summary>
  404. /// <returns>'true' if the client was successfully disconnected, 'false' if the client is already disconnected</returns>
  405. public virtual bool DisconnectAsync() => Disconnect();
  406. /// <summary>
  407. /// Reconnect the client (asynchronous)
  408. /// </summary>
  409. /// <returns>'true' if the client was successfully reconnected, 'false' if the client is already reconnected</returns>
  410. public virtual bool ReconnectAsync()
  411. {
  412. if (!DisconnectAsync())
  413. return false;
  414. while (IsConnected)
  415. Thread.Yield();
  416. return ConnectAsync();
  417. }
  418. #endregion
  419. #region Send/Receive data
  420. // Receive buffer
  421. private bool _receiving;
  422. private Buffer _receiveBuffer;
  423. // Send buffer
  424. private readonly object _sendLock = new object();
  425. private bool _sending;
  426. private Buffer _sendBufferMain;
  427. private Buffer _sendBufferFlush;
  428. private long _sendBufferFlushOffset;
  429. /// <summary>
  430. /// Send data to the server (synchronous)
  431. /// </summary>
  432. /// <param name="buffer">Buffer to send</param>
  433. /// <returns>Size of sent data</returns>
  434. public virtual long Send(byte[] buffer) => Send(buffer.AsSpan());
  435. /// <summary>
  436. /// Send data to the server (synchronous)
  437. /// </summary>
  438. /// <param name="buffer">Buffer to send</param>
  439. /// <param name="offset">Buffer offset</param>
  440. /// <param name="size">Buffer size</param>
  441. /// <returns>Size of sent data</returns>
  442. public virtual long Send(byte[] buffer, long offset, long size) => Send(buffer.AsSpan((int)offset, (int)size));
  443. /// <summary>
  444. /// Send data to the server (synchronous)
  445. /// </summary>
  446. /// <param name="buffer">Buffer to send as a span of bytes</param>
  447. /// <returns>Size of sent data</returns>
  448. public virtual long Send(ReadOnlySpan<byte> buffer)
  449. {
  450. if (!IsHandshaked)
  451. return 0;
  452. if (buffer.IsEmpty)
  453. return 0;
  454. try
  455. {
  456. // Sent data to the server
  457. _sslStream.Write(buffer);
  458. long sent = buffer.Length;
  459. // Update statistic
  460. BytesSent += sent;
  461. // Call the buffer sent handler
  462. OnSent(sent, BytesPending + BytesSending);
  463. return sent;
  464. }
  465. catch (Exception)
  466. {
  467. SendError(SocketError.OperationAborted);
  468. Disconnect();
  469. return 0;
  470. }
  471. }
  472. /// <summary>
  473. /// Send text to the server (synchronous)
  474. /// </summary>
  475. /// <param name="text">Text string to send</param>
  476. /// <returns>Size of sent text</returns>
  477. public virtual long Send(string text) => Send(Encoding.UTF8.GetBytes(text));
  478. /// <summary>
  479. /// Send text to the server (synchronous)
  480. /// </summary>
  481. /// <param name="text">Text to send as a span of characters</param>
  482. /// <returns>Size of sent text</returns>
  483. public virtual long Send(ReadOnlySpan<char> text) => Send(Encoding.UTF8.GetBytes(text.ToArray()));
  484. /// <summary>
  485. /// Send data to the server (asynchronous)
  486. /// </summary>
  487. /// <param name="buffer">Buffer to send</param>
  488. /// <returns>'true' if the data was successfully sent, 'false' if the client is not connected</returns>
  489. public virtual bool SendAsync(byte[] buffer) => SendAsync(buffer.AsSpan());
  490. /// <summary>
  491. /// Send data to the server (asynchronous)
  492. /// </summary>
  493. /// <param name="buffer">Buffer to send</param>
  494. /// <param name="offset">Buffer offset</param>
  495. /// <param name="size">Buffer size</param>
  496. /// <returns>'true' if the data was successfully sent, 'false' if the client is not connected</returns>
  497. public virtual bool SendAsync(byte[] buffer, long offset, long size) => SendAsync(buffer.AsSpan((int)offset, (int)size));
  498. /// <summary>
  499. /// Send data to the server (asynchronous)
  500. /// </summary>
  501. /// <param name="buffer">Buffer to send as a span of bytes</param>
  502. /// <returns>'true' if the data was successfully sent, 'false' if the client is not connected</returns>
  503. public virtual bool SendAsync(ReadOnlySpan<byte> buffer)
  504. {
  505. if (!IsHandshaked)
  506. return false;
  507. if (buffer.IsEmpty)
  508. return true;
  509. lock (_sendLock)
  510. {
  511. // Check the send buffer limit
  512. if (((_sendBufferMain.Size + buffer.Length) > OptionSendBufferLimit) && (OptionSendBufferLimit > 0))
  513. {
  514. SendError(SocketError.NoBufferSpaceAvailable);
  515. return false;
  516. }
  517. // Fill the main send buffer
  518. _sendBufferMain.Append(buffer);
  519. // Update statistic
  520. BytesPending = _sendBufferMain.Size;
  521. // Avoid multiple send handlers
  522. if (_sending)
  523. return true;
  524. else
  525. _sending = true;
  526. // Try to send the main buffer
  527. TrySend();
  528. }
  529. return true;
  530. }
  531. /// <summary>
  532. /// Send text to the server (asynchronous)
  533. /// </summary>
  534. /// <param name="text">Text string to send</param>
  535. /// <returns>'true' if the text was successfully sent, 'false' if the client is not connected</returns>
  536. public virtual bool SendAsync(string text) => SendAsync(Encoding.UTF8.GetBytes(text));
  537. /// <summary>
  538. /// Send text to the server (asynchronous)
  539. /// </summary>
  540. /// <param name="text">Text to send as a span of characters</param>
  541. /// <returns>'true' if the text was successfully sent, 'false' if the client is not connected</returns>
  542. public virtual bool SendAsync(ReadOnlySpan<char> text) => SendAsync(Encoding.UTF8.GetBytes(text.ToArray()));
  543. /// <summary>
  544. /// Receive data from the server (synchronous)
  545. /// </summary>
  546. /// <param name="buffer">Buffer to receive</param>
  547. /// <returns>Size of received data</returns>
  548. public virtual long Receive(byte[] buffer) { return Receive(buffer, 0, buffer.Length); }
  549. /// <summary>
  550. /// Receive data from the server (synchronous)
  551. /// </summary>
  552. /// <param name="buffer">Buffer to receive</param>
  553. /// <param name="offset">Buffer offset</param>
  554. /// <param name="size">Buffer size</param>
  555. /// <returns>Size of received data</returns>
  556. public virtual long Receive(byte[] buffer, long offset, long size)
  557. {
  558. if (!IsHandshaked)
  559. return 0;
  560. if (size == 0)
  561. return 0;
  562. try
  563. {
  564. // Receive data from the server
  565. long received = _sslStream.Read(buffer, (int)offset, (int)size);
  566. if (received > 0)
  567. {
  568. // Update statistic
  569. BytesReceived += received;
  570. // Call the buffer received handler
  571. OnReceived(buffer, 0, received);
  572. }
  573. return received;
  574. }
  575. catch (Exception)
  576. {
  577. SendError(SocketError.OperationAborted);
  578. Disconnect();
  579. return 0;
  580. }
  581. }
  582. /// <summary>
  583. /// Receive text from the server (synchronous)
  584. /// </summary>
  585. /// <param name="size">Text size to receive</param>
  586. /// <returns>Received text</returns>
  587. public virtual string Receive(long size)
  588. {
  589. var buffer = new byte[size];
  590. var length = Receive(buffer);
  591. return Encoding.UTF8.GetString(buffer, 0, (int)length);
  592. }
  593. /// <summary>
  594. /// Receive data from the server (asynchronous)
  595. /// </summary>
  596. public virtual void ReceiveAsync()
  597. {
  598. // Try to receive data from the server
  599. TryReceive();
  600. }
  601. /// <summary>
  602. /// Try to receive new data
  603. /// </summary>
  604. private void TryReceive()
  605. {
  606. if (_receiving)
  607. return;
  608. if (!IsHandshaked)
  609. return;
  610. try
  611. {
  612. // Async receive with the receive handler
  613. IAsyncResult result;
  614. do
  615. {
  616. if (!IsHandshaked)
  617. return;
  618. _receiving = true;
  619. result = _sslStream.BeginRead(_receiveBuffer.Data, 0, (int)_receiveBuffer.Capacity, ProcessReceive, _sslStreamId);
  620. } while (result.CompletedSynchronously);
  621. }
  622. catch (ObjectDisposedException) {}
  623. }
  624. /// <summary>
  625. /// Try to send pending data
  626. /// </summary>
  627. private void TrySend()
  628. {
  629. if (!IsHandshaked)
  630. return;
  631. bool empty = false;
  632. lock (_sendLock)
  633. {
  634. // Is previous socket send in progress?
  635. if (_sendBufferFlush.IsEmpty)
  636. {
  637. // Swap flush and main buffers
  638. _sendBufferFlush = Interlocked.Exchange(ref _sendBufferMain, _sendBufferFlush);
  639. _sendBufferFlushOffset = 0;
  640. // Update statistic
  641. BytesPending = 0;
  642. BytesSending += _sendBufferFlush.Size;
  643. // Check if the flush buffer is empty
  644. if (_sendBufferFlush.IsEmpty)
  645. {
  646. // Need to call empty send buffer handler
  647. empty = true;
  648. // End sending process
  649. _sending = false;
  650. }
  651. }
  652. else
  653. return;
  654. }
  655. // Call the empty send buffer handler
  656. if (empty)
  657. {
  658. OnEmpty();
  659. return;
  660. }
  661. try
  662. {
  663. // Async write with the write handler
  664. _sslStream.BeginWrite(_sendBufferFlush.Data, (int)_sendBufferFlushOffset, (int)(_sendBufferFlush.Size - _sendBufferFlushOffset), ProcessSend, _sslStreamId);
  665. }
  666. catch (ObjectDisposedException) {}
  667. }
  668. /// <summary>
  669. /// Clear send/receive buffers
  670. /// </summary>
  671. private void ClearBuffers()
  672. {
  673. lock (_sendLock)
  674. {
  675. // Clear send buffers
  676. _sendBufferMain.Clear();
  677. _sendBufferFlush.Clear();
  678. _sendBufferFlushOffset= 0;
  679. // Update statistic
  680. BytesPending = 0;
  681. BytesSending = 0;
  682. }
  683. }
  684. #endregion
  685. #region IO processing
  686. /// <summary>
  687. /// This method is called whenever a receive or send operation is completed on a socket
  688. /// </summary>
  689. private void OnAsyncCompleted(object sender, SocketAsyncEventArgs e)
  690. {
  691. if (IsSocketDisposed)
  692. return;
  693. // Determine which type of operation just completed and call the associated handler
  694. switch (e.LastOperation)
  695. {
  696. case SocketAsyncOperation.Connect:
  697. ProcessConnect(e);
  698. break;
  699. default:
  700. throw new ArgumentException("The last operation completed on the socket was not a receive or send");
  701. }
  702. }
  703. /// <summary>
  704. /// This method is invoked when an asynchronous connect operation completes
  705. /// </summary>
  706. private void ProcessConnect(SocketAsyncEventArgs e)
  707. {
  708. IsConnecting = false;
  709. if (e.SocketError == SocketError.Success)
  710. {
  711. // Apply the option: keep alive
  712. if (OptionKeepAlive)
  713. Socket.SetSocketOption(SocketOptionLevel.Socket, SocketOptionName.KeepAlive, true);
  714. if (OptionTcpKeepAliveTime >= 0)
  715. Socket.SetSocketOption(SocketOptionLevel.Tcp, SocketOptionName.TcpKeepAliveTime, OptionTcpKeepAliveTime);
  716. if (OptionTcpKeepAliveInterval >= 0)
  717. Socket.SetSocketOption(SocketOptionLevel.Tcp, SocketOptionName.TcpKeepAliveInterval, OptionTcpKeepAliveInterval);
  718. if (OptionTcpKeepAliveRetryCount >= 0)
  719. Socket.SetSocketOption(SocketOptionLevel.Tcp, SocketOptionName.TcpKeepAliveRetryCount, OptionTcpKeepAliveRetryCount);
  720. // Apply the option: no delay
  721. if (OptionNoDelay)
  722. Socket.SetSocketOption(SocketOptionLevel.Tcp, SocketOptionName.NoDelay, true);
  723. // Prepare receive & send buffers
  724. _receiveBuffer.Reserve(OptionReceiveBufferSize);
  725. _sendBufferMain.Reserve(OptionSendBufferSize);
  726. _sendBufferFlush.Reserve(OptionSendBufferSize);
  727. // Reset statistic
  728. BytesPending = 0;
  729. BytesSending = 0;
  730. BytesSent = 0;
  731. BytesReceived = 0;
  732. // Update the connected flag
  733. IsConnected = true;
  734. // Call the client connected handler
  735. OnConnected();
  736. try
  737. {
  738. // Create SSL stream
  739. _sslStreamId = Guid.NewGuid();
  740. _sslStream = (Context.CertificateValidationCallback != null) ? new SslStream(new NetworkStream(Socket, false), false, Context.CertificateValidationCallback) : new SslStream(new NetworkStream(Socket, false), false);
  741. // Call the session handshaking handler
  742. OnHandshaking();
  743. // Begin the SSL handshake
  744. IsHandshaking = true;
  745. if (Context.Certificates != null)
  746. _sslStream.BeginAuthenticateAsClient(Address, Context.Certificates, Context.Protocols, true, ProcessHandshake, _sslStreamId);
  747. else if (Context.Certificate != null)
  748. _sslStream.BeginAuthenticateAsClient(Address, new X509CertificateCollection(new[] { Context.Certificate }), Context.Protocols, true, ProcessHandshake, _sslStreamId);
  749. else
  750. _sslStream.BeginAuthenticateAsClient(Address, ProcessHandshake, _sslStreamId);
  751. }
  752. catch (Exception)
  753. {
  754. SendError(SocketError.NotConnected);
  755. DisconnectAsync();
  756. }
  757. }
  758. else
  759. {
  760. // Call the client disconnected handler
  761. SendError(e.SocketError);
  762. OnDisconnected();
  763. }
  764. }
  765. /// <summary>
  766. /// This method is invoked when an asynchronous handshake operation completes
  767. /// </summary>
  768. private void ProcessHandshake(IAsyncResult result)
  769. {
  770. try
  771. {
  772. IsHandshaking = false;
  773. if (IsHandshaked)
  774. return;
  775. // Validate SSL stream Id
  776. var sslStreamId = result.AsyncState as Guid?;
  777. if (_sslStreamId != sslStreamId)
  778. return;
  779. // End the SSL handshake
  780. _sslStream.EndAuthenticateAsClient(result);
  781. // Update the handshaked flag
  782. IsHandshaked = true;
  783. // Try to receive something from the server
  784. TryReceive();
  785. // Check the socket disposed state: in some rare cases it might be disconnected while receiving!
  786. if (IsSocketDisposed)
  787. return;
  788. // Call the session handshaked handler
  789. OnHandshaked();
  790. // Call the empty send buffer handler
  791. if (_sendBufferMain.IsEmpty)
  792. OnEmpty();
  793. }
  794. catch (Exception)
  795. {
  796. SendError(SocketError.NotConnected);
  797. DisconnectAsync();
  798. }
  799. }
  800. /// <summary>
  801. /// This method is invoked when an asynchronous receive operation completes
  802. /// </summary>
  803. private void ProcessReceive(IAsyncResult result)
  804. {
  805. try
  806. {
  807. if (!IsHandshaked)
  808. return;
  809. // Validate SSL stream Id
  810. var sslStreamId = result.AsyncState as Guid?;
  811. if (_sslStreamId != sslStreamId)
  812. return;
  813. // End the SSL read
  814. long size = _sslStream.EndRead(result);
  815. // Received some data from the server
  816. if (size > 0)
  817. {
  818. // Update statistic
  819. BytesReceived += size;
  820. // Call the buffer received handler
  821. OnReceived(_receiveBuffer.Data, 0, size);
  822. // If the receive buffer is full increase its size
  823. if (_receiveBuffer.Capacity == size)
  824. {
  825. // Check the receive buffer limit
  826. if (((2 * size) > OptionReceiveBufferLimit) && (OptionReceiveBufferLimit > 0))
  827. {
  828. SendError(SocketError.NoBufferSpaceAvailable);
  829. DisconnectAsync();
  830. return;
  831. }
  832. _receiveBuffer.Reserve(2 * size);
  833. }
  834. }
  835. _receiving = false;
  836. // If zero is returned from a read operation, the remote end has closed the connection
  837. if (size > 0)
  838. {
  839. if (!result.CompletedSynchronously)
  840. TryReceive();
  841. }
  842. else
  843. DisconnectAsync();
  844. }
  845. catch (Exception)
  846. {
  847. SendError(SocketError.OperationAborted);
  848. DisconnectAsync();
  849. }
  850. }
  851. /// <summary>
  852. /// This method is invoked when an asynchronous send operation completes
  853. /// </summary>
  854. private void ProcessSend(IAsyncResult result)
  855. {
  856. try
  857. {
  858. if (!IsHandshaked)
  859. return;
  860. // Validate SSL stream Id
  861. var sslStreamId = result.AsyncState as Guid?;
  862. if (_sslStreamId != sslStreamId)
  863. return;
  864. // End the SSL write
  865. _sslStream.EndWrite(result);
  866. long size = _sendBufferFlush.Size;
  867. // Send some data to the server
  868. if (size > 0)
  869. {
  870. // Update statistic
  871. BytesSending -= size;
  872. BytesSent += size;
  873. // Increase the flush buffer offset
  874. _sendBufferFlushOffset += size;
  875. // Successfully send the whole flush buffer
  876. if (_sendBufferFlushOffset == _sendBufferFlush.Size)
  877. {
  878. // Clear the flush buffer
  879. _sendBufferFlush.Clear();
  880. _sendBufferFlushOffset = 0;
  881. }
  882. // Call the buffer sent handler
  883. OnSent(size, BytesPending + BytesSending);
  884. }
  885. // Try to send again if the client is valid
  886. TrySend();
  887. }
  888. catch (Exception)
  889. {
  890. SendError(SocketError.OperationAborted);
  891. DisconnectAsync();
  892. }
  893. }
  894. #endregion
  895. #region Session handlers
  896. /// <summary>
  897. /// Handle client connecting notification
  898. /// </summary>
  899. protected virtual void OnConnecting() {}
  900. /// <summary>
  901. /// Handle client connected notification
  902. /// </summary>
  903. protected virtual void OnConnected() {}
  904. /// <summary>
  905. /// Handle client handshaking notification
  906. /// </summary>
  907. protected virtual void OnHandshaking() {}
  908. /// <summary>
  909. /// Handle client handshaked notification
  910. /// </summary>
  911. protected virtual void OnHandshaked() {}
  912. /// <summary>
  913. /// Handle client disconnecting notification
  914. /// </summary>
  915. protected virtual void OnDisconnecting() {}
  916. /// <summary>
  917. /// Handle client disconnected notification
  918. /// </summary>
  919. protected virtual void OnDisconnected() {}
  920. /// <summary>
  921. /// Handle buffer received notification
  922. /// </summary>
  923. /// <param name="buffer">Received buffer</param>
  924. /// <param name="offset">Received buffer offset</param>
  925. /// <param name="size">Received buffer size</param>
  926. /// <remarks>
  927. /// Notification is called when another part of buffer was received from the server
  928. /// </remarks>
  929. protected virtual void OnReceived(byte[] buffer, long offset, long size) {}
  930. /// <summary>
  931. /// Handle buffer sent notification
  932. /// </summary>
  933. /// <param name="sent">Size of sent buffer</param>
  934. /// <param name="pending">Size of pending buffer</param>
  935. /// <remarks>
  936. /// Notification is called when another part of buffer was sent to the server.
  937. /// This handler could be used to send another buffer to the server for instance when the pending size is zero.
  938. /// </remarks>
  939. protected virtual void OnSent(long sent, long pending) {}
  940. /// <summary>
  941. /// Handle empty send buffer notification
  942. /// </summary>
  943. /// <remarks>
  944. /// Notification is called when the send buffer is empty and ready for a new data to send.
  945. /// This handler could be used to send another buffer to the server.
  946. /// </remarks>
  947. protected virtual void OnEmpty() {}
  948. /// <summary>
  949. /// Handle error notification
  950. /// </summary>
  951. /// <param name="error">Socket error code</param>
  952. protected virtual void OnError(SocketError error) {}
  953. #endregion
  954. #region Error handling
  955. /// <summary>
  956. /// Send error notification
  957. /// </summary>
  958. /// <param name="error">Socket error code</param>
  959. private void SendError(SocketError error)
  960. {
  961. // Skip disconnect errors
  962. if ((error == SocketError.ConnectionAborted) ||
  963. (error == SocketError.ConnectionRefused) ||
  964. (error == SocketError.ConnectionReset) ||
  965. (error == SocketError.OperationAborted) ||
  966. (error == SocketError.Shutdown))
  967. return;
  968. OnError(error);
  969. }
  970. #endregion
  971. #region IDisposable implementation
  972. /// <summary>
  973. /// Disposed flag
  974. /// </summary>
  975. public bool IsDisposed { get; private set; }
  976. /// <summary>
  977. /// Client socket disposed flag
  978. /// </summary>
  979. public bool IsSocketDisposed { get; private set; } = true;
  980. // Implement IDisposable.
  981. public void Dispose()
  982. {
  983. Dispose(true);
  984. GC.SuppressFinalize(this);
  985. }
  986. protected virtual void Dispose(bool disposingManagedResources)
  987. {
  988. // The idea here is that Dispose(Boolean) knows whether it is
  989. // being called to do explicit cleanup (the Boolean is true)
  990. // versus being called due to a garbage collection (the Boolean
  991. // is false). This distinction is useful because, when being
  992. // disposed explicitly, the Dispose(Boolean) method can safely
  993. // execute code using reference type fields that refer to other
  994. // objects knowing for sure that these other objects have not been
  995. // finalized or disposed of yet. When the Boolean is false,
  996. // the Dispose(Boolean) method should not execute code that
  997. // refer to reference type fields because those objects may
  998. // have already been finalized."
  999. if (!IsDisposed)
  1000. {
  1001. if (disposingManagedResources)
  1002. {
  1003. // Dispose managed resources here...
  1004. DisconnectAsync();
  1005. }
  1006. // Dispose unmanaged resources here...
  1007. // Set large fields to null here...
  1008. // Mark as disposed.
  1009. IsDisposed = true;
  1010. }
  1011. }
  1012. #endregion
  1013. }
  1014. }