diff --git a/Apewer.Web/WebSocket/SimpleServer.cs b/Apewer.Web/WebSocket/SimpleServer.cs new file mode 100644 index 0000000..c3fc423 --- /dev/null +++ b/Apewer.Web/WebSocket/SimpleServer.cs @@ -0,0 +1,176 @@ +#if !NET20 + +using System; +using System.Collections.Generic; +using System.Linq; +using System.Net; +using System.Text; + +namespace Apewer.WebSocket +{ + + /// 简易的 WebSocket 服务器。 + public class SimpleServer + { + + static GenericServer _server = null; + static Dictionary _clients = new Dictionary(); + + /// 服务端实例。 + public static GenericServer Server { get => _server; } + + /// 消息日志记录程序。 + public static Logger Logger { get; set; } + + /// 当前已连接的客户端。 + public static string[] Clients + { + get + { + lock (_clients) + { + var endpoints = _clients.Keys.ToArray(); + return endpoints; + } + } + } + + /// 收到消息时的回调。 + public static Action OnMessage { get; set; } + + /// 收到消息时的回调。 + public static Action OnBytes { get; set; } + + /// 客户端断开的回调 + public static Action OnClose { get; set; } + + /// 客户端连接的回调。 + public static Action OnOpen { get; set; } + + /// 发生错误触发的事件。 + public static Action OnError { get; set; } + + /// 启动服务端。 + /// + public static GenericServer Run(IPEndPoint endpoint) + { + if (_server != null) throw new InvalidOperationException($"已经存在服务端实例,无法再次启动。"); + if (endpoint == null) throw new ArgumentNullException(nameof(endpoint)); + + var port = endpoint.Port; + if (NetworkUtility.ActiveTcpPorts().Contains(port)) + { + var error = $"端口 {port} 已被占用,无法启动 WebSocket 服务器。"; + Logger?.Text($"WebSocket {port}", "Run", error); + throw new Exception(error); + } + + _server = new GenericServer(); + _server.OnPing += (s, e) => s.Send("PONG"); + _server.OnError += (conn, ex) => RuntimeUtility.InBackground(() => + { + Logger?.Text($"WebSocket {conn.Port}", "OnError", $"{conn.Address}:{conn.Port}", ex.GetType().Name, ex.Message); + OnError?.Invoke(conn, ex); + }); + _server.OnOpen += (conn) => + { + lock (_clients) + { + var endpoint = $"{conn.Address}:{conn.Port}"; + if (endpoint.Contains(endpoint)) _clients[endpoint] = conn; + else _clients.Add(endpoint, conn); + } + RuntimeUtility.InBackground(() => + { + Logger?.Text($"WebSocket {port}", "OnOpen", $"{conn.Address}:{conn.Port}"); + OnOpen?.Invoke(conn); + }); + }; + _server.OnClose += (conn) => + { + lock (_clients) + { + var endpoint = $"{conn.Address}:{conn.Port}"; + if (_clients.ContainsKey(endpoint)) _clients.Remove(endpoint); + } + RuntimeUtility.InBackground(() => + { + Logger?.Text($"WebSocket {port}", "OnClose", $"{conn.Address}:{conn.Port}"); + OnClose?.Invoke(conn); + }); + }; + _server.OnBytes += (conn, bytes) => + { + if (bytes.IsEmpty()) return; + RuntimeUtility.InBackground(() => + { + Logger?.Text($"WebSocket {port}", "OnBytes", $"{conn.Address}:{conn.Port}", $"Length = {bytes.Length}", bytes.ToX2(" ")); + OnBytes?.Invoke(conn, bytes); + }); + }; + _server.OnMessage += (conn, text) => + { + if (text.IsEmpty()) return; + switch (text.Lower()) + { + case "ping": + conn.Send("pong"); + return; + case "pong": + return; + } + RuntimeUtility.InBackground(() => + { + Logger?.Text($"WebSocket {port}", "OnMessage", $"{conn.Address}:{conn.Port}", text); + OnMessage?.Invoke(conn, text); + }); + }; + _server.Start(port); + + Logger?.Text($"WebSocket {port}", "Run", "已启动。"); + return _server; + } + + /// 向单个客户端发送文本消息。 + public static void Send(string endpoint, string message) + { + if (endpoint.IsEmpty()) return; + if (message.IsEmpty()) return; + + Connection conn; + lock (_clients) + { + if (!_clients.TryGetValue(endpoint, out conn)) + { + return; + // throw new Exception($"终结点【{endpoint}】不存在。"); + } + } + + Logger?.Text($"WebSocket {conn.Port}", "Send", endpoint, message); + conn.Send(message); + } + + /// 向所有客户端广播文本消息。 + public static void Broadcast(string message) + { + if (message.IsEmpty()) return; + var endpoints = Clients; + foreach (var endpoint in endpoints) + { + try + { + Send(endpoint, message); + } + catch { } + } + } + + /// 向所有客户端广播文本消息。 + public static void Broadcast(object json) => Broadcast(Json.From(json)); + + } + +} + +#endif