完善 HTTPClient;添加日志级别;添加匿名服务器模组;修复不同时间多客户端连接游戏模组时可能产生的线程安全问题 (#106)

* 完善 HTTPClient;添加日志级别;添加匿名服务器模组(此模组不强制要求登录、客户端安装)

* 添加参数

* 添加 null 检查

* 修复不同时间多客户端连接游戏模组时可能产生的线程安全问题
This commit is contained in:
milimoe 2025-01-17 18:58:51 +08:00 committed by GitHub
parent ce0c933b35
commit d23c6597d1
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
15 changed files with 410 additions and 134 deletions

View File

@ -70,13 +70,13 @@ namespace Milimoe.FunGame.Core.Api.Transmittal
/// 插件则使用 <see cref="RunTimeController"/> 中的 <see cref="RunTimeController.NewDataRequestForAddon(DataRequestType)"/> 创建一个新的请求<para/>
/// 此数据请求只能调用异步方法 <see cref="SendRequestAsync"/> 请求数据
/// </summary>
/// <param name="WebSocket"></param>
/// <param name="HTTPClient"></param>
/// <param name="RequestType"></param>
/// <param name="IsLongRunning"></param>
/// <param name="RuntimeType"></param>
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);
}
/// <summary>
@ -99,13 +99,13 @@ namespace Milimoe.FunGame.Core.Api.Transmittal
/// 此构造方法是给 <see cref="Library.Common.Addon.GameModule"/> 提供的<para/>
/// 此数据请求只能调用异步方法 <see cref="SendRequestAsync"/> 请求数据
/// </summary>
/// <param name="WebSocket"></param>
/// <param name="Client"></param>
/// <param name="GamingType"></param>
/// <param name="IsLongRunning"></param>
/// <param name="RuntimeType"></param>
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);
}
/// <summary>
@ -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();
}

View File

@ -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
/// </summary>
/// <param name="file_path">要计算哈希值的文件路径</param>
/// <returns>文件的 SHA-256 哈希值</returns>
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);
}
/// <summary>
@ -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);
}
}

View File

@ -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");

View File

@ -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
/// <summary>
/// 输出系统消息
/// </summary>
protected Action<string> MaskMethod_WriteLine { get; set; }
protected Action<string, string, LogLevel, bool> MaskMethod_WriteLine { get; set; }
/// <summary>
/// 输出错误消息
@ -28,8 +29,10 @@ namespace Milimoe.FunGame.Core.Controller
/// 输出系统消息
/// </summary>
/// <param name="msg"></param>
/// <param name="level"></param>
/// <param name="useLevel"></param>
/// <returns></returns>
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);
/// <summary>
/// 输出错误消息
@ -46,7 +49,7 @@ namespace Milimoe.FunGame.Core.Controller
public BaseAddonController(IAddon addon, Dictionary<string, object> delegates)
{
Addon = (T)addon;
if (delegates.TryGetValue("WriteLine", out object? value)) MaskMethod_WriteLine = value != null ? (Action<string>)value : new(DefaultPrint);
if (delegates.TryGetValue("WriteLine", out object? value)) MaskMethod_WriteLine = value != null ? (Action<string, string, LogLevel, bool>)value : new(DefaultPrint);
if (delegates.TryGetValue("Error", out value)) MaskMethod_Error = value != null ? (Action<Exception>)value : new(DefaultPrint);
MaskMethod_WriteLine ??= new(DefaultPrint);
MaskMethod_Error ??= new(DefaultPrint);
@ -55,15 +58,22 @@ namespace Milimoe.FunGame.Core.Controller
/// <summary>
/// 默认的输出错误消息方法
/// </summary>
/// <param name="name"></param>
/// <param name="msg"></param>
/// <param name="level"></param>
/// <param name="useLevel"></param>
/// <returns></returns>
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> ");
}
/// <summary>
/// 输出错误消息
/// </summary>
/// <param name="e"></param>
/// <returns></returns>
private void DefaultPrint(Exception e) => DefaultPrint(e.ToString());
private void DefaultPrint(Exception e) => DefaultPrint(Addon.Name, e.ToString(), LogLevel.Error);
}
}

View File

