Extensions.cs 8.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166
  1. using System;
  2. using System.Net;
  3. using System.Net.Sockets;
  4. namespace kcp2k
  5. {
  6. public static class Extensions
  7. {
  8. // ArraySegment as HexString for convenience
  9. public static string ToHexString(this ArraySegment<byte> segment) =>
  10. BitConverter.ToString(segment.Array, segment.Offset, segment.Count);
  11. // non-blocking UDP send.
  12. // allows for reuse when overwriting KcpServer/Client (i.e. for relays).
  13. // => wrapped with Poll to avoid WouldBlock allocating new SocketException.
  14. // => wrapped with try-catch to ignore WouldBlock exception.
  15. // make sure to set socket.Blocking = false before using this!
  16. public static bool SendToNonBlocking(this Socket socket, ArraySegment<byte> data, EndPoint remoteEP)
  17. {
  18. try
  19. {
  20. // when using non-blocking sockets, SendTo may return WouldBlock.
  21. // in C#, WouldBlock throws a SocketException, which is expected.
  22. // unfortunately, creating the SocketException allocates in C#.
  23. // let's poll first to avoid the WouldBlock allocation.
  24. // note that this entirely to avoid allocations.
  25. // non-blocking UDP doesn't need Poll in other languages.
  26. // and the code still works without the Poll call.
  27. if (!socket.Poll(0, SelectMode.SelectWrite)) return false;
  28. // send to the the endpoint.
  29. // do not send to 'newClientEP', as that's always reused.
  30. // fixes https://github.com/MirrorNetworking/Mirror/issues/3296
  31. socket.SendTo(data.Array, data.Offset, data.Count, SocketFlags.None, remoteEP);
  32. return true;
  33. }
  34. catch (SocketException e)
  35. {
  36. // for non-blocking sockets, SendTo may throw WouldBlock.
  37. // in that case, simply drop the message. it's UDP, it's fine.
  38. if (e.SocketErrorCode == SocketError.WouldBlock) return false;
  39. // otherwise it's a real socket error. throw it.
  40. throw;
  41. }
  42. }
  43. // non-blocking UDP send.
  44. // allows for reuse when overwriting KcpServer/Client (i.e. for relays).
  45. // => wrapped with Poll to avoid WouldBlock allocating new SocketException.
  46. // => wrapped with try-catch to ignore WouldBlock exception.
  47. // make sure to set socket.Blocking = false before using this!
  48. public static bool SendNonBlocking(this Socket socket, ArraySegment<byte> data)
  49. {
  50. try
  51. {
  52. // when using non-blocking sockets, SendTo may return WouldBlock.
  53. // in C#, WouldBlock throws a SocketException, which is expected.
  54. // unfortunately, creating the SocketException allocates in C#.
  55. // let's poll first to avoid the WouldBlock allocation.
  56. // note that this entirely to avoid allocations.
  57. // non-blocking UDP doesn't need Poll in other languages.
  58. // and the code still works without the Poll call.
  59. if (!socket.Poll(0, SelectMode.SelectWrite)) return false;
  60. // SendTo allocates. we used bound Send.
  61. socket.Send(data.Array, data.Offset, data.Count, SocketFlags.None);
  62. return true;
  63. }
  64. catch (SocketException e)
  65. {
  66. // for non-blocking sockets, SendTo may throw WouldBlock.
  67. // in that case, simply drop the message. it's UDP, it's fine.
  68. if (e.SocketErrorCode == SocketError.WouldBlock) return false;
  69. // otherwise it's a real socket error. throw it.
  70. throw;
  71. }
  72. }
  73. // non-blocking UDP receive.
  74. // allows for reuse when overwriting KcpServer/Client (i.e. for relays).
  75. // => wrapped with Poll to avoid WouldBlock allocating new SocketException.
  76. // => wrapped with try-catch to ignore WouldBlock exception.
  77. // make sure to set socket.Blocking = false before using this!
  78. public static bool ReceiveFromNonBlocking(this Socket socket, byte[] recvBuffer, out ArraySegment<byte> data, ref EndPoint remoteEP)
  79. {
  80. data = default;
  81. try
  82. {
  83. // when using non-blocking sockets, ReceiveFrom may return WouldBlock.
  84. // in C#, WouldBlock throws a SocketException, which is expected.
  85. // unfortunately, creating the SocketException allocates in C#.
  86. // let's poll first to avoid the WouldBlock allocation.
  87. // note that this entirely to avoid allocations.
  88. // non-blocking UDP doesn't need Poll in other languages.
  89. // and the code still works without the Poll call.
  90. if (!socket.Poll(0, SelectMode.SelectRead)) return false;
  91. // NOTE: ReceiveFrom allocates.
  92. // we pass our IPEndPoint to ReceiveFrom.
  93. // receive from calls newClientEP.Create(socketAddr).
  94. // IPEndPoint.Create always returns a new IPEndPoint.
  95. // https://github.com/mono/mono/blob/f74eed4b09790a0929889ad7fc2cf96c9b6e3757/mcs/class/System/System.Net.Sockets/Socket.cs#L1761
  96. //
  97. // throws SocketException if datagram was larger than buffer.
  98. // https://learn.microsoft.com/en-us/dotnet/api/system.net.sockets.socket.receive?view=net-6.0
  99. int size = socket.ReceiveFrom(recvBuffer, 0, recvBuffer.Length, SocketFlags.None, ref remoteEP);
  100. data = new ArraySegment<byte>(recvBuffer, 0, size);
  101. return true;
  102. }
  103. catch (SocketException e)
  104. {
  105. // for non-blocking sockets, Receive throws WouldBlock if there is
  106. // no message to read. that's okay. only log for other errors.
  107. if (e.SocketErrorCode == SocketError.WouldBlock) return false;
  108. // otherwise it's a real socket error. throw it.
  109. throw;
  110. }
  111. }
  112. // non-blocking UDP receive.
  113. // allows for reuse when overwriting KcpServer/Client (i.e. for relays).
  114. // => wrapped with Poll to avoid WouldBlock allocating new SocketException.
  115. // => wrapped with try-catch to ignore WouldBlock exception.
  116. // make sure to set socket.Blocking = false before using this!
  117. public static bool ReceiveNonBlocking(this Socket socket, byte[] recvBuffer, out ArraySegment<byte> data)
  118. {
  119. data = default;
  120. try
  121. {
  122. // when using non-blocking sockets, ReceiveFrom may return WouldBlock.
  123. // in C#, WouldBlock throws a SocketException, which is expected.
  124. // unfortunately, creating the SocketException allocates in C#.
  125. // let's poll first to avoid the WouldBlock allocation.
  126. // note that this entirely to avoid allocations.
  127. // non-blocking UDP doesn't need Poll in other languages.
  128. // and the code still works without the Poll call.
  129. if (!socket.Poll(0, SelectMode.SelectRead)) return false;
  130. // ReceiveFrom allocates. we used bound Receive.
  131. // returns amount of bytes written into buffer.
  132. // throws SocketException if datagram was larger than buffer.
  133. // https://learn.microsoft.com/en-us/dotnet/api/system.net.sockets.socket.receive?view=net-6.0
  134. //
  135. // throws SocketException if datagram was larger than buffer.
  136. // https://learn.microsoft.com/en-us/dotnet/api/system.net.sockets.socket.receive?view=net-6.0
  137. int size = socket.Receive(recvBuffer, 0, recvBuffer.Length, SocketFlags.None);
  138. data = new ArraySegment<byte>(recvBuffer, 0, size);
  139. return true;
  140. }
  141. catch (SocketException e)
  142. {
  143. // for non-blocking sockets, Receive throws WouldBlock if there is
  144. // no message to read. that's okay. only log for other errors.
  145. if (e.SocketErrorCode == SocketError.WouldBlock) return false;
  146. // otherwise it's a real socket error. throw it.
  147. throw;
  148. }
  149. }
  150. }
  151. }