You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

442 lines
14 KiB

4 years ago
4 years ago
4 years ago
4 years ago
4 years ago
  1. #if !NET20
  2. using Apewer;
  3. using Apewer.Network;
  4. using System;
  5. using System.Collections.Generic;
  6. namespace Apewer.WebSocket
  7. {
  8. /// <summary></summary>
  9. public sealed class ChatServer
  10. {
  11. #region public
  12. /// <summary></summary>
  13. public Action<string> ErrorAction { get; set; }
  14. /// <summary></summary>
  15. public Action<Exception> ExceptedAction { get; set; }
  16. /// <summary></summary>
  17. public Action DisposedAction { get; set; }
  18. /// <summary></summary>
  19. public bool WebSocketListening
  20. {
  21. get { return (_websocket != null && _websocket.Running); }
  22. }
  23. /// <summary>WebSocket 监听的地址,默认为 0.0.0.0。</summary>
  24. public string WebSocketAddress
  25. {
  26. get { return _websocketaddress; }
  27. set { if (!WebSocketListening) _websocketaddress = value; }
  28. }
  29. /// <summary>WebSocket 监听的端口,默认为 8000。</summary>
  30. public int WebSocketPort
  31. {
  32. get { return _websocketport; }
  33. set { if (!WebSocketListening) _websocketport = NumberUtility.Restrict(value, 0, ushort.MaxValue); }
  34. }
  35. /// <summary></summary>
  36. public bool CommandListening
  37. {
  38. get { return false; }
  39. }
  40. /// <summary>命令服务监听的地址,默认为 0.0.0.0。</summary>
  41. public string CommandAddress
  42. {
  43. get { return _commandaddress; }
  44. set { if (!CommandListening) _commandaddress = value; }
  45. }
  46. /// <summary>命令服务监听的端口,默认为 8000。</summary>
  47. public int CommandPort
  48. {
  49. get { return _commandport; }
  50. set { if (!CommandListening) _commandport = NumberUtility.Restrict(value, 0, ushort.MaxValue); }
  51. }
  52. /// <summary></summary>
  53. public void Start() { PrivateStart(); }
  54. /// <summary></summary>
  55. public void Dispose()
  56. {
  57. if (_websocket != null)
  58. {
  59. lock (_websocket)
  60. {
  61. _websocket.Close();
  62. _websocket.Dispose();
  63. }
  64. _websocket = null;
  65. }
  66. DisposedAction?.Invoke();
  67. }
  68. #endregion
  69. #region fields
  70. private string _websocketaddress = "0.0.0.0";
  71. private int _websocketport = 8000;
  72. private string _commandaddress = "0.0.0.0";
  73. private int _commandport = 8000;
  74. private Dictionary<int, List<Connection>> _rooms = new Dictionary<int, List<Connection>>();
  75. private List<Connection> _lobby = new List<Connection>();
  76. private GenericServer _websocket = null;
  77. private UdpServer _command = null;
  78. #endregion
  79. #region private
  80. void RaiseError(params string[] text)
  81. {
  82. var merged = TextUtility.Merge(text);
  83. if (!TextUtility.IsBlank(merged)) ErrorAction?.Invoke(merged);
  84. }
  85. void RaiseExcepted(Exception exception)
  86. {
  87. if (exception != null) ExceptedAction?.Invoke(exception);
  88. }
  89. void RaiseConsole(params string[] text)
  90. {
  91. var merged = TextUtility.Merge(text);
  92. if (!TextUtility.IsBlank(merged)) Logger.Web.Text(this, merged);
  93. }
  94. private void PrivateStart()
  95. {
  96. if (_websocket != null) return;
  97. _websocket = new GenericServer();
  98. lock (_websocket)
  99. {
  100. var ws = _websocket.Start(_websocketport, _websocketaddress);
  101. if (ws)
  102. {
  103. _websocket.OnMessage += WebSocketReceived;
  104. _websocket.OnOpen += WebsocketOnOpen;
  105. _websocket.OnClose += WebsocketOnClose;
  106. RaiseConsole("WebSocket 服务已启动。");
  107. }
  108. else
  109. {
  110. _websocket = null;
  111. RaiseError("WebSocket 服务启动失败。");
  112. return;
  113. }
  114. _command = new UdpServer();
  115. _command.Port = _commandport;
  116. _command.Excepted += (s, ex) => RaiseExcepted(ex);
  117. _command.Quitted += (s) => RaiseConsole("命令服务已退出。");
  118. _command.Started += (s) => RaiseConsole("命令服务已启动。");
  119. _command.Received += (s, e) => CommandReceived(s, e.IP, e.Port, e.Bytes);
  120. _command.Start();
  121. }
  122. }
  123. void WebsocketOnClose(Connection socket)
  124. {
  125. var room = GetRoomId(socket.QueryPath);
  126. var connections = LeaveRoom(socket, room);
  127. Send(connections, PackLeft(socket, room).ToString());
  128. }
  129. void WebsocketOnOpen(Connection socket)
  130. {
  131. var room = GetRoomId(socket.QueryPath);
  132. var connections = JoinRoom(socket, room);
  133. Send(connections, PackJoined(socket, room).ToString());
  134. }
  135. void WebSocketReceived(Connection socket, string content)
  136. {
  137. if (socket == null || string.IsNullOrEmpty(content))
  138. {
  139. RaiseConsole("WebSocket 服务接收到空消息。");
  140. return;
  141. }
  142. var input = Json.From(content);
  143. if (input == null)
  144. {
  145. RaiseConsole("WebSocket 服务接收到无效消息。");
  146. return;
  147. }
  148. var room = GetRoomId(socket.QueryPath);
  149. switch (input["type"])
  150. {
  151. case "command":
  152. {
  153. switch (input["command"])
  154. {
  155. case "exit":
  156. Dispose();
  157. break;
  158. }
  159. }
  160. break;
  161. case "broadcast":
  162. {
  163. var json = PackMessage(socket, 0, input["text"], input.GetProperty("addition"));
  164. if (_websocket != null)
  165. {
  166. lock (_websocket)
  167. {
  168. _websocket.Send(json.ToString());
  169. }
  170. }
  171. }
  172. break;
  173. case "message":
  174. {
  175. var connections = JoinRoom(socket, room);
  176. var json = PackMessage(socket, room, input["text"], input.GetProperty("addition"));
  177. Send(connections, json.ToString());
  178. }
  179. break;
  180. }
  181. }
  182. void Send(List<Connection> connections, string message)
  183. {
  184. if (connections == null) return;
  185. if (string.IsNullOrEmpty(message)) return;
  186. foreach (var connection in connections)
  187. {
  188. if (connection == null) continue;
  189. try
  190. {
  191. connection.Send(message);
  192. }
  193. catch (Exception exception)
  194. {
  195. if (ExceptedAction == null)
  196. {
  197. var a = connection.Address;
  198. var p = connection.Port.ToString();
  199. var e = exception.ToString();
  200. Logger.Web.Error(this, TextUtility.Merge($"对 {a}:{p} 发送消息失败。"), exception.Message);
  201. }
  202. else
  203. {
  204. ExceptedAction(exception);
  205. }
  206. }
  207. }
  208. }
  209. int GetRoomId(string path)
  210. {
  211. if (TextUtility.IsBlank(path)) return 0;
  212. var array = path.Split('/');
  213. if (array.Length > 1)
  214. {
  215. var caption = array[1];
  216. var room = NumberUtility.Int32(caption);
  217. var connections = new List<Connection>();
  218. if (room == 0 && !TextUtility.IsBlank(caption)) room = caption.GetHashCode();
  219. return room;
  220. }
  221. return 0;
  222. }
  223. List<Connection> JoinRoom(Connection socket, int room)
  224. {
  225. var connections = new List<Connection>();
  226. if (room == 0)
  227. {
  228. lock (_lobby)
  229. {
  230. if (!_lobby.Contains(socket)) _lobby.Add(socket);
  231. connections.AddRange(_lobby);
  232. }
  233. }
  234. else
  235. {
  236. lock (_rooms)
  237. {
  238. if (_rooms.ContainsKey(room))
  239. {
  240. var value = _rooms[room];
  241. lock (value)
  242. {
  243. if (!value.Contains(socket)) value.Add(socket);
  244. connections.AddRange(value);
  245. }
  246. }
  247. else
  248. {
  249. var value = new List<Connection>();
  250. value.Add(socket);
  251. connections.AddRange(value);
  252. _rooms.Add(room, value);
  253. }
  254. }
  255. }
  256. return connections;
  257. }
  258. List<Connection> LeaveRoom(Connection socket, int room)
  259. {
  260. var connections = new List<Connection>();
  261. if (room == 0)
  262. {
  263. lock (_lobby)
  264. {
  265. if (_lobby.Contains(socket)) _lobby.Remove(socket);
  266. connections.AddRange(_lobby);
  267. }
  268. }
  269. else
  270. {
  271. lock (_rooms)
  272. {
  273. if (_rooms.ContainsKey(room))
  274. {
  275. var value = _rooms[room];
  276. var empty = false;
  277. lock (value)
  278. {
  279. if (value.Contains(socket)) value.Remove(socket);
  280. empty = value.Count == 0;
  281. if (!empty) connections.AddRange(value);
  282. if (empty) _rooms.Remove(room);
  283. }
  284. }
  285. }
  286. }
  287. return connections;
  288. }
  289. Json PackJoined(Connection socket, int room = 0)
  290. {
  291. var json = Json.NewObject();
  292. json.SetProperty("utcstamp", ClockUtility.UtcStamp);
  293. json.SetProperty("nowstamp", ClockUtility.NowStamp);
  294. json.SetProperty("nowlucid", ClockUtility.LucidNow);
  295. json.SetProperty("address", socket.Address);
  296. json.SetProperty("port", socket.Port);
  297. json.SetProperty("room", room);
  298. json.SetProperty("type", "joined");
  299. return json;
  300. }
  301. Json PackLeft(Connection socket, int room = 0)
  302. {
  303. var json = Json.NewObject();
  304. json.SetProperty("utcstamp", ClockUtility.UtcStamp);
  305. json.SetProperty("nowstamp", ClockUtility.NowStamp);
  306. json.SetProperty("nowlucid", ClockUtility.LucidNow);
  307. json.SetProperty("address", socket.Address);
  308. json.SetProperty("port", socket.Port);
  309. json.SetProperty("room", room);
  310. json.SetProperty("type", "left");
  311. return json;
  312. }
  313. Json PackMessage(Connection socket, int room = 0, string text = null, Json addition = null)
  314. {
  315. var json = Json.NewObject();
  316. json.SetProperty("utcstamp", ClockUtility.UtcStamp);
  317. json.SetProperty("nowstamp", ClockUtility.NowStamp);
  318. json.SetProperty("nowlucid", ClockUtility.LucidNow);
  319. json.SetProperty("address", socket.Address);
  320. json.SetProperty("port", socket.Port);
  321. json.SetProperty("room", room);
  322. json.SetProperty("type", "message");
  323. json.SetProperty("text", text);
  324. json.SetProperty("addition", addition);
  325. return json;
  326. }
  327. void CommandReceived(object sender, string ip, int port, byte[] bytes)
  328. {
  329. }
  330. #endregion
  331. #region static
  332. /// <summary></summary>
  333. public static LogLevel LogLevel
  334. {
  335. get { return GenericServer.LogLevel; }
  336. set { GenericServer.LogLevel = value; }
  337. }
  338. #endregion
  339. #region program
  340. /// <summary>运行示例实例。</summary>
  341. public static void RunDemo(int chatTcpPort = 8000, int commandUdpPort = 8000)
  342. {
  343. var cs = new ChatServer();
  344. cs.WebSocketPort = chatTcpPort;
  345. cs.CommandPort = commandUdpPort;
  346. cs.Start();
  347. ChatServer.LogLevel = LogLevel.Debug;
  348. while (true)
  349. {
  350. var input = Console.ReadLine();
  351. if (input == "exit") break;
  352. }
  353. cs.Dispose();
  354. }
  355. static void RunLite()
  356. {
  357. var ws = new GenericServer();
  358. ws.OnOpen += (s) =>
  359. {
  360. ws.Send(s.Address, ":", s.Port.ToString(), "\n已连接\nHost: ", s.Host, "\nOrigin: ", s.Origin + "\nPath: ", s.QueryPath);
  361. };
  362. ws.OnClose += (s) =>
  363. {
  364. ws.Send(s.Address, ":", s.Port.ToString(), "\n已断开。");
  365. };
  366. ws.OnMessage += (s, m) =>
  367. {
  368. s.Send(s.Address, ":", s.Port.ToString(), "\n", m);
  369. };
  370. ws.Start();
  371. while (true)
  372. {
  373. var input = Console.ReadLine();
  374. if (input == "exit") break;
  375. ws.Send(ws.Address, ":", ws.Port.ToString(), "\n", input);
  376. }
  377. ws.Close();
  378. ws.Dispose();
  379. }
  380. #endregion
  381. }
  382. }
  383. #endif