From d23c6597d1f277a14bab5a1190fe2fc8a6cf93c5 Mon Sep 17 00:00:00 2001 From: milimoe <110188673+milimoe@users.noreply.github.com> Date: Fri, 17 Jan 2025 18:58:51 +0800 Subject: [PATCH] =?UTF-8?q?=E5=AE=8C=E5=96=84=20HTTPClient=EF=BC=9B?= =?UTF-8?q?=E6=B7=BB=E5=8A=A0=E6=97=A5=E5=BF=97=E7=BA=A7=E5=88=AB=EF=BC=9B?= =?UTF-8?q?=E6=B7=BB=E5=8A=A0=E5=8C=BF=E5=90=8D=E6=9C=8D=E5=8A=A1=E5=99=A8?= =?UTF-8?q?=E6=A8=A1=E7=BB=84=EF=BC=9B=E4=BF=AE=E5=A4=8D=E4=B8=8D=E5=90=8C?= =?UTF-8?q?=E6=97=B6=E9=97=B4=E5=A4=9A=E5=AE=A2=E6=88=B7=E7=AB=AF=E8=BF=9E?= =?UTF-8?q?=E6=8E=A5=E6=B8=B8=E6=88=8F=E6=A8=A1=E7=BB=84=E6=97=B6=E5=8F=AF?= =?UTF-8?q?=E8=83=BD=E4=BA=A7=E7=94=9F=E7=9A=84=E7=BA=BF=E7=A8=8B=E5=AE=89?= =?UTF-8?q?=E5=85=A8=E9=97=AE=E9=A2=98=20(#106)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * 完善 HTTPClient;添加日志级别;添加匿名服务器模组(此模组不强制要求登录、客户端安装) * 添加参数 * 添加 null 检查 * 修复不同时间多客户端连接游戏模组时可能产生的线程安全问题 --- Api/Transmittal/DataRequest.cs | 32 +++--- Api/Utility/General.cs | 10 +- Api/Utility/TextReader.cs | 6 +- Controller/BaseAddonController.cs | 20 +++- Controller/RunTimeController.cs | 71 +++++++----- Controller/ServerAddonController.cs | 4 +- Interface/Base/Addons/IGameModuleServer.cs | 2 +- .../Common/Addon/Example/ExampleGameModule.cs | 103 +++++++++++++----- Library/Common/Addon/GameModuleServer.cs | 97 ++++++++++++++++- Library/Common/Architecture/HeartBeat.cs | 70 +++++++----- Library/Common/Network/HTTPClient.cs | 71 ++++++++++-- Library/Common/Network/Socket.cs | 13 +++ Library/Constant/ConstantSet.cs | 18 +++ Library/Constant/TypeEnum.cs | 13 ++- Service/HTTPManager.cs | 14 +-- 15 files changed, 410 insertions(+), 134 deletions(-) diff --git a/Api/Transmittal/DataRequest.cs b/Api/Transmittal/DataRequest.cs index 90b1a5b..083eb2b 100644 --- a/Api/Transmittal/DataRequest.cs +++ b/Api/Transmittal/DataRequest.cs @@ -70,13 +70,13 @@ namespace Milimoe.FunGame.Core.Api.Transmittal /// 插件则使用 中的 创建一个新的请求 /// 此数据请求只能调用异步方法 请求数据 /// - /// + /// /// /// /// - internal DataRequest(HTTPClient WebSocket, DataRequestType RequestType, bool IsLongRunning = false, SocketRuntimeType RuntimeType = SocketRuntimeType.Client) + internal DataRequest(HTTPClient HTTPClient, DataRequestType RequestType, bool IsLongRunning = false, SocketRuntimeType RuntimeType = SocketRuntimeType.Client) { - Worker = new(WebSocket, RequestType, Guid.NewGuid(), IsLongRunning, RuntimeType); + Worker = new(HTTPClient, RequestType, Guid.NewGuid(), IsLongRunning, RuntimeType); } /// @@ -99,13 +99,13 @@ namespace Milimoe.FunGame.Core.Api.Transmittal /// 此构造方法是给 提供的 /// 此数据请求只能调用异步方法 请求数据 /// - /// + /// /// /// /// - internal DataRequest(HTTPClient WebSocket, GamingType GamingType, bool IsLongRunning = false, SocketRuntimeType RuntimeType = SocketRuntimeType.Client) + internal DataRequest(HTTPClient Client, GamingType GamingType, bool IsLongRunning = false, SocketRuntimeType RuntimeType = SocketRuntimeType.Client) { - GamingWorker = new(WebSocket, GamingType, Guid.NewGuid(), IsLongRunning, RuntimeType); + GamingWorker = new(Client, GamingType, Guid.NewGuid(), IsLongRunning, RuntimeType); } /// @@ -194,7 +194,7 @@ namespace Milimoe.FunGame.Core.Api.Transmittal public string Error => _Error; private readonly Socket? Socket = null; - private readonly HTTPClient? WebSocket = null; + private readonly HTTPClient? HTTPClient = null; private readonly DataRequestType RequestType = DataRequestType.UnKnown; private readonly Guid RequestID = Guid.Empty; private readonly bool IsLongRunning = false; @@ -212,9 +212,9 @@ namespace Milimoe.FunGame.Core.Api.Transmittal this.RuntimeType = RuntimeType; } - public SocketRequest(HTTPClient? WebSocket, DataRequestType RequestType, Guid RequestID, bool IsLongRunning = false, SocketRuntimeType RuntimeType = SocketRuntimeType.Client) : base(WebSocket) + public SocketRequest(HTTPClient? HTTPClient, DataRequestType RequestType, Guid RequestID, bool IsLongRunning = false, SocketRuntimeType RuntimeType = SocketRuntimeType.Client) : base(HTTPClient) { - this.WebSocket = WebSocket; + this.HTTPClient = HTTPClient; this.RequestType = RequestType; this.RequestID = RequestID; this.IsLongRunning = IsLongRunning; @@ -236,7 +236,7 @@ namespace Milimoe.FunGame.Core.Api.Transmittal { WaitForWorkDone(); } - else if (WebSocket != null) + else if (HTTPClient != null) { throw new AsyncSendException(); } @@ -265,7 +265,7 @@ namespace Milimoe.FunGame.Core.Api.Transmittal { await WaitForWorkDoneAsync(); } - else if (WebSocket != null && await WebSocket.Send(SocketMessageType.DataRequest, RequestType, RequestID, RequestData) == SocketResult.Success) + else if (HTTPClient != null && await HTTPClient.Send(SocketMessageType.DataRequest, RequestType, RequestID, RequestData) == SocketResult.Success) { await WaitForWorkDoneAsync(); } @@ -317,7 +317,7 @@ namespace Milimoe.FunGame.Core.Api.Transmittal public string Error => _Error; private readonly Socket? Socket = null; - private readonly HTTPClient? WebSocket = null; + private readonly HTTPClient? HTTPClient = null; private readonly GamingType GamingType = GamingType.None; private readonly Guid RequestID = Guid.Empty; private readonly bool IsLongRunning = false; @@ -335,9 +335,9 @@ namespace Milimoe.FunGame.Core.Api.Transmittal this.RuntimeType = RuntimeType; } - public GamingRequest(HTTPClient? WebSocket, GamingType GamingType, Guid RequestID, bool IsLongRunning = false, SocketRuntimeType RuntimeType = SocketRuntimeType.Client) : base(WebSocket) + public GamingRequest(HTTPClient? HTTPClient, GamingType GamingType, Guid RequestID, bool IsLongRunning = false, SocketRuntimeType RuntimeType = SocketRuntimeType.Client) : base(HTTPClient) { - this.WebSocket = WebSocket; + this.HTTPClient = HTTPClient; this.GamingType = GamingType; this.RequestID = RequestID; this.IsLongRunning = IsLongRunning; @@ -359,7 +359,7 @@ namespace Milimoe.FunGame.Core.Api.Transmittal { WaitForWorkDone(); } - else if (WebSocket != null) + else if (HTTPClient != null) { throw new AsyncSendException(); } @@ -388,7 +388,7 @@ namespace Milimoe.FunGame.Core.Api.Transmittal { await WaitForWorkDoneAsync(); } - else if (WebSocket != null && await WebSocket.Send(SocketMessageType.GamingRequest, GamingType, RequestID, RequestData) == SocketResult.Success) + else if (HTTPClient != null && await HTTPClient.Send(SocketMessageType.GamingRequest, GamingType, RequestID, RequestData) == SocketResult.Success) { await WaitForWorkDoneAsync(); } diff --git a/Api/Utility/General.cs b/Api/Utility/General.cs index 87e6ffb..bd54cc8 100644 --- a/Api/Utility/General.cs +++ b/Api/Utility/General.cs @@ -443,7 +443,7 @@ namespace Milimoe.FunGame.Core.Api.Utility byte[] key_bytes = General.DefaultEncoding.GetBytes(key); HMACSHA512 hmacsha512 = new(key_bytes); byte[] hash_bytes = hmacsha512.ComputeHash(text_bytes); - string hmac = BitConverter.ToString(hash_bytes).Replace("-", ""); + string hmac = Convert.ToHexString(hash_bytes); return hmac.ToLower(); } @@ -452,12 +452,12 @@ namespace Milimoe.FunGame.Core.Api.Utility /// /// 要计算哈希值的文件路径 /// 文件的 SHA-256 哈希值 - public static string FileSha512(string file_path) + public static string FileSha256(string file_path) { using SHA256 sha256 = SHA256.Create(); using FileStream stream = File.OpenRead(file_path); byte[] hash = sha256.ComputeHash(stream); - return BitConverter.ToString(hash).Replace("-", "").ToLowerInvariant(); + return Convert.ToHexStringLower(hash); } /// @@ -471,7 +471,7 @@ namespace Milimoe.FunGame.Core.Api.Utility byte[] plain = General.DefaultEncoding.GetBytes(plain_text); using RSACryptoServiceProvider rsa = new(); rsa.FromXmlString(plublic_key); - byte[] encrypted = rsa.Encrypt(plain, false); + byte[] encrypted = rsa.Encrypt(plain, true); return Convert.ToBase64String(encrypted); } @@ -486,7 +486,7 @@ namespace Milimoe.FunGame.Core.Api.Utility byte[] secret = Convert.FromBase64String(secret_text); using RSACryptoServiceProvider rsa = new(); rsa.FromXmlString(private_key); - byte[] decrypted = rsa.Decrypt(secret, false); + byte[] decrypted = rsa.Decrypt(secret, true); return General.DefaultEncoding.GetString(decrypted); } } diff --git a/Api/Utility/TextReader.cs b/Api/Utility/TextReader.cs index 8768534..6ed1091 100644 --- a/Api/Utility/TextReader.cs +++ b/Api/Utility/TextReader.cs @@ -76,12 +76,16 @@ namespace Milimoe.FunGame.Core.Api.Utility WriteINI("Account", "AutoKey", ""); break; case FunGameInfo.FunGame.FunGame_Server: + /** + * Console + */ + WriteINI("Console", "LogLevel", "INFO"); /** * Server */ WriteINI("Server", "Name", "FunGame Server"); WriteINI("Server", "Password", ""); - WriteINI("Server", "Describe", "Just Another FunGame Server."); + WriteINI("Server", "Description", "Just Another FunGame Server."); WriteINI("Server", "Notice", "This is the FunGame Server's Notice."); WriteINI("Server", "Key", ""); WriteINI("Server", "Status", "1"); diff --git a/Controller/BaseAddonController.cs b/Controller/BaseAddonController.cs index 4824c47..88497da 100644 --- a/Controller/BaseAddonController.cs +++ b/Controller/BaseAddonController.cs @@ -1,5 +1,6 @@ using Milimoe.FunGame.Core.Interface.Addons; using Milimoe.FunGame.Core.Library.Common.Addon; +using Milimoe.FunGame.Core.Library.Constant; namespace Milimoe.FunGame.Core.Controller { @@ -17,7 +18,7 @@ namespace Milimoe.FunGame.Core.Controller /// /// 输出系统消息 /// - protected Action MaskMethod_WriteLine { get; set; } + protected Action MaskMethod_WriteLine { get; set; } /// /// 输出错误消息 @@ -28,8 +29,10 @@ namespace Milimoe.FunGame.Core.Controller /// 输出系统消息 /// /// + /// + /// /// - public void WriteLine(string msg) => MaskMethod_WriteLine(msg); + public void WriteLine(string msg, LogLevel level = LogLevel.Info, bool useLevel = true) => MaskMethod_WriteLine(Addon.Name, msg, level, useLevel); /// /// 输出错误消息 @@ -46,7 +49,7 @@ namespace Milimoe.FunGame.Core.Controller public BaseAddonController(IAddon addon, Dictionary delegates) { Addon = (T)addon; - if (delegates.TryGetValue("WriteLine", out object? value)) MaskMethod_WriteLine = value != null ? (Action)value : new(DefaultPrint); + if (delegates.TryGetValue("WriteLine", out object? value)) MaskMethod_WriteLine = value != null ? (Action)value : new(DefaultPrint); if (delegates.TryGetValue("Error", out value)) MaskMethod_Error = value != null ? (Action)value : new(DefaultPrint); MaskMethod_WriteLine ??= new(DefaultPrint); MaskMethod_Error ??= new(DefaultPrint); @@ -55,15 +58,22 @@ namespace Milimoe.FunGame.Core.Controller /// /// 默认的输出错误消息方法 /// + /// /// + /// + /// /// - private void DefaultPrint(string msg) => Console.Write("\r" + msg + "\n\r> "); + private void DefaultPrint(string name, string msg, LogLevel level = LogLevel.Info, bool useLevel = true) + { + DateTime now = DateTime.Now; + Console.Write("\r" + now.AddMilliseconds(-now.Millisecond).ToString() + $" {CommonSet.GetLogLevelPrefix(level)}/[Addon] {Addon.Name}:\n\r> "); + } /// /// 输出错误消息 /// /// /// - private void DefaultPrint(Exception e) => DefaultPrint(e.ToString()); + private void DefaultPrint(Exception e) => DefaultPrint(Addon.Name, e.ToString(), LogLevel.Error); } } diff --git a/Controller/RunTimeController.cs b/Controller/RunTimeController.cs index 420d9c0..b70b1d6 100644 --- a/Controller/RunTimeController.cs +++ b/Controller/RunTimeController.cs @@ -21,7 +21,7 @@ namespace Milimoe.FunGame.Core.Controller /// /// 与服务器的连接套接字实例(WebSocket) /// - public HTTPClient? WebSocket => _WebSocket; + public HTTPClient? HTTPClient => _HTTPClient; /// /// 套接字是否已经连接 @@ -41,7 +41,7 @@ namespace Milimoe.FunGame.Core.Controller /// /// 用于类内赋值 /// - protected HTTPClient? _WebSocket; + protected HTTPClient? _HTTPClient; /// /// 是否正在接收服务器信息 @@ -204,6 +204,7 @@ namespace Milimoe.FunGame.Core.Controller } } }); + _Socket.ConnectionLost += Error; } } } @@ -231,12 +232,12 @@ namespace Milimoe.FunGame.Core.Controller string serverName = ""; string notice = ""; - _WebSocket?.Close(); - _WebSocket = await HTTPClient.Connect(address, ssl, port, subUrl, connectArgs.Cast().ToArray()); - if (_WebSocket.Connected) + _HTTPClient?.Close(); + _HTTPClient = await HTTPClient.Connect(address, ssl, port, subUrl, connectArgs.Cast().ToArray()); + if (_HTTPClient.Connected) { bool webSocketConnected = false; - _WebSocket.AddSocketObjectHandler(obj => + _HTTPClient.AddSocketObjectHandler(obj => { try { @@ -247,7 +248,7 @@ namespace Milimoe.FunGame.Core.Controller result = success ? ConnectResult.Success : ConnectResult.ConnectFailed; if (success) { - _WebSocket.Token = obj.GetParam(2); + _HTTPClient.Token = obj.GetParam(2); serverName = obj.GetParam(3) ?? ""; notice = obj.GetParam(4) ?? ""; } @@ -265,10 +266,11 @@ namespace Milimoe.FunGame.Core.Controller { await Task.Delay(100); } + _HTTPClient.ConnectionLost += Error; } else { - _WebSocket?.Close(); + _HTTPClient?.Close(); result = ConnectResult.ConnectFailed; } @@ -370,10 +372,10 @@ namespace Milimoe.FunGame.Core.Controller { try { - if (_WebSocket != null) + if (_HTTPClient != null) { - _WebSocket.Close(); - _WebSocket = null; + _HTTPClient.Close(); + _HTTPClient = null; } } catch (Exception e) @@ -383,12 +385,14 @@ namespace Milimoe.FunGame.Core.Controller } return true; } - + /// /// 输出消息 /// /// - public abstract void WritelnSystemInfo(string msg); + /// + /// + public abstract void WritelnSystemInfo(string msg, LogLevel level = LogLevel.Info, bool useLevel = true); /// /// 自定处理异常的方法 @@ -410,9 +414,9 @@ namespace Milimoe.FunGame.Core.Controller DataRequest request = new(_Socket, RequestType); return request; } - else if (_WebSocket != null) + else if (_HTTPClient != null) { - DataRequest request = new(_WebSocket, RequestType); + DataRequest request = new(_HTTPClient, RequestType); return request; } throw new ConnectFailedException(); @@ -431,9 +435,9 @@ namespace Milimoe.FunGame.Core.Controller DataRequest request = new(_Socket, RequestType, true); return request; } - else if (_WebSocket != null) + else if (_HTTPClient != null) { - DataRequest request = new(_WebSocket, RequestType, true); + DataRequest request = new(_HTTPClient, RequestType, true); return request; } throw new ConnectFailedException(); @@ -453,9 +457,9 @@ namespace Milimoe.FunGame.Core.Controller DataRequest request = new(_Socket, RequestType, false, SocketRuntimeType.Addon); return request; } - else if (_WebSocket != null) + else if (_HTTPClient != null) { - DataRequest request = new(_WebSocket, RequestType, false, SocketRuntimeType.Addon); + DataRequest request = new(_HTTPClient, RequestType, false, SocketRuntimeType.Addon); return request; } throw new ConnectFailedException(); @@ -475,9 +479,9 @@ namespace Milimoe.FunGame.Core.Controller DataRequest request = new(_Socket, RequestType, true, SocketRuntimeType.Addon); return request; } - else if (_WebSocket != null) + else if (_HTTPClient != null) { - DataRequest request = new(_WebSocket, RequestType, true, SocketRuntimeType.Addon); + DataRequest request = new(_HTTPClient, RequestType, true, SocketRuntimeType.Addon); return request; } throw new ConnectFailedException(); @@ -497,9 +501,9 @@ namespace Milimoe.FunGame.Core.Controller DataRequest request = new(_Socket, GamingType, false, SocketRuntimeType.Addon); return request; } - else if (_WebSocket != null) + else if (_HTTPClient != null) { - DataRequest request = new(_WebSocket, GamingType, false, SocketRuntimeType.Addon); + DataRequest request = new(_HTTPClient, GamingType, false, SocketRuntimeType.Addon); return request; } throw new ConnectFailedException(); @@ -519,9 +523,9 @@ namespace Milimoe.FunGame.Core.Controller DataRequest request = new(_Socket, GamingType, true, SocketRuntimeType.Addon); return request; } - else if (_WebSocket != null) + else if (_HTTPClient != null) { - DataRequest request = new(_WebSocket, GamingType, true, SocketRuntimeType.Addon); + DataRequest request = new(_HTTPClient, GamingType, true, SocketRuntimeType.Addon); return request; } throw new ConnectFailedException(); @@ -576,8 +580,8 @@ namespace Milimoe.FunGame.Core.Controller } catch (Exception e) { - // 报错中断服务器连接 - Error(e); + _Socket?.OnConnectionLost(e); + Close_Socket(); } return result; } @@ -641,6 +645,10 @@ namespace Milimoe.FunGame.Core.Controller case SocketMessageType.Gaming: SocketHandler_Gaming(obj); break; + + case SocketMessageType.AnonymousGameServer: + SocketHandler_AnonymousGameServer(obj); + break; case SocketMessageType.Unknown: default: @@ -735,5 +743,14 @@ namespace Milimoe.FunGame.Core.Controller { } + + /// + /// 客户端接收并处理匿名服务器的消息 + /// + /// + protected virtual void SocketHandler_AnonymousGameServer(SocketObject ServerMessage) + { + + } } } diff --git a/Controller/ServerAddonController.cs b/Controller/ServerAddonController.cs index 2f0eb62..e384673 100644 --- a/Controller/ServerAddonController.cs +++ b/Controller/ServerAddonController.cs @@ -72,7 +72,7 @@ namespace Milimoe.FunGame.Core.Controller } else { - WriteLine("已经创建过 SQLHelper 实例。"); + WriteLine("已经创建过 SQLHelper 实例。", Library.Constant.LogLevel.Warning); } } @@ -87,7 +87,7 @@ namespace Milimoe.FunGame.Core.Controller } else { - WriteLine("已经创建过 MailSender 实例。"); + WriteLine("已经创建过 MailSender 实例。", Library.Constant.LogLevel.Warning); } } diff --git a/Interface/Base/Addons/IGameModuleServer.cs b/Interface/Base/Addons/IGameModuleServer.cs index 0ee4fca..59fcea2 100644 --- a/Interface/Base/Addons/IGameModuleServer.cs +++ b/Interface/Base/Addons/IGameModuleServer.cs @@ -8,6 +8,6 @@ namespace Milimoe.FunGame.Core.Interface.Addons { public bool StartServer(string GameModule, Room Room, List Users, IServerModel RoomMasterServerModel, Dictionary ServerModels, params object[] args); - public Task> GamingMessageHandler(string username, GamingType type, Dictionary data); + public Task> GamingMessageHandler(IServerModel model, GamingType type, Dictionary data); } } diff --git a/Library/Common/Addon/Example/ExampleGameModule.cs b/Library/Common/Addon/Example/ExampleGameModule.cs index 7530d31..05b7e4d 100644 --- a/Library/Common/Addon/Example/ExampleGameModule.cs +++ b/Library/Common/Addon/Example/ExampleGameModule.cs @@ -1,3 +1,4 @@ +using System.Collections.Concurrent; using Milimoe.FunGame.Core.Api.Transmittal; using Milimoe.FunGame.Core.Api.Utility; using Milimoe.FunGame.Core.Entity; @@ -128,29 +129,31 @@ namespace Milimoe.FunGame.Core.Library.Common.Addon.Example public override GameModuleDepend GameModuleDepend => ExampleGameModuleConstant.GameModuleDepend; - protected Room Room = General.HallInstance; - protected List Users = []; - protected IServerModel? RoomMaster; - protected Dictionary All = []; + private readonly struct ModuleServerWorker(Room room, List users, IServerModel roomMaster, Dictionary serverModels) + { + public Room Room { get; } = room; + public List Users { get; } = users; + public IServerModel RoomMaster { get; } = roomMaster; + public Dictionary All { get; } = serverModels; + public List ConnectedUser { get; } = []; + public Dictionary> UserData { get; } = []; + } + + private ConcurrentDictionary Workers { get; } = []; public override bool StartServer(string GameModule, Room Room, List Users, IServerModel RoomMasterServerModel, Dictionary ServerModels, params object[] Args) { - // 将参数转为本地属性 - this.Room = Room; - this.Users = Users; - RoomMaster = RoomMasterServerModel; - All = ServerModels; + // 因为模组是单例的,需要为这个房间创建一个工作类接收参数,不能直接用本地变量处理 + ModuleServerWorker worker = new(Room, Users, RoomMasterServerModel, ServerModels); + Workers[Room.Roomid] = worker; // 创建一个线程执行Test() - TaskUtility.NewTask(Test).OnError(Controller.Error); + TaskUtility.NewTask(async () => await Test(worker)).OnError(Controller.Error); return true; } - private readonly List ConnectedUser = []; - private readonly Dictionary> UserData = []; - - private async Task Test() + private async Task Test(ModuleServerWorker worker) { - Controller.WriteLine("欢迎各位玩家进入房间 " + Room.Roomid + " 。"); + Controller.WriteLine("欢迎各位玩家进入房间 " + worker.Room.Roomid + " 。"); // 通常,我们可以对客户端的连接状态进行确认,此方法展示如何确认客户端的连接 // 有两种确认的方式,1是服务器主动确认,2是客户端发起确认 @@ -165,33 +168,36 @@ namespace Milimoe.FunGame.Core.Library.Common.Addon.Example data.Add("connect_token", token); // 我们保存到字典UserData中,这样可以方便跨方法检查变量 - foreach (string username in Users.Select(u => u.Username).Distinct()) + foreach (string username in worker.Users.Select(u => u.Username).Distinct()) { - if (UserData.TryGetValue(username, out Dictionary? value)) + if (worker.UserData.TryGetValue(username, out Dictionary? value)) { value.Add("connect_token", token); } else { - UserData.Add(username, []); - UserData[username].Add("connect_token", token); + worker.UserData.Add(username, []); + worker.UserData[username].Add("connect_token", token); } } - await SendGamingMessage(All.Values, GamingType.UpdateInfo, data); + await SendGamingMessage(worker.All.Values, GamingType.UpdateInfo, data); // 新建一个线程等待所有玩家确认 while (true) { - if (ConnectedUser.Count == Users.Count) break; + if (worker.ConnectedUser.Count == worker.Users.Count) break; // 每200ms确认一次,不需要太频繁 await Task.Delay(200); } Controller.WriteLine("所有玩家都已经连接。"); } - public override async Task> GamingMessageHandler(string username, GamingType type, Dictionary data) + public override async Task> GamingMessageHandler(IServerModel model, GamingType type, Dictionary data) { Dictionary result = []; + // 获取model所在的房间工作类 + ModuleServerWorker worker = Workers[model.InRoom.Roomid]; + string username = model.User.Username; switch (type) { @@ -200,12 +206,12 @@ namespace Milimoe.FunGame.Core.Library.Common.Addon.Example // 如果需要处理客户端传递的参数:获取与客户端约定好的参数key对应的值 string un = NetworkUtility.JsonDeserializeFromDictionary(data, "username") ?? ""; Guid token = NetworkUtility.JsonDeserializeFromDictionary(data, "connect_token"); - if (un == username && UserData.TryGetValue(username, out Dictionary? value) && value != null && (value["connect_token"]?.Equals(token) ?? false)) + if (un == username && worker.UserData.TryGetValue(username, out Dictionary? value) && value != null && (value["connect_token"]?.Equals(token) ?? false)) { - ConnectedUser.Add(Users.Where(u => u.Username == username).First()); + worker.ConnectedUser.Add(worker.Users.Where(u => u.Username == username).First()); Controller.WriteLine(username + " 已经连接。"); } - else Controller.WriteLine(username + " 确认连接失败!"); + else Controller.WriteLine(username + " 确认连接失败!", LogLevel.Warning); break; default: await Task.Delay(1); @@ -214,6 +220,53 @@ namespace Milimoe.FunGame.Core.Library.Common.Addon.Example return result; } + + protected HashSet _clientModels = []; + + /// + /// 匿名服务器示例 + /// + /// + /// + /// + public override bool StartAnonymousServer(IServerModel model, Dictionary data) + { + // 可以做验证处理 + string access_token = NetworkUtility.JsonDeserializeFromDictionary(data, "access_token") ?? ""; + if (access_token == "approval_access_token") + { + // 接收连接匿名服务器的客户端 + _clientModels.Add(model); + return true; + } + return false; + } + + public override void CloseAnonymousServer(IServerModel model) + { + // 移除客户端 + _clientModels.Remove(model); + } + + /// + /// 接收并处理匿名服务器消息 + /// + /// + /// + /// + public override async Task> AnonymousGameServerHandler(IServerModel model, Dictionary data) + { + Dictionary result = []; + + // 根据服务器和客户端的数据传输约定,自行处理 data,并返回。 + if (data.Count > 0) + { + await Task.Delay(1); + } + result.Add("msg", "匿名服务器已经收到消息了"); + + return result; + } } /// diff --git a/Library/Common/Addon/GameModuleServer.cs b/Library/Common/Addon/GameModuleServer.cs index c96541f..287d252 100644 --- a/Library/Common/Addon/GameModuleServer.cs +++ b/Library/Common/Addon/GameModuleServer.cs @@ -39,6 +39,11 @@ namespace Milimoe.FunGame.Core.Library.Common.Addon /// public abstract GameModuleDepend GameModuleDepend { get; } + /// + /// 是否是匿名服务器 + /// + public virtual bool IsAnonymous { get; set; } = false; + /// /// 包含了一些常用方法的控制器 /// @@ -77,11 +82,45 @@ namespace Milimoe.FunGame.Core.Library.Common.Addon /// /// 接收并处理GamingMessage /// - /// 发送此消息的账号 + /// 发送此消息的客户端 /// 消息类型 /// 消息参数 /// 底层会将字典中的数据发送给客户端 - public abstract Task> GamingMessageHandler(string username, GamingType type, Dictionary data); + public abstract Task> GamingMessageHandler(IServerModel model, GamingType type, Dictionary data); + + /// + /// 启动匿名服务器监听 + /// + /// + /// + /// + public virtual bool StartAnonymousServer(IServerModel model, Dictionary data) + { + return true; + } + + /// + /// 结束匿名服务器监听 + /// + /// + /// + public virtual void CloseAnonymousServer(IServerModel model) + { + + } + + /// + /// 接收并处理匿名服务器监听消息 + /// 此方法为可选实现,可以帮助 RESTful API 处理不需要验证的 WebSocket 请求 + /// + /// 发送此消息的客户端 + /// 消息参数 + /// 底层会将字典中的数据发送给客户端 + public virtual async Task> AnonymousGameServerHandler(IServerModel model, Dictionary data) + { + await Task.Delay(1); + return []; + } /// /// 加载标记 @@ -129,13 +168,24 @@ namespace Milimoe.FunGame.Core.Library.Common.Addon /// /// /// - protected virtual async Task SendGamingMessage(IEnumerable clients, GamingType type, Dictionary data) + /// 发送失败的客户端 + protected virtual async Task> SendGamingMessage(IEnumerable clients, GamingType type, Dictionary data) { // 发送局内消息 + List failedModels = []; foreach (IServerModel s in clients) { - await s.Send(SocketMessageType.Gaming, type, data); + try + { + await s.Send(SocketMessageType.Gaming, type, data); + } + catch (System.Exception e) + { + Controller.Error(e); + failedModels.Add(s); + } } + return failedModels; } /// @@ -144,13 +194,48 @@ namespace Milimoe.FunGame.Core.Library.Common.Addon /// /// /// - protected virtual async Task Send(IEnumerable clients, SocketMessageType type, params object[] args) + /// 发送失败的客户端 + protected virtual async Task> Send(IEnumerable clients, SocketMessageType type, params object[] args) { // 发送消息 + List failedModels = []; foreach (IServerModel s in clients) { - await s.Send(type, args); + try + { + await s.Send(type, args); + } + catch (System.Exception e) + { + Controller.Error(e); + failedModels.Add(s); + } } + return failedModels; + } + + /// + /// 给客户端发送匿名服务器消息 + /// + /// + /// + /// 发送失败的客户端 + protected virtual async Task> SendAnonymousGameServerMessage(IEnumerable clients, Dictionary data) + { + List failedModels = []; + foreach (IServerModel s in clients) + { + try + { + await s.Send(SocketMessageType.AnonymousGameServer, data); + } + catch (System.Exception e) + { + Controller.Error(e); + failedModels.Add(s); + } + } + return failedModels; } } } diff --git a/Library/Common/Architecture/HeartBeat.cs b/Library/Common/Architecture/HeartBeat.cs index e3f452c..0c13af4 100644 --- a/Library/Common/Architecture/HeartBeat.cs +++ b/Library/Common/Architecture/HeartBeat.cs @@ -52,39 +52,55 @@ namespace Milimoe.FunGame.Core.Library.Common.Architecture private async Task SendHeartBeat() { - await Task.Delay(100); - if (_Socket != null) + try { - while (_Socket.Connected) + await Task.Delay(100); + if (_Socket != null) { - if (!SendingHeartBeat) _SendingHeartBeat = true; - // 发送心跳包 - _LastHeartbeatReceived = false; - if (_Socket.Send(SocketMessageType.HeartBeat) == SocketResult.Success) + while (_Socket.Connected) { - await Task.Delay(4 * 1000); - if (!_LastHeartbeatReceived) AddHeartBeatFaileds(); + if (!SendingHeartBeat) _SendingHeartBeat = true; + // 发送心跳包 + _LastHeartbeatReceived = false; + if (_Socket.Send(SocketMessageType.HeartBeat) == SocketResult.Success) + { + await Task.Delay(4 * 1000); + if (!_LastHeartbeatReceived) AddHeartBeatFaileds(); + } + else AddHeartBeatFaileds(); + await Task.Delay(20 * 1000); } - else AddHeartBeatFaileds(); - await Task.Delay(20 * 1000); + } + else if (_HTTPClient != null) + { + while (_HTTPClient.WebSocket?.State == System.Net.WebSockets.WebSocketState.Open) + { + if (!SendingHeartBeat) _SendingHeartBeat = true; + // 发送心跳包 + if (await _HTTPClient.Send(SocketMessageType.HeartBeat) == SocketResult.Success) + { + await Task.Delay(4 * 1000); + AddHeartBeatFaileds(); + } + else AddHeartBeatFaileds(); + await Task.Delay(20 * 1000); + } + } + _SendingHeartBeat = false; + } + catch (System.Exception e) + { + if (_Socket != null) + { + _Socket.OnConnectionLost(e); + _Socket.Close(); + } + if (_HTTPClient != null) + { + _HTTPClient.OnConnectionLost(e); + _HTTPClient.Close(); } } - else if (_HTTPClient != null) - { - while (_HTTPClient.Instance?.State == System.Net.WebSockets.WebSocketState.Open) - { - if (!SendingHeartBeat) _SendingHeartBeat = true; - // 发送心跳包 - if (await _HTTPClient.Send(SocketMessageType.HeartBeat) == SocketResult.Success) - { - await Task.Delay(4 * 1000); - AddHeartBeatFaileds(); - } - else AddHeartBeatFaileds(); - await Task.Delay(20 * 1000); - } - } - _SendingHeartBeat = false; } private void AddHeartBeatFaileds() diff --git a/Library/Common/Network/HTTPClient.cs b/Library/Common/Network/HTTPClient.cs index 9cc9511..0493a65 100644 --- a/Library/Common/Network/HTTPClient.cs +++ b/Library/Common/Network/HTTPClient.cs @@ -1,4 +1,5 @@ using System.Net.WebSockets; +using Milimoe.FunGame.Core.Api.Utility; using Milimoe.FunGame.Core.Interface.HTTP; using Milimoe.FunGame.Core.Library.Common.Architecture; using Milimoe.FunGame.Core.Library.Constant; @@ -9,23 +10,28 @@ namespace Milimoe.FunGame.Core.Library.Common.Network { public class HTTPClient : IHTTPClient { - public System.Net.WebSockets.ClientWebSocket? Instance { get; } = null; + public HttpClient Instance { get; } + public ClientWebSocket? WebSocket { get; } = null; public SocketRuntimeType Runtime => SocketRuntimeType.Client; public Guid Token { get; set; } = Guid.Empty; public string ServerAddress { get; } = ""; public int ServerPort { get; } = 0; public string ServerName { get; } = ""; public string ServerNotice { get; } = ""; - public bool Connected => Instance != null && Instance.State == WebSocketState.Open; + public bool Connected => WebSocket != null && WebSocket.State == WebSocketState.Open; public bool Receiving => _receiving; private HeartBeat HeartBeat { get; } + public event Action? ConnectionLost; + public event Action? Closed; + private bool _receiving = false; private readonly HashSet> _boundEvents = []; - private HTTPClient(ClientWebSocket instance, string serverAddress, int serverPort, params object[] args) + private HTTPClient(ClientWebSocket websocket, string serverAddress, int serverPort, params object[] args) { - Instance = instance; + Instance = new(); + WebSocket = websocket; ServerAddress = serverAddress; ServerPort = serverPort; HeartBeat = new(this); @@ -55,8 +61,9 @@ namespace Milimoe.FunGame.Core.Library.Common.Network } catch (System.Exception e) { + OnConnectionLost(e); Close(); - Api.Utility.TXTHelper.AppendErrorLog(e.GetErrorInfo()); + TXTHelper.AppendErrorLog(e.GetErrorInfo()); throw new SocketWrongInfoException(); } } @@ -64,13 +71,46 @@ namespace Milimoe.FunGame.Core.Library.Common.Network public async Task Send(SocketMessageType type, params object[] objs) { - if (Instance != null) + if (WebSocket != null) { - return await HTTPManager.Send(Instance, new(type, Token, objs)); + return await HTTPManager.Send(WebSocket, new(type, Token, objs)); } return SocketResult.NotSent; } + /// + /// 发送 GET 请求 + /// + /// + /// + public async Task HttpGet(string url) + { + HttpResponseMessage response = await Instance.GetAsync(url); + response.EnsureSuccessStatusCode(); + + string content = await response.Content.ReadAsStringAsync(); + T? result = NetworkUtility.JsonDeserialize(content); + return result; + } + + /// + /// 发送 POST 请求 + /// + /// + /// + /// + /// + public async Task HttpPost(string url, string json) + { + HttpContent content = new StringContent(json, General.DefaultEncoding, "application/json"); + HttpResponseMessage response = await Instance.PostAsync(url, content); + response.EnsureSuccessStatusCode(); + + string read = await response.Content.ReadAsStringAsync(); + T? result = NetworkUtility.JsonDeserialize(read); + return result; + } + public void AddSocketObjectHandler(Action method) { if (_boundEvents.Add(method)) @@ -85,23 +125,32 @@ namespace Milimoe.FunGame.Core.Library.Common.Network SocketManager.SocketReceive -= new SocketManager.SocketReceiveHandler(method); } + public void OnConnectionLost(System.Exception e) + { + ConnectionLost?.Invoke(e); + } + public void Close() { + Instance.Dispose(); _receiving = false; HeartBeat.StopSendingHeartBeat(); - Instance?.CloseOutputAsync(WebSocketCloseStatus.NormalClosure, "", CancellationToken.None); - Instance?.Dispose(); + WebSocket?.CloseOutputAsync(WebSocketCloseStatus.NormalClosure, "", CancellationToken.None); + WebSocket?.Dispose(); foreach (Action method in _boundEvents.ToList()) { RemoveSocketObjectHandler(method); } + Closed?.Invoke(); + ConnectionLost = null; + Closed = null; } private async Task StartListening(params object[] args) { - if (Instance != null && Instance.State == WebSocketState.Open) + if (WebSocket != null && WebSocket.State == WebSocketState.Open) { - if (await HTTPManager.Send(Instance, new(SocketMessageType.Connect, Guid.Empty, args)) == SocketResult.Success && await HTTPManager.ReceiveMessage(this)) + if (await HTTPManager.Send(WebSocket, new(SocketMessageType.Connect, Guid.Empty, args)) == SocketResult.Success && await HTTPManager.ReceiveMessage(this)) { _receiving = true; await Receive(); diff --git a/Library/Common/Network/Socket.cs b/Library/Common/Network/Socket.cs index 6bf1a50..1c4aced 100644 --- a/Library/Common/Network/Socket.cs +++ b/Library/Common/Network/Socket.cs @@ -19,6 +19,9 @@ namespace Milimoe.FunGame.Core.Library.Common.Network public bool Receiving => _receiving; private HeartBeat HeartBeat { get; } + public event Action? ConnectionLost; + public event Action? Closed; + private Task? _receivingTask; private bool _receiving = false; private readonly HashSet> _boundEvents = []; @@ -61,6 +64,8 @@ namespace Milimoe.FunGame.Core.Library.Common.Network } catch (System.Exception e) { + OnConnectionLost(e); + Close(); Api.Utility.TXTHelper.AppendErrorLog(e.GetErrorInfo()); throw new SocketWrongInfoException(); } @@ -80,6 +85,11 @@ namespace Milimoe.FunGame.Core.Library.Common.Network SocketManager.SocketReceive -= new SocketManager.SocketReceiveHandler(method); } + public void OnConnectionLost(System.Exception e) + { + ConnectionLost?.Invoke(e); + } + public void Close() { HeartBeat.StopSendingHeartBeat(); @@ -89,6 +99,9 @@ namespace Milimoe.FunGame.Core.Library.Common.Network { RemoveSocketObjectHandler(method); } + Closed?.Invoke(); + ConnectionLost = null; + Closed = null; } public void StartReceiving(Task t) diff --git a/Library/Constant/ConstantSet.cs b/Library/Constant/ConstantSet.cs index 4590273..9aa9da5 100644 --- a/Library/Constant/ConstantSet.cs +++ b/Library/Constant/ConstantSet.cs @@ -3,6 +3,23 @@ */ namespace Milimoe.FunGame.Core.Library.Constant { + public class CommonSet + { + public static string GetLogLevelPrefix(LogLevel level) + { + return level switch + { + LogLevel.Trace => "T/", + LogLevel.Debug => "D/", + LogLevel.Info => "I/", + LogLevel.Warning => "W/", + LogLevel.Error => "E/", + LogLevel.Critical => "C/", + _ => "I/" + }; + } + } + /// /// 配合 使用,也别忘了修改 /// @@ -49,6 +66,7 @@ namespace Milimoe.FunGame.Core.Library.Constant public const string StartGame = "StartGame"; public const string EndGame = "EndGame"; public const string Gaming = "Gaming"; + public const string AnonymousGameServer = "AnonymousGameServer"; /// /// 将通信类型的枚举转换为字符串 diff --git a/Library/Constant/TypeEnum.cs b/Library/Constant/TypeEnum.cs index 403fe17..3745131 100644 --- a/Library/Constant/TypeEnum.cs +++ b/Library/Constant/TypeEnum.cs @@ -77,7 +77,8 @@ namespace Milimoe.FunGame.Core.Library.Constant MatchRoom, StartGame, EndGame, - Gaming + Gaming, + AnonymousGameServer } /// @@ -809,4 +810,14 @@ namespace Milimoe.FunGame.Core.Library.Constant Immediate, Progressive } + + public enum LogLevel + { + Trace, + Debug, + Info, + Warning, + Error, + Critical + } } diff --git a/Service/HTTPManager.cs b/Service/HTTPManager.cs index 02bb428..13995ea 100644 --- a/Service/HTTPManager.cs +++ b/Service/HTTPManager.cs @@ -38,9 +38,9 @@ namespace Milimoe.FunGame.Core.Service /// /// /// - internal static async Task Connect(Uri uri) + internal static async Task Connect(Uri uri) { - System.Net.WebSockets.ClientWebSocket socket = new(); + ClientWebSocket socket = new(); await socket.ConnectAsync(uri, CancellationToken.None); if (socket.State == WebSocketState.Open) { @@ -55,7 +55,7 @@ namespace Milimoe.FunGame.Core.Service /// /// /// - internal static async Task Send(System.Net.WebSockets.ClientWebSocket socket, SocketObject obj) + internal static async Task Send(ClientWebSocket socket, SocketObject obj) { if (socket != null) { @@ -176,12 +176,12 @@ namespace Milimoe.FunGame.Core.Service /// internal static async Task ReceiveMessage(HTTPClient client) { - if (client.Instance is null) return false; + if (client.WebSocket is null) return false; byte[] buffer = new byte[General.SocketByteSize]; - WebSocketReceiveResult result = await client.Instance.ReceiveAsync(new ArraySegment(buffer), CancellationToken.None); + WebSocketReceiveResult result = await client.WebSocket.ReceiveAsync(new ArraySegment(buffer), CancellationToken.None); string msg = General.DefaultEncoding.GetString(buffer).Replace("\0", "").Trim(); - SocketObject[] objs = await GetSocketObjects(client.Instance, result, msg); + SocketObject[] objs = await GetSocketObjects(client.WebSocket, result, msg); foreach (SocketObject obj in objs) { @@ -192,7 +192,7 @@ namespace Milimoe.FunGame.Core.Service } else if (obj.SocketType == SocketMessageType.Disconnect) { - await client.Instance.CloseAsync(result.CloseStatus ?? WebSocketCloseStatus.NormalClosure, result.CloseStatusDescription, CancellationToken.None); + await client.WebSocket.CloseAsync(result.CloseStatus ?? WebSocketCloseStatus.NormalClosure, result.CloseStatusDescription, CancellationToken.None); return true; } }