UdpClient.cs 36 KB

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