1 changed files with 176 additions and 0 deletions
			
			
		@ -0,0 +1,176 @@ | 
				
			|||||
 | 
					#if !NET20
 | 
				
			||||
 | 
					
 | 
				
			||||
 | 
					using System; | 
				
			||||
 | 
					using System.Collections.Generic; | 
				
			||||
 | 
					using System.Linq; | 
				
			||||
 | 
					using System.Net; | 
				
			||||
 | 
					using System.Text; | 
				
			||||
 | 
					
 | 
				
			||||
 | 
					namespace Apewer.WebSocket | 
				
			||||
 | 
					{ | 
				
			||||
 | 
					
 | 
				
			||||
 | 
					    /// <summary>简易的 WebSocket 服务器。</summary>
 | 
				
			||||
 | 
					    public class SimpleServer | 
				
			||||
 | 
					    { | 
				
			||||
 | 
					
 | 
				
			||||
 | 
					        static GenericServer _server = null; | 
				
			||||
 | 
					        static Dictionary<string, Connection> _clients = new Dictionary<string, Connection>(); | 
				
			||||
 | 
					
 | 
				
			||||
 | 
					        /// <summary>服务端实例。</summary>
 | 
				
			||||
 | 
					        public static GenericServer Server { get => _server; } | 
				
			||||
 | 
					
 | 
				
			||||
 | 
					        /// <summary>消息日志记录程序。</summary>
 | 
				
			||||
 | 
					        public static Logger Logger { get; set; } | 
				
			||||
 | 
					
 | 
				
			||||
 | 
					        /// <summary>当前已连接的客户端。</summary>
 | 
				
			||||
 | 
					        public static string[] Clients | 
				
			||||
 | 
					        { | 
				
			||||
 | 
					            get | 
				
			||||
 | 
					            { | 
				
			||||
 | 
					                lock (_clients) | 
				
			||||
 | 
					                { | 
				
			||||
 | 
					                    var endpoints = _clients.Keys.ToArray(); | 
				
			||||
 | 
					                    return endpoints; | 
				
			||||
 | 
					                } | 
				
			||||
 | 
					            } | 
				
			||||
 | 
					        } | 
				
			||||
 | 
					
 | 
				
			||||
 | 
					        /// <summary>收到消息时的回调。</summary>
 | 
				
			||||
 | 
					        public static Action<Connection, string> OnMessage { get; set; } | 
				
			||||
 | 
					
 | 
				
			||||
 | 
					        /// <summary>收到消息时的回调。</summary>
 | 
				
			||||
 | 
					        public static Action<Connection, byte[]> OnBytes { get; set; } | 
				
			||||
 | 
					
 | 
				
			||||
 | 
					        /// <summary>客户端断开的回调</summary>
 | 
				
			||||
 | 
					        public static Action<Connection> OnClose { get; set; } | 
				
			||||
 | 
					
 | 
				
			||||
 | 
					        /// <summary>客户端连接的回调。</summary>
 | 
				
			||||
 | 
					        public static Action<Connection> OnOpen { get; set; } | 
				
			||||
 | 
					
 | 
				
			||||
 | 
					        /// <summary>发生错误触发的事件。</summary>
 | 
				
			||||
 | 
					        public static Action<Connection, Exception> OnError { get; set; } | 
				
			||||
 | 
					
 | 
				
			||||
 | 
					        /// <summary>启动服务端。</summary>
 | 
				
			||||
 | 
					        /// <exception cref="Exception" />
 | 
				
			||||
 | 
					        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; | 
				
			||||
 | 
					        } | 
				
			||||
 | 
					
 | 
				
			||||
 | 
					        /// <summary>向单个客户端发送文本消息。</summary>
 | 
				
			||||
 | 
					        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); | 
				
			||||
 | 
					        } | 
				
			||||
 | 
					
 | 
				
			||||
 | 
					        /// <summary>向所有客户端广播文本消息。</summary>
 | 
				
			||||
 | 
					        public static void Broadcast(string message) | 
				
			||||
 | 
					        { | 
				
			||||
 | 
					            if (message.IsEmpty()) return; | 
				
			||||
 | 
					            var endpoints = Clients; | 
				
			||||
 | 
					            foreach (var endpoint in endpoints) | 
				
			||||
 | 
					            { | 
				
			||||
 | 
					                try | 
				
			||||
 | 
					                { | 
				
			||||
 | 
					                    Send(endpoint, message); | 
				
			||||
 | 
					                } | 
				
			||||
 | 
					                catch { } | 
				
			||||
 | 
					            } | 
				
			||||
 | 
					        } | 
				
			||||
 | 
					
 | 
				
			||||
 | 
					        /// <summary>向所有客户端广播文本消息。</summary>
 | 
				
			||||
 | 
					        public static void Broadcast(object json) => Broadcast(Json.From(json)); | 
				
			||||
 | 
					
 | 
				
			||||
 | 
					    } | 
				
			||||
 | 
					
 | 
				
			||||
 | 
					} | 
				
			||||
 | 
					
 | 
				
			||||
 | 
					#endif
 | 
				
			||||
					Loading…
					
					
				
		Reference in new issue