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.

239 lines
7.2 KiB

3 years ago
3 years ago
3 years ago
3 years ago
  1. using System;
  2. using System.Collections.Generic;
  3. using System.Net;
  4. using System.Net.Sockets;
  5. using System.Security.Authentication;
  6. using System.Security.Cryptography.X509Certificates;
  7. using System.Text;
  8. using System.Threading;
  9. namespace Apewer.Web
  10. {
  11. /// <summary></summary>
  12. public sealed class MiniServer
  13. {
  14. /// <summary></summary>
  15. public MiniServer()
  16. {
  17. SynchronousIO = true;
  18. MaxRequest = 1024 * 1024 * 1024;
  19. }
  20. #region configuration
  21. /// <summary>上下文处理程序。</summary>
  22. /// <remarks>默认值:未指定</remarks>
  23. public Action<MiniContext> Handler { get; set; }
  24. /// <summary>捕获处理请求的异常。</summary>
  25. /// <remarks>默认值:未指定</remarks>
  26. public Action<Exception> CatchException { get; set; }
  27. /// <summary>获取已指定的本地终结点。</summary>
  28. public IPEndPoint IPEndPoint { get; private set; }
  29. /// <summary>获取已监听的本地端口。</summary>
  30. public int Port { get => GetPort(); }
  31. /// <summary>入站超时毫秒数。指定 0 或 -1 时为无限大。</summary>
  32. /// <remarks>默认值:0</remarks>
  33. public int InboundTimeout { get; set; }
  34. /// <summary>出站超时毫秒数。指定 0 或 -1 时为无限大。</summary>
  35. /// <remarks>默认值:0</remarks>
  36. public int OutboundTimeout { get; set; }
  37. /// <summary>SSL 证书。</summary>
  38. internal X509Certificate SslCertificate { get; set; }
  39. /// <summary>SSL 域名。</summary>
  40. internal string SslDomain { get; set; }
  41. /// <summary>SSL 协议。</summary>
  42. internal SslProtocols SslProtocols { get; set; }
  43. /// <summary>同步 IO 操作。</summary>
  44. /// <remarks>默认值:True</remarks>
  45. internal bool SynchronousIO { get; set; }
  46. /// <summary>对响应体分块。</summary>
  47. /// <remarks>默认值:False</remarks>
  48. public bool Chunked { get; set; }
  49. /// <summary>限制请求大小。</summary>
  50. /// <remarks>默认值:1 GB。</remarks>
  51. public int MaxRequest { get; set; }
  52. /// <summary>允许压缩。</summary>
  53. public bool Compression { get; set; }
  54. #endregion
  55. #region launcher
  56. Thread _thread = null;
  57. /// <summary>启动服务器。</summary>
  58. /// <exception cref="ArgumentNullException"></exception>
  59. /// <exception cref="ArgumentOutOfRangeException"></exception>
  60. /// <exception cref="InvalidOperationException"></exception>
  61. /// <exception cref="SocketException"></exception>
  62. public void Run(int port = 0, bool await = true)
  63. {
  64. if (port < 0 || port > 65535) throw new ArgumentOutOfRangeException(nameof(port));
  65. Run(new IPEndPoint(IPAddress.Any, port), await);
  66. }
  67. /// <summary>启动服务器。</summary>
  68. /// <exception cref="ArgumentNullException"></exception>
  69. /// <exception cref="InvalidOperationException"></exception>
  70. /// <exception cref="SocketException"></exception>
  71. public void Run(IPEndPoint ipEndpoint, bool await = true)
  72. {
  73. if (_socket != null) throw new InvalidOperationException("存在已启动的 Socket。");
  74. if (ipEndpoint == null) throw new ArgumentNullException(nameof(ipEndpoint));
  75. IPEndPoint = ipEndpoint;
  76. var ipep = ipEndpoint;
  77. _socket = new Socket(ipep.AddressFamily, SocketType.Stream, ProtocolType.Tcp);
  78. _socket.Bind(ipep);
  79. _socket.Listen(500);
  80. var inTimeout = InboundTimeout;
  81. var outTimeout = OutboundTimeout;
  82. if (inTimeout > 0) _socket.ReceiveTimeout = inTimeout;
  83. if (outTimeout > 0) _socket.SendTimeout = outTimeout;
  84. _thread = new Thread(Listen);
  85. _thread.IsBackground = true;
  86. _thread.Start();
  87. if (await) while (_socket != null) Thread.Sleep(300);
  88. }
  89. /// <summary>关闭服务器。</summary>
  90. public void Shutdown()
  91. {
  92. var socket = _socket;
  93. if (socket != null)
  94. {
  95. try { socket.Shutdown(SocketShutdown.Both); } catch { }
  96. try { socket.Close(1); } catch { }
  97. _socket = null;
  98. }
  99. }
  100. #endregion
  101. #region listener
  102. Socket _socket = null;
  103. int GetPort()
  104. {
  105. var socket = _socket;
  106. if (socket == null) return 0;
  107. try
  108. {
  109. if (socket.LocalEndPoint is IPEndPoint ipep)
  110. {
  111. return ipep.Port;
  112. }
  113. }
  114. catch { }
  115. return 0;
  116. }
  117. void Listen()
  118. {
  119. if (SynchronousIO)
  120. {
  121. while (_socket != null)
  122. {
  123. var socket = null as Socket;
  124. try
  125. {
  126. socket = _socket.Accept();
  127. }
  128. catch
  129. {
  130. if (socket != null)
  131. {
  132. try { socket.Close(); } catch { }
  133. try { socket.Disconnect(false); } catch { }
  134. #if !NET20
  135. try { socket.Dispose(); } catch { }
  136. #endif
  137. }
  138. break;
  139. }
  140. if (socket != null) ThreadPool.QueueUserWorkItem(Process, socket);
  141. }
  142. }
  143. else
  144. {
  145. var e = new SocketAsyncEventArgs();
  146. e.UserToken = this;
  147. e.Completed += (sender, arg) => Process(arg);
  148. Socket dummy = null;
  149. Accept(e, ref dummy);
  150. }
  151. }
  152. void Accept(SocketAsyncEventArgs e, ref Socket socket)
  153. {
  154. e.AcceptSocket = null;
  155. var async = false;
  156. try
  157. {
  158. async = _socket.AcceptAsync(e);
  159. }
  160. catch
  161. {
  162. if (socket != null)
  163. {
  164. try { socket.Close(); } catch { }
  165. socket = null;
  166. }
  167. return;
  168. }
  169. if (!async) Process(e);
  170. }
  171. void Process(SocketAsyncEventArgs e)
  172. {
  173. var socket = null as Socket;
  174. if (e.SocketError == SocketError.Success) socket = e.AcceptSocket;
  175. Accept(e, ref socket);
  176. if (socket == null) return;
  177. Process(socket);
  178. }
  179. void Process(object obj)
  180. {
  181. var socket = obj as Socket;
  182. if (socket == null) return;
  183. try
  184. {
  185. var conn = null as MiniConnection;
  186. conn = new MiniConnection(this, socket);
  187. conn.BeginRead();
  188. }
  189. catch (Exception ex)
  190. {
  191. Shutdown();
  192. Logger.Web.Exception(ex, this);
  193. CatchException?.Invoke(ex);
  194. return;
  195. }
  196. }
  197. #endregion
  198. }
  199. }