@ -21,7 +21,7 @@ namespace Milimoe.FunGame.Core.Controller
/// <summary>
/// 与服务器的连接套接字实例WebSocket
/// </summary>
public HTTPClient? WebSocket => _WebSocket;
public HTTPClient? HTTPClient => _HTTPClient;
/// <summary>
/// 套接字是否已经连接
@ -41,7 +41,7 @@ namespace Milimoe.FunGame.Core.Controller
/// <summary>
/// 用于类内赋值
/// </summary>
protected HTTPClient? _WebSocket;
protected HTTPClient? _HTTPClient;
/// <summary>
/// 是否正在接收服务器信息
@ -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<object>().ToArray());
if (_WebSocket.Connected)
_HTTPClient?.Close();
_HTTPClient = await HTTPClient.Connect(address, ssl, port, subUrl, connectArgs.Cast<object>().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<Guid>(2);
_HTTPClient.Token = obj.GetParam<Guid>(2);
serverName = obj.GetParam<string>(3) ?? "";
notice = obj.GetParam<string>(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)
@ -388,7 +390,9 @@ namespace Milimoe.FunGame.Core.Controller
/// 输出消息
/// </summary>
/// <param name="msg"></param>
public abstract void WritelnSystemInfo(string msg);
/// <param name="level"></param>
/// <param name="useLevel"></param>
public abstract void WritelnSystemInfo(string msg, LogLevel level = LogLevel.Info, bool useLevel = true);
/// <summary>
/// 自定处理异常的方法
@ -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;
}
@ -642,6 +646,10 @@ namespace Milimoe.FunGame.Core.Controller
SocketHandler_Gaming(obj);
break;
case SocketMessageType.AnonymousGameServer:
SocketHandler_AnonymousGameServer(obj);
break;
case SocketMessageType.Unknown:
default:
break;
@ -735,5 +743,14 @@ namespace Milimoe.FunGame.Core.Controller
{
}
/// <summary>
/// 客户端接收并处理匿名服务器的消息
/// </summary>
/// <param name="ServerMessage"></param>
protected virtual void SocketHandler_AnonymousGameServer(SocketObject ServerMessage)
{
}
}
}

View File

@ -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);
}
}

View File

@ -8,6 +8,6 @@ namespace Milimoe.FunGame.Core.Interface.Addons
{
public bool StartServer(string GameModule, Room Room, List<User> Users, IServerModel RoomMasterServerModel, Dictionary<string, IServerModel> ServerModels, params object[] args);
public Task<Dictionary<string, object>> GamingMessageHandler(string username, GamingType type, Dictionary<string, object> data);
public Task<Dictionary<string, object>> GamingMessageHandler(IServerModel model, GamingType type, Dictionary<string, object> data);
}
}

View File

