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