You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 
 

268 lines
7.8 KiB

  1. using Microsoft.AspNetCore.Connections;
  2. using Microsoft.AspNetCore.Http.Features;
  3. using MQTTnet.Exceptions;
  4. using System;
  5. using System.Collections.Generic;
  6. using System.IO;
  7. using System.IO.Pipelines;
  8. using System.Net;
  9. using System.Net.Sockets;
  10. using System.Threading.Tasks;
  11. namespace MQTTnet.AspNetCore.Client.Tcp
  12. {
  13. public class TcpConnection : ConnectionContext
  14. {
  15. private volatile bool _aborted;
  16. private readonly EndPoint _endPoint;
  17. private SocketSender _sender;
  18. private SocketReceiver _receiver;
  19. private Socket _socket;
  20. private IDuplexPipe _application;
  21. public bool IsConnected { get; private set; }
  22. public override string ConnectionId { get; set; }
  23. public override IFeatureCollection Features { get; }
  24. public override IDictionary<object, object> Items { get; set; }
  25. public override IDuplexPipe Transport { get; set; }
  26. public TcpConnection(EndPoint endPoint)
  27. {
  28. _endPoint = endPoint;
  29. }
  30. public TcpConnection(Socket socket)
  31. {
  32. _socket = socket;
  33. _endPoint = socket.RemoteEndPoint;
  34. _sender = new SocketSender(_socket, PipeScheduler.ThreadPool);
  35. _receiver = new SocketReceiver(_socket, PipeScheduler.ThreadPool);
  36. }
  37. #if NETCOREAPP3_1 || NET5_0
  38. public override ValueTask DisposeAsync()
  39. #else
  40. public Task DisposeAsync()
  41. #endif
  42. {
  43. IsConnected = false;
  44. Transport?.Output.Complete();
  45. Transport?.Input.Complete();
  46. _socket?.Dispose();
  47. #if NETCOREAPP3_1 || NET5_0
  48. return base.DisposeAsync();
  49. }
  50. #else
  51. return Task.CompletedTask;
  52. }
  53. #endif
  54. public async Task StartAsync()
  55. {
  56. if (_socket == null)
  57. {
  58. _socket = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);
  59. _sender = new SocketSender(_socket, PipeScheduler.ThreadPool);
  60. _receiver = new SocketReceiver(_socket, PipeScheduler.ThreadPool);
  61. await _socket.ConnectAsync(_endPoint);
  62. }
  63. var pair = DuplexPipe.CreateConnectionPair(PipeOptions.Default, PipeOptions.Default);
  64. Transport = pair.Transport;
  65. _application = pair.Application;
  66. _ = ExecuteAsync();
  67. IsConnected = true;
  68. }
  69. private async Task ExecuteAsync()
  70. {
  71. Exception sendError = null;
  72. try
  73. {
  74. // Spawn send and receive logic
  75. var receiveTask = DoReceive();
  76. var sendTask = DoSend();
  77. // If the sending task completes then close the receive
  78. // We don't need to do this in the other direction because the kestrel
  79. // will trigger the output closing once the input is complete.
  80. if (await Task.WhenAny(receiveTask, sendTask) == sendTask)
  81. {
  82. // Tell the reader it's being aborted
  83. _socket.Dispose();
  84. }
  85. // Now wait for both to complete
  86. await receiveTask;
  87. sendError = await sendTask;
  88. // Dispose the socket(should noop if already called)
  89. _socket.Dispose();
  90. }
  91. catch (Exception ex)
  92. {
  93. Console.WriteLine($"Unexpected exception in {nameof(TcpConnection)}.{nameof(StartAsync)}: " + ex);
  94. }
  95. finally
  96. {
  97. // Complete the output after disposing the socket
  98. _application.Input.Complete(sendError);
  99. }
  100. }
  101. private async Task DoReceive()
  102. {
  103. Exception error = null;
  104. try
  105. {
  106. await ProcessReceives();
  107. }
  108. catch (SocketException ex) when (ex.SocketErrorCode == SocketError.ConnectionReset)
  109. {
  110. error = new MqttCommunicationException(ex);
  111. }
  112. catch (SocketException ex) when (ex.SocketErrorCode == SocketError.OperationAborted ||
  113. ex.SocketErrorCode == SocketError.ConnectionAborted ||
  114. ex.SocketErrorCode == SocketError.Interrupted ||
  115. ex.SocketErrorCode == SocketError.InvalidArgument)
  116. {
  117. if (!_aborted)
  118. {
  119. // Calling Dispose after ReceiveAsync can cause an "InvalidArgument" error on *nix.
  120. error = ConnectionAborted();
  121. }
  122. }
  123. catch (ObjectDisposedException)
  124. {
  125. if (!_aborted)
  126. {
  127. error = ConnectionAborted();
  128. }
  129. }
  130. catch (IOException ex)
  131. {
  132. error = ex;
  133. }
  134. catch (Exception ex)
  135. {
  136. error = new IOException(ex.Message, ex);
  137. }
  138. finally
  139. {
  140. if (_aborted)
  141. {
  142. error = error ?? ConnectionAborted();
  143. }
  144. _application.Output.Complete(error);
  145. }
  146. }
  147. private async Task ProcessReceives()
  148. {
  149. while (true)
  150. {
  151. // Ensure we have some reasonable amount of buffer space
  152. var buffer = _application.Output.GetMemory();
  153. var bytesReceived = await _receiver.ReceiveAsync(buffer);
  154. if (bytesReceived == 0)
  155. {
  156. // FIN
  157. break;
  158. }
  159. _application.Output.Advance(bytesReceived);
  160. var flushTask = _application.Output.FlushAsync();
  161. if (!flushTask.IsCompleted)
  162. {
  163. await flushTask;
  164. }
  165. var result = flushTask.GetAwaiter().GetResult();
  166. if (result.IsCompleted)
  167. {
  168. // Pipe consumer is shut down, do we stop writing
  169. break;
  170. }
  171. }
  172. }
  173. private Exception ConnectionAborted()
  174. {
  175. return new MqttCommunicationException("Connection Aborted");
  176. }
  177. private async Task<Exception> DoSend()
  178. {
  179. Exception error = null;
  180. try
  181. {
  182. await ProcessSends();
  183. }
  184. catch (SocketException ex) when (ex.SocketErrorCode == SocketError.OperationAborted)
  185. {
  186. }
  187. catch (ObjectDisposedException)
  188. {
  189. }
  190. catch (IOException ex)
  191. {
  192. error = ex;
  193. }
  194. catch (Exception ex)
  195. {
  196. error = new IOException(ex.Message, ex);
  197. }
  198. finally
  199. {
  200. _aborted = true;
  201. _socket.Shutdown(SocketShutdown.Both);
  202. }
  203. return error;
  204. }
  205. private async Task ProcessSends()
  206. {
  207. while (true)
  208. {
  209. // Wait for data to write from the pipe producer
  210. var result = await _application.Input.ReadAsync();
  211. var buffer = result.Buffer;
  212. if (result.IsCanceled)
  213. {
  214. break;
  215. }
  216. var end = buffer.End;
  217. var isCompleted = result.IsCompleted;
  218. if (!buffer.IsEmpty)
  219. {
  220. await _sender.SendAsync(buffer);
  221. }
  222. _application.Input.AdvanceTo(end);
  223. if (isCompleted)
  224. {
  225. break;
  226. }
  227. }
  228. }
  229. }
  230. }