@ -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<User> Users = [];
protected IServerModel? RoomMaster;
protected Dictionary<string, IServerModel> All = [];
private readonly struct ModuleServerWorker(Room room, List<User> users, IServerModel roomMaster, Dictionary<string, IServerModel> serverModels)
{
public Room Room { get; } = room;
public List<User> Users { get; } = users;
public IServerModel RoomMaster { get; } = roomMaster;
public Dictionary<string, IServerModel> All { get; } = serverModels;
public List<User> ConnectedUser { get; } = [];
public Dictionary<string, Dictionary<string, object>> UserData { get; } = [];
}
private ConcurrentDictionary<string, ModuleServerWorker> Workers { get; } = [];
public override bool StartServer(string GameModule, Room Room, List<User> Users, IServerModel RoomMasterServerModel, Dictionary<string, IServerModel> 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<User> ConnectedUser = [];
private readonly Dictionary<string, Dictionary<string, object>> 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<string, object>? value))
if (worker.UserData.TryGetValue(username, out Dictionary<string, object>? 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<Dictionary<string, object>> GamingMessageHandler(string username, GamingType type, Dictionary<string, object> data)
public override async Task<Dictionary<string, object>> GamingMessageHandler(IServerModel model, GamingType type, Dictionary<string, object> data)
{
Dictionary<string, object> 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<string>(data, "username") ?? "";
Guid token = NetworkUtility.JsonDeserializeFromDictionary<Guid>(data, "connect_token");
if (un == username && UserData.TryGetValue(username, out Dictionary<string, object>? value) && value != null && (value["connect_token"]?.Equals(token) ?? false))
if (un == username && worker.UserData.TryGetValue(username, out Dictionary<string, object>? 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<IServerModel> _clientModels = [];
/// <summary>
/// 匿名服务器示例
/// </summary>
/// <param name="model"></param>
/// <param name="data"></param>
/// <returns></returns>
public override bool StartAnonymousServer(IServerModel model, Dictionary<string, object> data)
{
// 可以做验证处理
string access_token = NetworkUtility.JsonDeserializeFromDictionary<string>(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);
}
/// <summary>
/// 接收并处理匿名服务器消息
/// </summary>
/// <param name="model"></param>
/// <param name="data"></param>
/// <returns></returns>
public override async Task<Dictionary<string, object>> AnonymousGameServerHandler(IServerModel model, Dictionary<string, object> data)
{
Dictionary<string, object> result = [];
// 根据服务器和客户端的数据传输约定,自行处理 data并返回。
if (data.Count > 0)
{
await Task.Delay(1);
}
result.Add("msg", "匿名服务器已经收到消息了");
return result;
}
}
/// <summary>

View File

@ -39,6 +39,11 @@ namespace Milimoe.FunGame.Core.Library.Common.Addon
/// </summary>
public abstract GameModuleDepend GameModuleDepend { get; }
/// <summary>
/// 是否是匿名服务器
/// </summary>
public virtual bool IsAnonymous { get; set; } = false;
/// <summary>
/// 包含了一些常用方法的控制器
/// </summary>
@ -77,11 +82,45 @@ namespace Milimoe.FunGame.Core.Library.Common.Addon
/// <summary>
/// 接收并处理GamingMessage
/// </summary>
/// <param name="username">发送此消息的账号</param>
/// <param name="model">发送此消息的客户端</param>
/// <param name="type">消息类型</param>
/// <param name="data">消息参数</param>
/// <returns>底层会将字典中的数据发送给客户端</returns>
public abstract Task<Dictionary<string, object>> GamingMessageHandler(string username, GamingType type, Dictionary<string, object> data);
public abstract Task<Dictionary<string, object>> GamingMessageHandler(IServerModel model, GamingType type, Dictionary<string, object> data);
/// <summary>
/// 启动匿名服务器监听
/// </summary>
/// <param name="model"></param>
/// <param name="data"></param>
/// <returns></returns>
public virtual bool StartAnonymousServer(IServerModel model, Dictionary<string, object> data)
{
return true;
}
/// <summary>
/// 结束匿名服务器监听
/// </summary>
/// <param name="model"></param>
/// <returns></returns>
public virtual void CloseAnonymousServer(IServerModel model)
{
}
/// <summary>
/// 接收并处理匿名服务器监听消息<para/>
/// 此方法为可选实现,可以帮助 RESTful API 处理不需要验证的 WebSocket 请求
/// </summary>
/// <param name="model">发送此消息的客户端</param>
/// <param name="data">消息参数</param>
/// <returns>底层会将字典中的数据发送给客户端</returns>
public virtual async Task<Dictionary<string, object>> AnonymousGameServerHandler(IServerModel model, Dictionary<string, object> data)
{
await Task.Delay(1);
return [];
}
/// <summary>
/// 加载标记
@ -129,13 +168,24 @@ namespace Milimoe.FunGame.Core.Library.Common.Addon
/// <param name="clients"></param>
/// <param name="type"></param>
/// <param name="data"></param>
protected virtual async Task SendGamingMessage(IEnumerable<IServerModel> clients, GamingType type, Dictionary<string, object> data)
/// <returns>发送失败的客户端</returns>
protected virtual async Task<List<IServerModel>> SendGamingMessage(IEnumerable<IServerModel> clients, GamingType type, Dictionary<string, object> data)
{
// 发送局内消息
List<IServerModel> failedModels = [];
foreach (IServerModel s in clients)
{
try
{
await s.Send(SocketMessageType.Gaming, type, data);
}
catch (System.Exception e)
{
Controller.Error(e);
failedModels.Add(s);
}
}
return failedModels;
}
/// <summary>
@ -144,13 +194,48 @@ namespace Milimoe.FunGame.Core.Library.Common.Addon
/// <param name="clients"></param>
/// <param name="type"></param>
/// <param name="args"></param>
protected virtual async Task Send(IEnumerable<IServerModel> clients, SocketMessageType type, params object[] args)
/// <returns>发送失败的客户端</returns>
protected virtual async Task<List<IServerModel>> Send(IEnumerable<IServerModel> clients, SocketMessageType type, params object[] args)
{
// 发送消息
List<IServerModel> failedModels = [];
foreach (IServerModel s in clients)
{
try
{
await s.Send(type, args);
}
catch (System.Exception e)
{
Controller.Error(e);
failedModels.Add(s);
}
}
return failedModels;
}
/// <summary>
/// 给客户端发送匿名服务器消息
/// </summary>
/// <param name="clients"></param>
/// <param name="data"></param>
/// <returns>发送失败的客户端</returns>
protected virtual async Task<List<IServerModel>> SendAnonymousGameServerMessage(IEnumerable<IServerModel> clients, Dictionary<string, object> data)
{
List<IServerModel> 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;
}
}
}

View File

@ -51,6 +51,8 @@ namespace Milimoe.FunGame.Core.Library.Common.Architecture
}
private async Task SendHeartBeat()
{
try
{
await Task.Delay(100);
if (_Socket != null)
@ -71,7 +73,7 @@ namespace Milimoe.FunGame.Core.Library.Common.Architecture
}
else if (_HTTPClient != null)
{
while (_HTTPClient.Instance?.State == System.Net.WebSockets.WebSocketState.Open)
while (_HTTPClient.WebSocket?.State == System.Net.WebSockets.WebSocketState.Open)
{
if (!SendingHeartBeat) _SendingHeartBeat = true;
// 发送心跳包
@ -86,6 +88,20 @@ namespace Milimoe.FunGame.Core.Library.Common.Architecture
}
_SendingHeartBeat = false;
}
catch (System.Exception e)
{
if (_Socket != null)
{
_Socket.OnConnectionLost(e);
_Socket.Close();
}
if (_HTTPClient != null)
{
_HTTPClient.OnConnectionLost(e);
_HTTPClient.Close();
}
}
}
private void AddHeartBeatFaileds()
{

View File

@ -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<System.Exception>? ConnectionLost;
public event Action? Closed;
private bool _receiving = false;
private readonly HashSet<Action<SocketObject>> _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<SocketResult> 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;
}
/// <summary>
/// 发送 GET 请求
/// </summary>
/// <param name="url"></param>
/// <returns></returns>
public async Task<T?> HttpGet<T>(string url)
{
HttpResponseMessage response = await Instance.GetAsync(url);
response.EnsureSuccessStatusCode();
string content = await response.Content.ReadAsStringAsync();
T? result = NetworkUtility.JsonDeserialize<T>(content);
return result;
}
/// <summary>
/// 发送 POST 请求
/// </summary>
/// <typeparam name="T"></typeparam>
/// <param name="url"></param>
/// <param name="json"></param>
/// <returns></returns>
public async Task<T?> HttpPost<T>(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<T>(read);
return result;
}
public void AddSocketObjectHandler(Action<SocketObject> 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<SocketObject> 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();

View File

@ -19,6 +19,9 @@ namespace Milimoe.FunGame.Core.Library.Common.Network
public bool Receiving => _receiving;
private HeartBeat HeartBeat { get; }
public event Action<System.Exception>? ConnectionLost;
public event Action? Closed;
private Task? _receivingTask;
private bool _receiving = false;
private readonly HashSet<Action<SocketObject>> _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)

View File

@ -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/"
};
}
}
/// <summary>
/// 配合 <see cref="InterfaceMethod"/> <see cref="InterfaceType"/> 使用,也别忘了修改 <see cref="Api.Utility.Implement"/>
/// </summary>
@ -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";
/// <summary>
/// 将通信类型的枚举转换为字符串

