#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