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