AsyncSocketListener.cs 14 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398
  1. #pragma warning disable IDE0005 // Using directive is unnecessary; IntegrationTests use implicit usings
  2. using System;
  3. using System.Collections.Generic;
  4. using System.Net;
  5. using System.Net.Sockets;
  6. using System.Threading;
  7. #pragma warning restore IDE0005
  8. namespace Renci.SshNet.Tests.Common
  9. {
  10. public class AsyncSocketListener : IDisposable
  11. {
  12. private readonly IPEndPoint _endPoint;
  13. private readonly ManualResetEvent _acceptCallbackDone;
  14. private readonly List<Socket> _connectedClients;
  15. private readonly object _syncLock;
  16. private Socket _listener;
  17. private Thread _receiveThread;
  18. private bool _started;
  19. private string _stackTrace;
  20. public delegate void BytesReceivedHandler(byte[] bytesReceived, Socket socket);
  21. public delegate void ConnectedHandler(Socket socket);
  22. public event BytesReceivedHandler BytesReceived;
  23. public event ConnectedHandler Connected;
  24. public event ConnectedHandler Disconnected;
  25. public AsyncSocketListener(IPEndPoint endPoint)
  26. {
  27. _endPoint = endPoint;
  28. _acceptCallbackDone = new ManualResetEvent(false);
  29. _connectedClients = new List<Socket>();
  30. _syncLock = new object();
  31. ShutdownRemoteCommunicationSocket = true;
  32. }
  33. /// <summary>
  34. /// Gets a value indicating whether the <see cref="Socket.Shutdown(SocketShutdown)"/> is invoked on the <see cref="Socket"/>
  35. /// that is used to handle the communication with the remote host, when the remote host has closed the connection.
  36. /// </summary>
  37. /// <value>
  38. /// <see langword="true"/> to invoke <see cref="Socket.Shutdown(SocketShutdown)"/> on the <see cref="Socket"/> that is used
  39. /// to handle the communication with the remote host, when the remote host has closed the connection; otherwise,
  40. /// <see langword="false"/>. The default is <see langword="true"/>.
  41. /// </value>
  42. public bool ShutdownRemoteCommunicationSocket { get; set; }
  43. public void Start()
  44. {
  45. _listener = new Socket(_endPoint.AddressFamily, SocketType.Stream, ProtocolType.Tcp);
  46. _listener.Bind(_endPoint);
  47. _listener.Listen(1);
  48. _started = true;
  49. _receiveThread = new Thread(StartListener);
  50. _receiveThread.Start(_listener);
  51. _stackTrace = Environment.StackTrace;
  52. }
  53. public void Stop()
  54. {
  55. _started = false;
  56. lock (_syncLock)
  57. {
  58. foreach (var connectedClient in _connectedClients)
  59. {
  60. try
  61. {
  62. connectedClient.Shutdown(SocketShutdown.Send);
  63. }
  64. catch (Exception ex)
  65. {
  66. Console.Error.WriteLine("[{0}] Failure shutting down socket: {1}",
  67. typeof(AsyncSocketListener).FullName,
  68. ex);
  69. }
  70. DrainSocket(connectedClient);
  71. connectedClient.Dispose();
  72. }
  73. _connectedClients.Clear();
  74. }
  75. _listener?.Dispose();
  76. if (_receiveThread != null)
  77. {
  78. _receiveThread.Join();
  79. _receiveThread = null;
  80. }
  81. }
  82. public void Dispose()
  83. {
  84. Stop();
  85. GC.SuppressFinalize(this);
  86. }
  87. private void StartListener(object state)
  88. {
  89. try
  90. {
  91. var listener = (Socket)state;
  92. while (_started)
  93. {
  94. _ = _acceptCallbackDone.Reset();
  95. _ = listener.BeginAccept(AcceptCallback, listener);
  96. _ = _acceptCallbackDone.WaitOne();
  97. }
  98. }
  99. catch (Exception ex)
  100. {
  101. // On .NET framework when Thread throws an exception then unit tests
  102. // were executed without any problem.
  103. // On new .NET exceptions from Thread breaks unit tests session.
  104. Console.Error.WriteLine("[{0}] Failure in StartListener: {1}",
  105. typeof(AsyncSocketListener).FullName,
  106. ex);
  107. }
  108. }
  109. private void AcceptCallback(IAsyncResult ar)
  110. {
  111. // Signal the main thread to continue
  112. _ = _acceptCallbackDone.Set();
  113. // Get the socket that listens for inbound connections
  114. var listener = (Socket)ar.AsyncState;
  115. // Get the socket that handles the client request
  116. Socket handler;
  117. try
  118. {
  119. handler = listener.EndAccept(ar);
  120. }
  121. catch (SocketException ex)
  122. {
  123. // The listener is stopped through a Dispose() call, which in turn causes
  124. // Socket.EndAccept(...) to throw a SocketException or
  125. // ObjectDisposedException
  126. //
  127. // Since we consider such an exception normal when the listener is being
  128. // stopped, we only write a message to stderr if the listener is considered
  129. // to be up and running
  130. if (_started)
  131. {
  132. Console.Error.WriteLine("[{0}] Failure accepting new connection: {1}",
  133. typeof(AsyncSocketListener).FullName,
  134. ex);
  135. }
  136. return;
  137. }
  138. catch (ObjectDisposedException ex)
  139. {
  140. // The listener is stopped through a Dispose() call, which in turn causes
  141. // Socket.EndAccept(IAsyncResult) to throw a SocketException or
  142. // ObjectDisposedException
  143. //
  144. // Since we consider such an exception normal when the listener is being
  145. // stopped, we only write a message to stderr if the listener is considered
  146. // to be up and running
  147. if (_started)
  148. {
  149. Console.Error.WriteLine("[{0}] Failure accepting new connection: {1}",
  150. typeof(AsyncSocketListener).FullName,
  151. ex);
  152. }
  153. return;
  154. }
  155. // Signal new connection
  156. SignalConnected(handler);
  157. lock (_syncLock)
  158. {
  159. // Register client socket
  160. _connectedClients.Add(handler);
  161. }
  162. var state = new SocketStateObject(handler);
  163. try
  164. {
  165. _ = handler.BeginReceive(state.Buffer, 0, state.Buffer.Length, 0, ReadCallback, state);
  166. }
  167. catch (SocketException ex)
  168. {
  169. // The listener is stopped through a Dispose() call, which in turn causes
  170. // Socket.BeginReceive(...) to throw a SocketException or
  171. // ObjectDisposedException
  172. //
  173. // Since we consider such an exception normal when the listener is being
  174. // stopped, we only write a message to stderr if the listener is considered
  175. // to be up and running
  176. if (_started)
  177. {
  178. Console.Error.WriteLine("[{0}] Failure receiving new data: {1}",
  179. typeof(AsyncSocketListener).FullName,
  180. ex);
  181. }
  182. }
  183. catch (ObjectDisposedException ex)
  184. {
  185. // The listener is stopped through a Dispose() call, which in turn causes
  186. // Socket.BeginReceive(...) to throw a SocketException or
  187. // ObjectDisposedException
  188. //
  189. // Since we consider such an exception normal when the listener is being
  190. // stopped, we only write a message to stderr if the listener is considered
  191. // to be up and running
  192. if (_started)
  193. {
  194. Console.Error.WriteLine("[{0}] Failure receiving new data: {1}",
  195. typeof(AsyncSocketListener).FullName,
  196. ex);
  197. }
  198. }
  199. }
  200. private void ReadCallback(IAsyncResult ar)
  201. {
  202. // Retrieve the state object and the handler socket
  203. // from the asynchronous state object
  204. var state = (SocketStateObject)ar.AsyncState;
  205. var handler = state.Socket;
  206. int bytesRead;
  207. try
  208. {
  209. // Read data from the client socket.
  210. bytesRead = handler.EndReceive(ar);
  211. }
  212. catch (SocketException ex)
  213. {
  214. // The listener is stopped through a Dispose() call, which in turn causes
  215. // Socket.EndReceive(...) to throw a SocketException or
  216. // ObjectDisposedException
  217. //
  218. // Since we consider such an exception normal when the listener is being
  219. // stopped, we only write a message to stderr if the listener is considered
  220. // to be up and running
  221. if (_started)
  222. {
  223. Console.Error.WriteLine("[{0}] Failure receiving new data: {1}",
  224. typeof(AsyncSocketListener).FullName,
  225. ex);
  226. }
  227. return;
  228. }
  229. catch (ObjectDisposedException ex)
  230. {
  231. // The listener is stopped through a Dispose() call, which in turn causes
  232. // Socket.EndReceive(...) to throw a SocketException or
  233. // ObjectDisposedException
  234. //
  235. // Since we consider such an exception normal when the listener is being
  236. // stopped, we only write a message to stderr if the listener is considered
  237. // to be up and running
  238. if (_started)
  239. {
  240. Console.Error.WriteLine("[{0}] Failure receiving new data: {1}",
  241. typeof(AsyncSocketListener).FullName,
  242. ex);
  243. }
  244. return;
  245. }
  246. void ConnectionDisconnected()
  247. {
  248. SignalDisconnected(handler);
  249. if (ShutdownRemoteCommunicationSocket)
  250. {
  251. lock (_syncLock)
  252. {
  253. if (!_started)
  254. {
  255. return;
  256. }
  257. try
  258. {
  259. if (handler.Connected)
  260. {
  261. handler.Shutdown(SocketShutdown.Send);
  262. }
  263. handler.Close();
  264. }
  265. catch (SocketException ex) when (ex.SocketErrorCode == SocketError.ConnectionReset)
  266. {
  267. // On .NET 7 we got Socker Exception with ConnectionReset from Shutdown method
  268. // when the socket is disposed
  269. }
  270. catch (SocketException ex)
  271. {
  272. throw new Exception("Exception in ReadCallback: " + ex.SocketErrorCode + " " + _stackTrace, ex);
  273. }
  274. catch (Exception ex)
  275. {
  276. throw new Exception("Exception in ReadCallback: " + _stackTrace, ex);
  277. }
  278. _ = _connectedClients.Remove(handler);
  279. }
  280. }
  281. }
  282. if (bytesRead > 0)
  283. {
  284. var bytesReceived = new byte[bytesRead];
  285. Array.Copy(state.Buffer, bytesReceived, bytesRead);
  286. SignalBytesReceived(bytesReceived, handler);
  287. try
  288. {
  289. _ = handler.BeginReceive(state.Buffer, 0, state.Buffer.Length, 0, ReadCallback, state);
  290. }
  291. catch (ObjectDisposedException)
  292. {
  293. // TODO On .NET 7, sometimes we get ObjectDisposedException when _started but only on appveyor, locally it works
  294. ConnectionDisconnected();
  295. }
  296. catch (SocketException ex)
  297. {
  298. if (!_started)
  299. {
  300. throw new Exception("BeginReceive while stopping!", ex);
  301. }
  302. throw new Exception("BeginReceive while started!: " + ex.SocketErrorCode + " " + _stackTrace, ex);
  303. }
  304. }
  305. else
  306. {
  307. ConnectionDisconnected();
  308. }
  309. }
  310. private void SignalBytesReceived(byte[] bytesReceived, Socket client)
  311. {
  312. BytesReceived?.Invoke(bytesReceived, client);
  313. }
  314. private void SignalConnected(Socket client)
  315. {
  316. Connected?.Invoke(client);
  317. }
  318. private void SignalDisconnected(Socket client)
  319. {
  320. Disconnected?.Invoke(client);
  321. }
  322. private static void DrainSocket(Socket socket)
  323. {
  324. var buffer = new byte[128];
  325. try
  326. {
  327. while (true && socket.Connected)
  328. {
  329. var bytesRead = socket.Receive(buffer);
  330. if (bytesRead == 0)
  331. {
  332. break;
  333. }
  334. }
  335. }
  336. catch (SocketException ex)
  337. {
  338. Console.Error.WriteLine("[{0}] Failure draining socket ({1}): {2}",
  339. typeof(AsyncSocketListener).FullName,
  340. ex.SocketErrorCode.ToString("G"),
  341. ex);
  342. }
  343. }
  344. private class SocketStateObject
  345. {
  346. public Socket Socket { get; private set; }
  347. public readonly byte[] Buffer = new byte[2048];
  348. public SocketStateObject(Socket handler)
  349. {
  350. Socket = handler;
  351. }
  352. }
  353. }
  354. }