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.

176 lines
6.0 KiB

  1. #if !NET20
  2. using System;
  3. using System.Collections.Generic;
  4. using System.Linq;
  5. using System.Net;
  6. using System.Text;
  7. namespace Apewer.WebSocket
  8. {
  9. /// <summary>简易的 WebSocket 服务器。</summary>
  10. public class SimpleServer
  11. {
  12. static GenericServer _server = null;
  13. static Dictionary<string, Connection> _clients = new Dictionary<string, Connection>();
  14. /// <summary>服务端实例。</summary>
  15. public static GenericServer Server { get => _server; }
  16. /// <summary>消息日志记录程序。</summary>
  17. public static Logger Logger { get; set; }
  18. /// <summary>当前已连接的客户端。</summary>
  19. public static string[] Clients
  20. {
  21. get
  22. {
  23. lock (_clients)
  24. {
  25. var endpoints = _clients.Keys.ToArray();
  26. return endpoints;
  27. }
  28. }
  29. }
  30. /// <summary>收到消息时的回调。</summary>
  31. public static Action<Connection, string> OnMessage { get; set; }
  32. /// <summary>收到消息时的回调。</summary>
  33. public static Action<Connection, byte[]> OnBytes { get; set; }
  34. /// <summary>客户端断开的回调</summary>
  35. public static Action<Connection> OnClose { get; set; }
  36. /// <summary>客户端连接的回调。</summary>
  37. public static Action<Connection> OnOpen { get; set; }
  38. /// <summary>发生错误触发的事件。</summary>
  39. public static Action<Connection, Exception> OnError { get; set; }
  40. /// <summary>启动服务端。</summary>
  41. /// <exception cref="Exception" />
  42. public static GenericServer Run(IPEndPoint endpoint)
  43. {
  44. if (_server != null) throw new InvalidOperationException($"已经存在服务端实例,无法再次启动。");
  45. if (endpoint == null) throw new ArgumentNullException(nameof(endpoint));
  46. var port = endpoint.Port;
  47. if (NetworkUtility.ActiveTcpPorts().Contains(port))
  48. {
  49. var error = $"端口 {port} 已被占用,无法启动 WebSocket 服务器。";
  50. Logger?.Text($"WebSocket {port}", "Run", error);
  51. throw new Exception(error);
  52. }
  53. _server = new GenericServer();
  54. _server.OnPing += (s, e) => s.Send("PONG");
  55. _server.OnError += (conn, ex) => RuntimeUtility.InBackground(() =>
  56. {
  57. Logger?.Text($"WebSocket {conn.Port}", "OnError", $"{conn.Address}:{conn.Port}", ex.GetType().Name, ex.Message);
  58. OnError?.Invoke(conn, ex);
  59. });
  60. _server.OnOpen += (conn) =>
  61. {
  62. lock (_clients)
  63. {
  64. var endpoint = $"{conn.Address}:{conn.Port}";
  65. if (endpoint.Contains(endpoint)) _clients[endpoint] = conn;
  66. else _clients.Add(endpoint, conn);
  67. }
  68. RuntimeUtility.InBackground(() =>
  69. {
  70. Logger?.Text($"WebSocket {port}", "OnOpen", $"{conn.Address}:{conn.Port}");
  71. OnOpen?.Invoke(conn);
  72. });
  73. };
  74. _server.OnClose += (conn) =>
  75. {
  76. lock (_clients)
  77. {
  78. var endpoint = $"{conn.Address}:{conn.Port}";
  79. if (_clients.ContainsKey(endpoint)) _clients.Remove(endpoint);
  80. }
  81. RuntimeUtility.InBackground(() =>
  82. {
  83. Logger?.Text($"WebSocket {port}", "OnClose", $"{conn.Address}:{conn.Port}");
  84. OnClose?.Invoke(conn);
  85. });
  86. };
  87. _server.OnBytes += (conn, bytes) =>
  88. {
  89. if (bytes.IsEmpty()) return;
  90. RuntimeUtility.InBackground(() =>
  91. {
  92. Logger?.Text($"WebSocket {port}", "OnBytes", $"{conn.Address}:{conn.Port}", $"Length = {bytes.Length}", bytes.ToX2(" "));
  93. OnBytes?.Invoke(conn, bytes);
  94. });
  95. };
  96. _server.OnMessage += (conn, text) =>
  97. {
  98. if (text.IsEmpty()) return;
  99. switch (text.Lower())
  100. {
  101. case "ping":
  102. conn.Send("pong");
  103. return;
  104. case "pong":
  105. return;
  106. }
  107. RuntimeUtility.InBackground(() =>
  108. {
  109. Logger?.Text($"WebSocket {port}", "OnMessage", $"{conn.Address}:{conn.Port}", text);
  110. OnMessage?.Invoke(conn, text);
  111. });
  112. };
  113. _server.Start(port);
  114. Logger?.Text($"WebSocket {port}", "Run", "已启动。");
  115. return _server;
  116. }
  117. /// <summary>向单个客户端发送文本消息。</summary>
  118. public static void Send(string endpoint, string message)
  119. {
  120. if (endpoint.IsEmpty()) return;
  121. if (message.IsEmpty()) return;
  122. Connection conn;
  123. lock (_clients)
  124. {
  125. if (!_clients.TryGetValue(endpoint, out conn))
  126. {
  127. return;
  128. // throw new Exception($"终结点【{endpoint}】不存在。");
  129. }
  130. }
  131. Logger?.Text($"WebSocket {conn.Port}", "Send", endpoint, message);
  132. conn.Send(message);
  133. }
  134. /// <summary>向所有客户端广播文本消息。</summary>
  135. public static void Broadcast(string message)
  136. {
  137. if (message.IsEmpty()) return;
  138. var endpoints = Clients;
  139. foreach (var endpoint in endpoints)
  140. {
  141. try
  142. {
  143. Send(endpoint, message);
  144. }
  145. catch { }
  146. }
  147. }
  148. /// <summary>向所有客户端广播文本消息。</summary>
  149. public static void Broadcast(object json) => Broadcast(Json.From(json));
  150. }
  151. }
  152. #endif