View File

@ -77,7 +77,8 @@ namespace Milimoe.FunGame.Core.Library.Constant
MatchRoom,
StartGame,
EndGame,
Gaming
Gaming,
AnonymousGameServer
}
/// <summary>
@ -809,4 +810,14 @@ namespace Milimoe.FunGame.Core.Library.Constant
Immediate,
Progressive
}
public enum LogLevel
{
Trace,
Debug,
Info,
Warning,
Error,
Critical
}
}

View File

@ -38,9 +38,9 @@ namespace Milimoe.FunGame.Core.Service
/// </summary>
/// <param name="uri"></param>
/// <returns></returns>
internal static async Task<System.Net.WebSockets.ClientWebSocket?> Connect(Uri uri)
internal static async Task<ClientWebSocket?> 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
/// <param name="socket"></param>
/// <param name="obj"></param>
/// <returns></returns>
internal static async Task<SocketResult> Send(System.Net.WebSockets.ClientWebSocket socket, SocketObject obj)
internal static async Task<SocketResult> Send(ClientWebSocket socket, SocketObject obj)
{
if (socket != null)
{
@ -176,12 +176,12 @@ namespace Milimoe.FunGame.Core.Service
/// <returns></returns>
internal static async Task<bool> 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<byte>(buffer), CancellationToken.None);
WebSocketReceiveResult result = await client.WebSocket.ReceiveAsync(new ArraySegment<byte>(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;
}
}