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.
 
 
 
 

272 lines
8.0 KiB

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