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.

182 lines
6.3 KiB

4 years ago
  1. #if !NET20
  2. using System;
  3. using System.Collections.Generic;
  4. using System.Net;
  5. using System.Net.Sockets;
  6. using System.Security.Authentication;
  7. using System.Security.Cryptography.X509Certificates;
  8. namespace Apewer.WebSocket
  9. {
  10. internal class WebSocketServer : IDisposable
  11. {
  12. private readonly string _scheme;
  13. private readonly IPAddress _locationIP;
  14. private Action<Connection> _config;
  15. /// <summary></summary>
  16. /// <exception cref="FormatException"></exception>
  17. public WebSocketServer(string location, bool supportDualStack = true)
  18. {
  19. var uri = new Uri(location);
  20. Port = uri.Port;
  21. Location = location;
  22. SupportDualStack = supportDualStack;
  23. _locationIP = ParseIPAddress(uri);
  24. _scheme = uri.Scheme;
  25. var socket = new Socket(_locationIP.AddressFamily, SocketType.Stream, ProtocolType.IP);
  26. if (SupportDualStack)
  27. {
  28. if (!Runtime.IsRunningOnMono() && Runtime.IsRunningOnWindows())
  29. {
  30. socket.SetSocketOption(SocketOptionLevel.IPv6, SocketOptionName.IPv6Only, false);
  31. socket.SetSocketOption(SocketOptionLevel.Socket, SocketOptionName.ReuseAddress, 1);
  32. }
  33. }
  34. ListenerSocket = new SocketWrapper(socket);
  35. SupportedSubProtocols = new string[0];
  36. }
  37. public SocketWrapper ListenerSocket { get; set; }
  38. public string Location { get; private set; }
  39. public bool SupportDualStack { get; }
  40. public int Port { get; private set; }
  41. public X509Certificate2 Certificate { get; set; }
  42. public SslProtocols EnabledSslProtocols { get; set; }
  43. public IEnumerable<string> SupportedSubProtocols { get; set; }
  44. public bool RestartAfterListenError { get; set; }
  45. public bool IsSecure
  46. {
  47. get { return _scheme == "wss" && Certificate != null; }
  48. }
  49. public void Dispose()
  50. {
  51. ListenerSocket.Dispose();
  52. }
  53. /// <summary></summary>
  54. /// <exception cref="FormatException"></exception>
  55. private IPAddress ParseIPAddress(Uri uri)
  56. {
  57. string ipStr = uri.Host;
  58. if (ipStr == "0.0.0.0")
  59. {
  60. return IPAddress.Any;
  61. }
  62. else if (ipStr == "[0000:0000:0000:0000:0000:0000:0000:0000]")
  63. {
  64. return IPAddress.IPv6Any;
  65. }
  66. else
  67. {
  68. try
  69. {
  70. return IPAddress.Parse(ipStr);
  71. }
  72. catch (Exception ex)
  73. {
  74. throw new FormatException("Failed to parse the IP address part of the location. Please make sure you specify a valid IP address. Use 0.0.0.0 or [::] to listen on all interfaces.", ex);
  75. }
  76. }
  77. }
  78. public void Start(Action<Connection> config)
  79. {
  80. var ipLocal = new IPEndPoint(_locationIP, Port);
  81. ListenerSocket.Bind(ipLocal);
  82. ListenerSocket.Listen(100);
  83. Port = ((IPEndPoint)ListenerSocket.LocalEndPoint).Port;
  84. WebSocketLog.Info(string.Format("Server started at {0} (actual port {1})", Location, Port));
  85. if (_scheme == "wss")
  86. {
  87. if (Certificate == null)
  88. {
  89. WebSocketLog.Error("Scheme cannot be 'wss' without a Certificate");
  90. return;
  91. }
  92. if (EnabledSslProtocols == SslProtocols.None)
  93. {
  94. EnabledSslProtocols = SslProtocols.Tls;
  95. WebSocketLog.Debug("Using default TLS 1.0 security protocol.");
  96. }
  97. }
  98. ListenForClients();
  99. _config = config;
  100. }
  101. private void ListenForClients()
  102. {
  103. ListenerSocket.Accept(OnClientConnect, e =>
  104. {
  105. WebSocketLog.Error("Listener socket is closed", e);
  106. if (RestartAfterListenError)
  107. {
  108. WebSocketLog.Info("Listener socket restarting");
  109. try
  110. {
  111. ListenerSocket.Dispose();
  112. var socket = new Socket(_locationIP.AddressFamily, SocketType.Stream, ProtocolType.IP);
  113. ListenerSocket = new SocketWrapper(socket);
  114. Start(_config);
  115. WebSocketLog.Info("Listener socket restarted");
  116. }
  117. catch (Exception ex)
  118. {
  119. WebSocketLog.Error("Listener could not be restarted", ex);
  120. }
  121. }
  122. });
  123. }
  124. private void OnClientConnect(SocketWrapper clientSocket)
  125. {
  126. if (clientSocket == null) return; // socket closed
  127. WebSocketLog.Debug(String.Format("Client connected from {0}:{1}", clientSocket.RemoteIpAddress, clientSocket.RemotePort.ToString()));
  128. ListenForClients();
  129. Connection connection = null;
  130. connection = new Connection(
  131. clientSocket,
  132. _config,
  133. bytes => RequestParser.Parse(bytes, _scheme),
  134. r => HandlerFactory.BuildHandler(r,
  135. s => connection.OnMessage(s),
  136. connection.Close,
  137. b => connection.OnBytes(b),
  138. b => connection.OnPing(b),
  139. b => connection.OnPong(b)),
  140. s => SubProtocolNegotiator.Negotiate(SupportedSubProtocols, s));
  141. if (IsSecure)
  142. {
  143. WebSocketLog.Debug("Authenticating Secure Connection");
  144. clientSocket
  145. .Authenticate(Certificate,
  146. EnabledSslProtocols,
  147. connection.StartReceiving,
  148. e => WebSocketLog.Warn("Failed to Authenticate", e));
  149. }
  150. else
  151. {
  152. connection.StartReceiving();
  153. }
  154. }
  155. }
  156. }
  157. #endif