TcpClient.cs 37 KB

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