├── .gitignore ├── Misc ├── ClientTestRun.png └── ServerTestRun.png ├── UpUpServer ├── Core │ ├── CoreInterface │ │ ├── IProtocol.cs │ │ ├── ISystem.cs │ │ ├── INetPackage.cs │ │ ├── IPackSerialize.cs │ │ ├── IRouter.cs │ │ ├── IRequest.cs │ │ ├── IServer.cs │ │ ├── IConnection.cs │ │ ├── IConnManager.cs │ │ └── IMsgManager.cs │ ├── Attribute │ │ ├── AutoInitAttribute.cs │ │ ├── AutoSystemAttribute.cs │ │ └── AutoRouterAttribute.cs │ ├── Const │ │ └── BaseDefine.cs │ ├── Log │ │ ├── LogLevel.cs │ │ ├── ILog.cs │ │ ├── Log.cs │ │ ├── ConsoleLogger.cs │ │ ├── NLogger.cs │ │ ├── Logger.cs │ │ └── FileLogger.cs │ ├── CoreImpl │ │ ├── UpNetPackage.cs │ │ ├── Request.cs │ │ ├── DB │ │ │ ├── RedisDBProxy.cs │ │ │ └── MongoDBProxy.cs │ │ ├── Manager │ │ │ ├── SystemManager.cs │ │ │ ├── ConnManager.cs │ │ │ └── MsgManager.cs │ │ ├── TimerManager │ │ │ ├── DelayTimer.cs │ │ │ ├── LoopTimer.cs │ │ │ ├── TimerManager.cs │ │ │ └── Timer.cs │ │ ├── JsonRpcRouter.cs │ │ ├── UpPackSerialize.cs │ │ ├── Server.cs │ │ └── Connection.cs │ ├── DefaultProto │ │ └── EchoProto.cs │ ├── Config │ │ ├── ServerConfig.cs │ │ └── ConfigManager.cs │ └── DefaultRouter │ │ └── EchoRouter.cs ├── Utils │ ├── ReflectUtil.cs │ ├── SimpleSingle.cs │ ├── NamespaceProcess.cs │ ├── ObjectExtension.cs │ └── TimeUtil.cs ├── Program.cs ├── Conf │ ├── ServerConfig.json │ └── nlog.config ├── UpUpServer.csproj └── UnitTest │ └── ServerTest.cs ├── ServerTest ├── Const │ ├── BaseDefine.cs │ └── RedisDefine.cs ├── Data │ └── User.cs ├── Proto │ ├── ServerProto.cs │ ├── LoginProto.cs │ ├── ProtoDefine.cs │ ├── ResponseDefine.cs │ └── ChatProto.cs ├── shell │ ├── run.sh │ ├── stop.sh │ ├── install.sh │ └── DbBackup.sh ├── ServerTest.csproj ├── RpcHandle │ ├── ServerRouter.cs │ ├── ChatRouter.cs │ └── LoginRouter.cs ├── Program.cs └── System │ └── ChatSystem.cs ├── ClientTest ├── UpNetPackage.cs ├── SimpleSingle.cs ├── ClientTest.csproj ├── Sync │ └── MainThreadSynchronizationContext.cs ├── UpPackSerialize.cs ├── TcpClient.cs ├── MsgManager.cs ├── Program.cs └── Connection.cs ├── README.md ├── LICENSE └── UpUpServer.sln /.gitignore: -------------------------------------------------------------------------------- 1 | .idea/ 2 | *bin/ 3 | *obj/ 4 | /packages/ 5 | riderModule.iml -------------------------------------------------------------------------------- /Misc/ClientTestRun.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xtlvlv/UpUpServer/HEAD/Misc/ClientTestRun.png -------------------------------------------------------------------------------- /Misc/ServerTestRun.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xtlvlv/UpUpServer/HEAD/Misc/ServerTestRun.png -------------------------------------------------------------------------------- /UpUpServer/Core/CoreInterface/IProtocol.cs: -------------------------------------------------------------------------------- 1 | namespace UpUpServer; 2 | 3 | public interface IProtocol 4 | { 5 | } -------------------------------------------------------------------------------- /UpUpServer/Utils/ReflectUtil.cs: -------------------------------------------------------------------------------- 1 | using System.Reflection; 2 | using MongoDB.Driver; 3 | 4 | namespace UpUpServer; 5 | 6 | -------------------------------------------------------------------------------- /UpUpServer/Core/Attribute/AutoInitAttribute.cs: -------------------------------------------------------------------------------- 1 | namespace UpUpServer; 2 | 3 | public class AutoInitAttribute : Attribute 4 | { 5 | } -------------------------------------------------------------------------------- /ServerTest/Const/BaseDefine.cs: -------------------------------------------------------------------------------- 1 | using UpUpServer; 2 | 3 | public class BaseDefine 4 | { 5 | public const string CompanyName = "UpUpDraw"; 6 | } 7 | 8 | -------------------------------------------------------------------------------- /UpUpServer/Core/CoreInterface/ISystem.cs: -------------------------------------------------------------------------------- 1 | namespace UpUpServer; 2 | 3 | public interface ISystem 4 | { 5 | void Init(); 6 | void Update(); 7 | void Dispose(); 8 | } -------------------------------------------------------------------------------- /UpUpServer/Core/Const/BaseDefine.cs: -------------------------------------------------------------------------------- 1 | namespace UpUpServer; 2 | 3 | public class BaseDefine 4 | { 5 | public const string ServerConfigPath = 6 | @"Conf/ServerConfig.json"; 7 | } -------------------------------------------------------------------------------- /UpUpServer/Core/Attribute/AutoSystemAttribute.cs: -------------------------------------------------------------------------------- 1 | namespace UpUpServer; 2 | 3 | public class AutoSystemAttribute: Attribute 4 | { 5 | public AutoSystemAttribute() 6 | { 7 | } 8 | } -------------------------------------------------------------------------------- /ServerTest/Const/RedisDefine.cs: -------------------------------------------------------------------------------- 1 | namespace ServerTest.Const; 2 | 3 | public class RedisKeyDefine 4 | { 5 | public const string UserNameKey = "UserNames"; 6 | public const string UidKey = "UidKey"; 7 | 8 | } -------------------------------------------------------------------------------- /ClientTest/UpNetPackage.cs: -------------------------------------------------------------------------------- 1 | using System.Buffers; 2 | 3 | public class UpNetPackage 4 | { 5 | public uint AllLength { get; set; } 6 | public uint MsgId { get; set; } 7 | public ReadOnlySequence Data { get; set; } 8 | } -------------------------------------------------------------------------------- /UpUpServer/Core/Log/LogLevel.cs: -------------------------------------------------------------------------------- 1 | namespace UpUpServer; 2 | 3 | public class LogLevel 4 | { 5 | public const int Trace = 0; 6 | public const int Info = 1; 7 | public const int Warning = 2; 8 | public const int Error = 3; 9 | } -------------------------------------------------------------------------------- /ServerTest/Data/User.cs: -------------------------------------------------------------------------------- 1 | 2 | // 用户数据表 3 | public class User 4 | { 5 | public string Id; 6 | public string UserName; 7 | public ulong Uid; 8 | public string Password; 9 | public string Data; 10 | public ulong DataVersion; 11 | } -------------------------------------------------------------------------------- /UpUpServer/Core/Attribute/AutoRouterAttribute.cs: -------------------------------------------------------------------------------- 1 | namespace UpUpServer; 2 | 3 | public class AutoRouterAttribute: Attribute 4 | { 5 | public uint ReqId; 6 | public AutoRouterAttribute(uint reqId) 7 | { 8 | ReqId = reqId; 9 | } 10 | } -------------------------------------------------------------------------------- /ServerTest/Proto/ServerProto.cs: -------------------------------------------------------------------------------- 1 | 2 | using UpUpServer; 3 | 4 | // 获取服务器信息 5 | public class GetOnlineInfoRequest : IProtocol 6 | { 7 | } 8 | 9 | public class GetOnlineInfoResponse : IProtocol 10 | { 11 | public int OnlinePlayerCount; // 在线玩家数量 12 | } 13 | -------------------------------------------------------------------------------- /UpUpServer/Core/CoreInterface/INetPackage.cs: -------------------------------------------------------------------------------- 1 | using System.Buffers; 2 | 3 | namespace UpUpServer; 4 | 5 | public interface INetPackage 6 | { 7 | uint AllLength { get; set; } 8 | uint MsgId { get; set; } 9 | ReadOnlySequence Data { get; set; } 10 | } -------------------------------------------------------------------------------- /UpUpServer/Program.cs: -------------------------------------------------------------------------------- 1 | namespace UpUpServer; 2 | 3 | class Program 4 | { 5 | static Server server; 6 | 7 | public static void Main(string[] args) 8 | { 9 | Console.WriteLine("Hello, UpUpServer!"); 10 | // ServerTest.TestRedis(); 11 | } 12 | } -------------------------------------------------------------------------------- /UpUpServer/Core/CoreInterface/IPackSerialize.cs: -------------------------------------------------------------------------------- 1 | namespace UpUpServer; 2 | using System.Buffers; 3 | 4 | public interface IPackSerialize 5 | { 6 | uint GetHeadLen(); 7 | void Serialize(INetPackage msg, ref Memory data); 8 | INetPackage Deserialize(ReadOnlySequence data); 9 | } -------------------------------------------------------------------------------- /UpUpServer/Core/CoreImpl/UpNetPackage.cs: -------------------------------------------------------------------------------- 1 | using System.Buffers; 2 | 3 | namespace UpUpServer; 4 | 5 | public class UpNetPackage : INetPackage 6 | { 7 | public uint AllLength { get; set; } 8 | public uint MsgId { get; set; } 9 | public ReadOnlySequence Data { get; set; } 10 | } -------------------------------------------------------------------------------- /ServerTest/shell/run.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | # 定义程序名字 4 | PROGRAM_NAME="ServerTest.dll" 5 | 6 | # 路径 7 | PROGRAM_PATH="/root/UpUpServer/net7.0" 8 | 9 | cd ${PROGRAM_PATH} 10 | 11 | # 在后台执行程序 12 | nohup dotnet ${PROGRAM_NAME} > /dev/null 2>&1 & 13 | 14 | # 提示用户,可以根据需要自定义提示信息 15 | echo "C# 程序已在后台启动" -------------------------------------------------------------------------------- /UpUpServer/Conf/ServerConfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "ip": "0.0.0.0", 3 | "port": 9999, 4 | "workPoolSize": 4, 5 | "NLogConfigPath": "Conf/nlog.config", 6 | "MongoDbUrl": "mongodb://localhost:27017", 7 | "MongoDbName": "GameTest", 8 | "RedisDbUrl": "127.0.0.1:6379?password=123456" 9 | } 10 | -------------------------------------------------------------------------------- /UpUpServer/Core/CoreInterface/IRouter.cs: -------------------------------------------------------------------------------- 1 | namespace UpUpServer; 2 | 3 | public interface IRouter 4 | { 5 | int ErrorCode { get; set; } 6 | IMsgManager MsgManager { get; set; } 7 | void PreHandle(IRequest request); 8 | Task Handle(IRequest request); 9 | void PostHandle(IRequest request); 10 | } -------------------------------------------------------------------------------- /UpUpServer/Core/DefaultProto/EchoProto.cs: -------------------------------------------------------------------------------- 1 | namespace UpUpServer; 2 | 3 | public class EchoReq : IProtocol 4 | { 5 | public string Msg { get; set; } 6 | } 7 | 8 | public class EchoRes : IProtocol 9 | { 10 | public string Msg { get; set; } 11 | } 12 | 13 | public class NoResponse: IProtocol 14 | { 15 | } -------------------------------------------------------------------------------- /UpUpServer/Core/CoreInterface/IRequest.cs: -------------------------------------------------------------------------------- 1 | namespace UpUpServer; 2 | 3 | public interface IRequest 4 | { 5 | IConnection Connection { get; set; } 6 | INetPackage NetPackage { get; set; } 7 | IProtocol? Req { get; set; } 8 | IProtocol Res { get; set; } 9 | INetPackage ResNetPackage { get; set; } 10 | 11 | } -------------------------------------------------------------------------------- /ServerTest/shell/stop.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | # 定义程序名字 4 | PROGRAM_NAME="ServerTest.dll" 5 | # 路径 6 | PROGRAM_PATH="/root/UpUpServer/net7.0" 7 | 8 | # 找到进程id 9 | pid=`ps -ef | grep ${PROGRAM_NAME} | grep -v grep | awk '{print $2}'` 10 | 11 | # 杀死进程 12 | kill -9 ${pid} 13 | 14 | # 提示用户,可以根据需要自定义提示信息 15 | echo "pid=${pid} 程序已停止" -------------------------------------------------------------------------------- /UpUpServer/Core/CoreInterface/IServer.cs: -------------------------------------------------------------------------------- 1 | namespace UpUpServer; 2 | 3 | public interface IServer 4 | { 5 | IMsgManager MsgManager { get; set; } 6 | IConnManager ConnManager { get; set; } 7 | 8 | bool IsRunning { get; set; } 9 | 10 | void Start(); 11 | void Stop(); 12 | void AddRouter(uint msgId, IRouter router); 13 | } -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # 介绍 2 | 3 | 一个简单的服务端rpc框架 4 | - UpUpServer, 框架核心 5 | - ServerTest, 服务示例 6 | - ClientTest, 客户端示例 7 | 8 | # 使用 9 | - 本地需要安装MongoDB 10 | - ServerTest实现了登录和聊天逻辑,参考 ServerTest/RpcHandler目录中的实现 11 | 12 | 13 | # Demo 14 | - ServerTest 运行示例 15 | ![ServerTestRun.png](Misc/ServerTestRun.png) 16 | - ClientTest 运行示例 17 | ![ServerTestRun.png](Misc/ClientTestRun.png) -------------------------------------------------------------------------------- /ClientTest/SimpleSingle.cs: -------------------------------------------------------------------------------- 1 | 2 | public class SingleClass where T : class, new() 3 | { 4 | private static T _instance; 5 | 6 | public static T Instance 7 | { 8 | get 9 | { 10 | if (_instance == null) 11 | { 12 | _instance = new T(); 13 | } 14 | 15 | return _instance; 16 | } 17 | } 18 | } -------------------------------------------------------------------------------- /ServerTest/Proto/LoginProto.cs: -------------------------------------------------------------------------------- 1 | using UpUpServer; 2 | 3 | public class LoginRequest : IProtocol 4 | { 5 | public string UserId; 6 | public string Password; 7 | } 8 | 9 | public class LoginResponse : IProtocol 10 | { 11 | public int Status; 12 | 13 | // 数据及数据版本 14 | public string Data; 15 | public ulong DataVersion; 16 | public ulong Uid; 17 | 18 | } 19 | 20 | -------------------------------------------------------------------------------- /ServerTest/Proto/ProtoDefine.cs: -------------------------------------------------------------------------------- 1 | 2 | public class ProtoDefine 3 | { 4 | 5 | // 业务协议 6 | public const int LoginReq = 1001; // 登录 7 | public const int LoginRes = 1002; 8 | 9 | 10 | public const int ChatReq = 1005; // 聊天 11 | public const int ChatRes = 1006; 12 | 13 | public const int GetOnlineInfoReq = 1025; // 获取服务器在线信息 14 | public const int GetOnlineInfoRes = 1026; 15 | } 16 | -------------------------------------------------------------------------------- /UpUpServer/Utils/SimpleSingle.cs: -------------------------------------------------------------------------------- 1 | namespace UpUpServer; 2 | 3 | public class SingleClass where T : class, new() 4 | { 5 | private static T _instance; 6 | 7 | public static T Instance 8 | { 9 | get 10 | { 11 | if (_instance == null) 12 | { 13 | _instance = new T(); 14 | } 15 | 16 | return _instance; 17 | } 18 | } 19 | } -------------------------------------------------------------------------------- /UpUpServer/Core/Config/ServerConfig.cs: -------------------------------------------------------------------------------- 1 | namespace UpUpServer; 2 | 3 | public class ServerConfig 4 | { 5 | public string Ip; 6 | public int Port; 7 | public int WorkPoolSize; 8 | public string LogPath; 9 | 10 | public string NLogConfigPath; 11 | 12 | public string MongoDbUrl; // 数据库地址 13 | public string MongoDbName; // 数据库名 14 | 15 | public string RedisDbUrl; // redis地址 16 | 17 | } -------------------------------------------------------------------------------- /UpUpServer/Core/CoreInterface/IConnection.cs: -------------------------------------------------------------------------------- 1 | namespace UpUpServer; 2 | 3 | public interface IConnection 4 | { 5 | uint Id { get; set; } 6 | string UserId { get; } 7 | 8 | IMsgManager MsgManager { get; set; } 9 | 10 | bool IsConnected { get; } 11 | 12 | void SetUserId(string userId); 13 | void Start(); 14 | void Close(); 15 | void Send(Memory buffer); 16 | ValueTask SendAsync(Memory data); 17 | } -------------------------------------------------------------------------------- /ServerTest/ServerTest.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Exe 5 | net7.0 6 | enable 7 | enable 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | -------------------------------------------------------------------------------- /ServerTest/Proto/ResponseDefine.cs: -------------------------------------------------------------------------------- 1 | 2 | public class ResponseDefine 3 | { 4 | public const int Success = 0; 5 | public const int Fail = 1; 6 | public const int UserNotExist = 2; // 用户不存在 7 | 8 | public const int UserNameHasUsed = 110; // 用户名已被使用 9 | public const int UserNameInvalid = 111; // 用户名无效 10 | 11 | public const int LoginNewUser = 121; 12 | public const int LoginOldUser = 122; 13 | 14 | } 15 | 16 | -------------------------------------------------------------------------------- /UpUpServer/Core/CoreInterface/IConnManager.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Concurrent; 2 | 3 | namespace UpUpServer; 4 | 5 | public interface IConnManager 6 | { 7 | ConcurrentDictionary Connections { get; } 8 | 9 | void AddConnection(IConnection connection); 10 | void RemoveConnection(uint connId); 11 | IConnection? GetConnection(uint connId); 12 | void CheckOnline(); 13 | int GetOnlinePlayerCount(); 14 | 15 | void Release(); 16 | } -------------------------------------------------------------------------------- /ServerTest/shell/install.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | # 安装步骤 4 | # 编译release版本,压缩成zip包,上传到服务器UpUpServer目录下 5 | # 执行 sh install.sh 自动解压 6 | # 执行 sh run.sh 启动程序 7 | # 执行 sh stop.sh 停止程序 8 | 9 | # 路径 10 | PROGRAM_PATH="/root/UpUpServer/net7.0" 11 | 12 | # 把 /root/UpUpServer/net7.0/logs 目录下的文件拷贝到 /root/UpUpServer/logs 目录下, 如果没有logs目录,就创建logs目录 13 | mkdir -p /root/UpUpServer/logs 14 | cp -r ${PROGRAM_PATH}/logs/* /root/UpUpServer/logs 15 | 16 | rm -rf ${PROGRAM_PATH} 17 | 18 | unzip /root/UpUpServer/net7.0.zip -d /root/UpUpServer 19 | 20 | -------------------------------------------------------------------------------- /UpUpServer/Core/DefaultRouter/EchoRouter.cs: -------------------------------------------------------------------------------- 1 | namespace UpUpServer; 2 | 3 | public class EchoRouter : JsonRpcRouter 4 | { 5 | public override Task Handle(IRequest request) 6 | { 7 | if (ErrorCode != 0) 8 | { 9 | return Task.CompletedTask; 10 | } 11 | base.Handle(request); 12 | var req = (EchoReq)request.Req!; 13 | var res = (EchoRes)request.Res; 14 | 15 | res.Msg = req.Msg; 16 | 17 | return Task.CompletedTask; 18 | } 19 | } -------------------------------------------------------------------------------- /UpUpServer/Core/CoreImpl/Request.cs: -------------------------------------------------------------------------------- 1 | using System.Buffers; 2 | 3 | namespace UpUpServer; 4 | 5 | public class Request : IRequest 6 | { 7 | public IConnection Connection { get; set; } 8 | public INetPackage NetPackage { get; set; } 9 | public IProtocol? Req { get; set; } 10 | public IProtocol Res { get; set; } 11 | public INetPackage ResNetPackage { get; set; } 12 | 13 | public Request(IConnection connection, INetPackage netPackage) 14 | { 15 | Connection = connection; 16 | NetPackage = netPackage; 17 | } 18 | } -------------------------------------------------------------------------------- /UpUpServer/Core/CoreInterface/IMsgManager.cs: -------------------------------------------------------------------------------- 1 | namespace UpUpServer; 2 | 3 | public interface IMsgManager 4 | { 5 | IServer Server { get; set; } 6 | Dictionary Routers { get; } 7 | void StartWorkPool(IServer server); 8 | void RegisterRouter(uint msgId, IRouter router); 9 | void UnRegisterRouter(uint msgId); 10 | Task HandleMsg(uint msgId, IRequest request); 11 | void AddRequest(IRequest request); 12 | void AddResponse(IRequest request); 13 | void AddPackResponse(IRequest request, uint msgId); 14 | 15 | 16 | } -------------------------------------------------------------------------------- /ServerTest/RpcHandle/ServerRouter.cs: -------------------------------------------------------------------------------- 1 | using MongoDB.Driver; 2 | using UpUpServer; 3 | 4 | [AutoRouter(ProtoDefine.GetOnlineInfoReq)] 5 | public class GetOnlineInfoRouter: JsonRpcRouter 6 | { 7 | 8 | public override async Task Handle(IRequest request) 9 | { 10 | await base.Handle(request); 11 | var req = (GetOnlineInfoRequest)request.Req!; 12 | var res = (GetOnlineInfoResponse)request.Res; 13 | 14 | res.OnlinePlayerCount = Program.Server.ConnManager.GetOnlinePlayerCount(); 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /UpUpServer/Core/Log/ILog.cs: -------------------------------------------------------------------------------- 1 | namespace UpUpServer; 2 | 3 | public interface ILog 4 | { 5 | void Init(); 6 | void Trace(string message, string tag = ""); 7 | void Info(string message, string tag = ""); 8 | void Warning(string message, string tag = ""); 9 | void Error(string message, string tag = ""); 10 | void Trace(string message, params object[] args); 11 | void Info(string message, params object[] args); 12 | void Warning(string message, params object[] args); 13 | void Error(string message, params object[] args); 14 | 15 | void Flush(); 16 | } -------------------------------------------------------------------------------- /UpUpServer/Core/Config/ConfigManager.cs: -------------------------------------------------------------------------------- 1 | namespace UpUpServer; 2 | 3 | using Newtonsoft.Json; 4 | 5 | public class ConfigManager : SingleClass 6 | { 7 | public ServerConfig? ServerConfig; 8 | 9 | public ConfigManager() 10 | { 11 | } 12 | 13 | public void Init() 14 | { 15 | ServerConfig = JsonConvert.DeserializeObject(File.ReadAllText(BaseDefine.ServerConfigPath)); 16 | if (ServerConfig is null) 17 | { 18 | Log.Error($"ServerConfig DeserializeObject failed"); 19 | Environment.Exit(0); 20 | } 21 | } 22 | } -------------------------------------------------------------------------------- /ClientTest/ClientTest.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Exe 5 | net7.0 6 | enable 7 | enable 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | -------------------------------------------------------------------------------- /ServerTest/RpcHandle/ChatRouter.cs: -------------------------------------------------------------------------------- 1 | using MongoDB.Driver; 2 | using UpUpServer; 3 | 4 | [AutoRouter(ProtoDefine.ChatReq)] 5 | public class ChatRouter: JsonRpcRouter 6 | { 7 | ChatSystem? _chatSystem; 8 | public override async Task Handle(IRequest request) 9 | { 10 | await base.Handle(request); 11 | if (_chatSystem is null) 12 | { 13 | _chatSystem = Program.Server.SysManager.GetSystem(); 14 | if (_chatSystem is null) 15 | { 16 | Log.Error("ChatSystem is null"); 17 | return; 18 | } 19 | } 20 | _chatSystem.AddChatRequest(request); 21 | } 22 | } -------------------------------------------------------------------------------- /ServerTest/Proto/ChatProto.cs: -------------------------------------------------------------------------------- 1 | 2 | using UpUpServer; 3 | 4 | public class ChatRequest : IProtocol 5 | { 6 | public ChatMessage Message; 7 | } 8 | 9 | public class ChatResponse : IProtocol 10 | { 11 | public List Messages; 12 | } 13 | 14 | public class ChatMessage : IProtocol 15 | { 16 | public int Channel; // 频道 17 | public int MsgType; // 消息类型 18 | public string UserName; 19 | public string Content; 20 | } 21 | 22 | public class ChatChannelDefine 23 | { 24 | public const int World = 1; 25 | public const int Team = 2; 26 | } 27 | 28 | public class ChatMsgTypeDefine 29 | { 30 | public const int Text = 1; // 支持表情 31 | public const int Equip = 2; 32 | } 33 | 34 | -------------------------------------------------------------------------------- /ServerTest/shell/DbBackup.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | # 根据参数执行不同的命令 4 | # 1.日备份 5 | # 2.周备份 6 | # 3.月备份 7 | 8 | # 获取参数 9 | # $0 是脚本名字 10 | # $1 是第一个参数 指定备份类型 11 | 12 | if [ $1 -eq 1 ]; then 13 | # 日备份 14 | mongodump --db GameTest --out /root/MongoDBBackup/day$(date +"%Y-%m-%d") 15 | elif [ $1 -eq 2 ]; then 16 | # 周备份 17 | mongodump --db GameTest --out /root/MongoDBBackup/week$(date +"%Y-%m-%d") 18 | elif [ $1 -eq 3 ]; then 19 | # 月备份 20 | mongodump --db GameTest --out /root/MongoDBBackup/month$(date +"%Y-%m-%d") 21 | else 22 | # 默认日备份 23 | mongodump --db GameTest --out /root/MongoDBBackup/default$(date +"%Y-%m-%d") 24 | fi 25 | 26 | # 删除7天前的备份 27 | find /root/MongoDBBackup -mtime +7 -name "day*" -exec rm -rf {} \; -------------------------------------------------------------------------------- /UpUpServer/Core/CoreImpl/DB/RedisDBProxy.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.Extensions.Logging; 2 | using MongoDB.Driver; 3 | using ServiceStack; 4 | using ServiceStack.Redis; 5 | 6 | namespace UpUpServer; 7 | 8 | public class RedisDBProxy: SingleClass 9 | { 10 | public RedisManagerPool RedisPool { get; private set; } = null!; 11 | public IRedisClient? RedisClient; 12 | 13 | public void Open(string url) 14 | { 15 | try 16 | { 17 | RedisPool = new RedisManagerPool(url); 18 | // RedisClient = RedisPool.GetClient(); 19 | Log.Info($"Init Redis Success Url:{url}"); 20 | } 21 | catch (Exception) 22 | { 23 | Log.Error($"Init Redis Fail Url:{url}"); 24 | throw; 25 | } 26 | } 27 | public void Close() 28 | { 29 | RedisPool.Dispose(); 30 | } 31 | 32 | } -------------------------------------------------------------------------------- /UpUpServer/Core/CoreImpl/DB/MongoDBProxy.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.Extensions.Logging; 2 | using MongoDB.Driver; 3 | 4 | namespace UpUpServer; 5 | 6 | public class MongoDBProxy: SingleClass 7 | { 8 | public MongoClient Client { get; private set; } 9 | public IMongoDatabase CurDB { get; private set; } 10 | 11 | public void Open(string url, string dbName) 12 | { 13 | try 14 | { 15 | var settings = MongoClientSettings.FromConnectionString(url); 16 | Client = new MongoClient(settings); 17 | CurDB = Client.GetDatabase(dbName); 18 | Log.Info($"Init MongoDB Success Url:{url} DbName:{dbName}"); 19 | } 20 | catch (Exception) 21 | { 22 | Log.Error($"Init MongoDB Fail Url:{url} DbName:{dbName}"); 23 | throw; 24 | } 25 | } 26 | public void Close() 27 | { 28 | Client.Cluster.Dispose(); 29 | } 30 | 31 | } -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2024 UpUpDraw 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /UpUpServer/Conf/nlog.config: -------------------------------------------------------------------------------- 1 | 2 | 3 | 8 | 9 | 10 | 11 | 12 | 20 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | -------------------------------------------------------------------------------- /UpUpServer/Core/CoreImpl/Manager/SystemManager.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Concurrent; 2 | 3 | namespace UpUpServer; 4 | 5 | public class SystemManager 6 | { 7 | private HashSet _systems { get; } = new HashSet(); 8 | private Dictionary _systemsIOC = new Dictionary(); 9 | private List _updateSystems = new List(); 10 | 11 | public void Update() 12 | { 13 | for (int i = 0; i < _updateSystems.Count; i++) 14 | { 15 | _updateSystems[i].Update(); 16 | } 17 | } 18 | 19 | public void RegistSystem(Type type, ISystem system) 20 | { 21 | system.Init(); 22 | _systems.Add(system); 23 | _updateSystems.Add(system); 24 | _systemsIOC.Add(type, system); 25 | } 26 | 27 | public T? GetSystem() where T : class 28 | { 29 | if (_systemsIOC.TryGetValue(typeof(T), out var system)) 30 | { 31 | return system as T; 32 | } 33 | else 34 | { 35 | Log.Error($"no system type {typeof(T)}"); 36 | } 37 | 38 | return null; 39 | } 40 | 41 | public void Release() 42 | { 43 | 44 | } 45 | 46 | 47 | } -------------------------------------------------------------------------------- /UpUpServer/UpUpServer.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Exe 5 | net7.0 6 | enable 7 | enable 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | Always 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | Always 34 | 35 | 36 | 37 | 38 | -------------------------------------------------------------------------------- /UpUpServer/Core/Log/Log.cs: -------------------------------------------------------------------------------- 1 | namespace UpUpServer; 2 | 3 | public static class Log 4 | { 5 | public static void Trace(string msg) 6 | { 7 | Logger.Instance.Trace(msg); 8 | } 9 | 10 | public static void Info(string msg) 11 | { 12 | Logger.Instance.Info(msg); 13 | } 14 | 15 | public static void Warning(string msg) 16 | { 17 | Logger.Instance.Warning(msg); 18 | } 19 | 20 | public static void Error(string msg) 21 | { 22 | Logger.Instance.Error(msg); 23 | } 24 | 25 | public static void Error(Exception e) 26 | { 27 | Logger.Instance.Error(e.Message); 28 | } 29 | 30 | public static void Trace(string message, params object[] args) 31 | { 32 | Logger.Instance.Trace(message, args); 33 | } 34 | 35 | public static void Warning(string message, params object[] args) 36 | { 37 | Logger.Instance.Warning(string.Format(message, args)); 38 | } 39 | 40 | public static void Info(string message, params object[] args) 41 | { 42 | Logger.Instance.Info(string.Format(message, args)); 43 | } 44 | 45 | 46 | public static void Error(string message, params object[] args) 47 | { 48 | Logger.Instance.Error(message, args); 49 | } 50 | 51 | public static void Flush() 52 | { 53 | Logger.Instance.Flush(); 54 | } 55 | } -------------------------------------------------------------------------------- /ClientTest/Sync/MainThreadSynchronizationContext.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Concurrent; 3 | using System.Threading; 4 | 5 | public class MainThreadSynchronizationContext : SynchronizationContext 6 | { 7 | private static MainThreadSynchronizationContext instance; 8 | public static MainThreadSynchronizationContext Instance 9 | { 10 | get 11 | { 12 | if (instance == null) 13 | { 14 | instance = new MainThreadSynchronizationContext(); 15 | } 16 | 17 | return instance; 18 | } 19 | } 20 | 21 | private int mMainThreadId = Thread.CurrentThread.ManagedThreadId; 22 | private readonly ConcurrentQueue mConcurrentQueue = new ConcurrentQueue(); 23 | private Action mTempAction; 24 | 25 | public void Update() 26 | { 27 | int count = mConcurrentQueue.Count; 28 | for (int i = 0; i < count; ++i) 29 | { 30 | if (mConcurrentQueue.TryDequeue(out mTempAction)) 31 | { 32 | mTempAction(); 33 | } 34 | } 35 | } 36 | 37 | public override void Post(SendOrPostCallback sendOrPostCallback, object state = null) 38 | { 39 | if (Thread.CurrentThread.ManagedThreadId == mMainThreadId) 40 | { 41 | sendOrPostCallback(state); 42 | return; 43 | } 44 | 45 | mConcurrentQueue.Enqueue(() => { sendOrPostCallback(state); }); 46 | } 47 | } -------------------------------------------------------------------------------- /UpUpServer/Core/CoreImpl/Manager/ConnManager.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Concurrent; 2 | 3 | namespace UpUpServer; 4 | 5 | public class ConnManager: IConnManager 6 | { 7 | public ConcurrentDictionary Connections { get; } = new ConcurrentDictionary(); 8 | public void AddConnection(IConnection connection) 9 | { 10 | Connections.TryAdd(connection.Id, connection); 11 | } 12 | 13 | public void RemoveConnection(uint connId) 14 | { 15 | Connections.TryRemove(connId, out var conn); 16 | } 17 | 18 | public IConnection? GetConnection(uint connId) 19 | { 20 | Connections.TryGetValue(connId, out var conn); 21 | return conn; 22 | } 23 | 24 | public void CheckOnline() 25 | { 26 | var keys = Connections.Keys.ToArray(); 27 | foreach (var cid in keys) 28 | { 29 | var conn = GetConnection(cid); 30 | if (conn == null) 31 | { 32 | RemoveConnection(cid); 33 | continue; 34 | } 35 | else 36 | { 37 | if (!conn.IsConnected) 38 | { 39 | conn.Close(); 40 | RemoveConnection(cid); 41 | Log.Info($"ConnManager kick conn {cid}"); 42 | } 43 | } 44 | } 45 | } 46 | 47 | public int GetOnlinePlayerCount() 48 | { 49 | var count = Connections.Count; 50 | return count; 51 | } 52 | public void Release() 53 | { 54 | 55 | } 56 | } -------------------------------------------------------------------------------- /ServerTest/Program.cs: -------------------------------------------------------------------------------- 1 | using UpUpServer; 2 | using System.Collections; 3 | 4 | class Program 5 | { 6 | public static Server Server; 7 | public static ulong Count = 0; 8 | 9 | public static void Main(string[] args) 10 | { 11 | Console.WriteLine("Hello, UpUpServer!"); 12 | AppRun(); 13 | // ServerTest.TestEndian(); 14 | } 15 | 16 | private static void AppRun() 17 | { 18 | AppDomain.CurrentDomain.ProcessExit += (s, e) => 19 | { 20 | Log.Flush(); 21 | // 正常退出 22 | OnProcessExit(s, e); 23 | }; 24 | 25 | AppDomain.CurrentDomain.UnhandledException += (s, e) => 26 | { 27 | Log.Flush(); 28 | OnFetalException(e.ExceptionObject); 29 | // OnProcessExit(s, e); 30 | }; 31 | 32 | ConfigManager.Instance.Init(); 33 | 34 | Logger.Instance.ILog = new NLogger(); 35 | 36 | Server = new Server(); 37 | Server.Start(); 38 | 39 | while (true) 40 | { 41 | // Log.Info($"RunCount={Count}"); 42 | Thread.Sleep(10000); 43 | } 44 | } 45 | 46 | private static void OnProcessExit(object sender, EventArgs e) 47 | { 48 | Log.Info($"app exit"); 49 | Server.Stop(); 50 | } 51 | 52 | 53 | private static void OnFetalException(object e) 54 | { 55 | Log.Error("UnhandledException exit"); 56 | if (e is IEnumerable arr) 57 | { 58 | foreach (var ex in arr) 59 | Log.Error($"Unhandled Exception:{ex}"); 60 | } 61 | else 62 | { 63 | Log.Error($"Unhandled Exception:{e}"); 64 | } 65 | } 66 | } -------------------------------------------------------------------------------- /UpUpServer/Core/CoreImpl/TimerManager/DelayTimer.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | 3 | namespace UpUpServer 4 | { 5 | public class DelayTimer : Timer 6 | { 7 | protected Action _onComplete; 8 | 9 | /// 10 | /// 定时执行 11 | /// 12 | /// 为true时,这个定时器不受暂停和cancel影响,如果该定时器挂载在物体上,物体销毁该定时器还是会被销毁 13 | /// 延迟时间 14 | /// 完成回调 15 | /// 每帧回调(Unity使用) 16 | /// 17 | public DelayTimer(bool isPersistence, long duration, Action onComplete, Action? onUpdate, 18 | object autoDestroyOwner) 19 | : base(isPersistence, duration, onUpdate, autoDestroyOwner) 20 | { 21 | _onComplete = onComplete; 22 | } 23 | 24 | public override void Update() 25 | { 26 | if (!CheckUpdate()) return; 27 | 28 | if (_onUpdate != null) 29 | SafeCall(_onUpdate, GetTimeElapsed()); 30 | 31 | if (GetWorldTime() >= GetFireTime()) 32 | { 33 | isCompleted = true; 34 | SafeCall(_onComplete); 35 | } 36 | } 37 | 38 | public void Restart(long newDuration) 39 | { 40 | duration = newDuration; 41 | Restart(); 42 | } 43 | 44 | public void Restart(long newDuration, Action newOnComplete, Action newOnUpdate) 45 | { 46 | duration = newDuration; 47 | _onComplete = newOnComplete; 48 | _onUpdate = newOnUpdate; 49 | Restart(); 50 | } 51 | } 52 | } -------------------------------------------------------------------------------- /UpUpServer/Utils/NamespaceProcess.cs: -------------------------------------------------------------------------------- 1 | namespace UpUpServer; 2 | 3 | using System; 4 | using System.IO; 5 | 6 | public class NamespaceProcess 7 | { 8 | static string rootPath = @"D:\Unity\ConsoleProject\ConsoleApp2"; // 替换为你的项目路径 9 | static string targetNamespace = "UpUpServer"; // 替换为你的目标命名空间 10 | static List ingoreDirs = new List() { "obj", "bin", }; // 替换为你的忽略目录 11 | 12 | public static void NamespaceBatchChange() 13 | { 14 | Console.WriteLine("将会对文件进行批量添加命名空间操作,此操作不可逆,请注意备份,是否继续?(y/n)"); 15 | var key = Console.ReadKey(); 16 | Console.WriteLine(); 17 | if (key.KeyChar == 'y') 18 | { 19 | Console.WriteLine("开始批量添加命名空间..."); 20 | InsertNamespaceToFiles(); 21 | Console.WriteLine("批量添加命名空间完成"); 22 | } 23 | else 24 | { 25 | Console.WriteLine("已取消批量添加命名空间操作"); 26 | return; 27 | } 28 | } 29 | 30 | static void InsertNamespaceToFiles() 31 | { 32 | string[] files = Directory.GetFiles(rootPath, "*.cs", SearchOption.AllDirectories); 33 | 34 | foreach (string file in files) 35 | { 36 | bool isIgnore = false; 37 | ingoreDirs.ForEach(x => 38 | { 39 | if (file.Contains(x)) 40 | isIgnore = true; 41 | }); 42 | if (isIgnore) 43 | { 44 | Log.Info($"忽略文件:{file}"); 45 | continue; 46 | } 47 | 48 | string content = File.ReadAllText(file); 49 | 50 | if (!content.Contains($"namespace {targetNamespace};")) 51 | { 52 | content = $"namespace {targetNamespace};\n\n{content}"; 53 | 54 | File.WriteAllText(file, content); 55 | } 56 | } 57 | } 58 | } -------------------------------------------------------------------------------- /UpUpServer/Core/Log/ConsoleLogger.cs: -------------------------------------------------------------------------------- 1 | namespace UpUpServer; 2 | 3 | public class ConsoleLogger : ILog 4 | { 5 | private string timeFormat = "yyyy-MM-dd HH:mm:ss.fff"; 6 | 7 | public void Init() 8 | { 9 | } 10 | 11 | public void Trace(string msg, string tag = "") 12 | { 13 | Console.WriteLine($"{tag} {msg}"); 14 | } 15 | 16 | public void Debug(string msg, string tag = "") 17 | { 18 | Console.WriteLine($"{tag} {msg}"); 19 | } 20 | 21 | public void Info(string msg, string tag = "") 22 | { 23 | Console.WriteLine($"[Info]{DateTime.Now.ToString(timeFormat)} {tag} {msg}"); 24 | } 25 | 26 | public void Warning(string msg, string tag = "") 27 | { 28 | Console.WriteLine($"[Warning]{DateTime.Now.ToString(timeFormat)} {tag} {msg}"); 29 | } 30 | 31 | public void Error(string msg, string tag = "") 32 | { 33 | Console.WriteLine($"[Error]{DateTime.Now.ToString(timeFormat)} {tag} {msg}"); 34 | } 35 | 36 | public void Error(Exception e) 37 | { 38 | Console.WriteLine(e); 39 | } 40 | 41 | public void Trace(string message, params object[] args) 42 | { 43 | Console.WriteLine(String.Format(message, args)); 44 | } 45 | 46 | public void Warning(string message, params object[] args) 47 | { 48 | Console.WriteLine(String.Format(message, args)); 49 | } 50 | 51 | public void Info(string message, params object[] args) 52 | { 53 | Console.WriteLine(message, args); 54 | } 55 | 56 | public void Debug(string message, params object[] args) 57 | { 58 | Console.WriteLine(message, args); 59 | } 60 | 61 | public void Error(string message, params object[] args) 62 | { 63 | Console.WriteLine(message, args); 64 | } 65 | 66 | public void Flush() 67 | { 68 | } 69 | } -------------------------------------------------------------------------------- /UpUpServer.sln: -------------------------------------------------------------------------------- 1 | 2 | Microsoft Visual Studio Solution File, Format Version 12.00 3 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "UpUpServer", "UpUpServer\UpUpServer.csproj", "{D122C609-5EB0-425A-AD35-D0CA0594676A}" 4 | EndProject 5 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "ClientTest", "ClientTest\ClientTest.csproj", "{0B766ECF-3361-4881-A86C-D7A7FA3F8464}" 6 | EndProject 7 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "ServerTest", "ServerTest\ServerTest.csproj", "{A20F5A28-EECD-467F-B5B7-76E97B70318A}" 8 | EndProject 9 | Global 10 | GlobalSection(SolutionConfigurationPlatforms) = preSolution 11 | Debug|Any CPU = Debug|Any CPU 12 | Release|Any CPU = Release|Any CPU 13 | EndGlobalSection 14 | GlobalSection(ProjectConfigurationPlatforms) = postSolution 15 | {D122C609-5EB0-425A-AD35-D0CA0594676A}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 16 | {D122C609-5EB0-425A-AD35-D0CA0594676A}.Debug|Any CPU.Build.0 = Debug|Any CPU 17 | {D122C609-5EB0-425A-AD35-D0CA0594676A}.Release|Any CPU.ActiveCfg = Release|Any CPU 18 | {D122C609-5EB0-425A-AD35-D0CA0594676A}.Release|Any CPU.Build.0 = Release|Any CPU 19 | {0B766ECF-3361-4881-A86C-D7A7FA3F8464}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 20 | {0B766ECF-3361-4881-A86C-D7A7FA3F8464}.Debug|Any CPU.Build.0 = Debug|Any CPU 21 | {0B766ECF-3361-4881-A86C-D7A7FA3F8464}.Release|Any CPU.ActiveCfg = Release|Any CPU 22 | {0B766ECF-3361-4881-A86C-D7A7FA3F8464}.Release|Any CPU.Build.0 = Release|Any CPU 23 | {A20F5A28-EECD-467F-B5B7-76E97B70318A}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 24 | {A20F5A28-EECD-467F-B5B7-76E97B70318A}.Debug|Any CPU.Build.0 = Debug|Any CPU 25 | {A20F5A28-EECD-467F-B5B7-76E97B70318A}.Release|Any CPU.ActiveCfg = Release|Any CPU 26 | {A20F5A28-EECD-467F-B5B7-76E97B70318A}.Release|Any CPU.Build.0 = Release|Any CPU 27 | EndGlobalSection 28 | GlobalSection(NestedProjects) = preSolution 29 | EndGlobalSection 30 | EndGlobal 31 | -------------------------------------------------------------------------------- /ServerTest/System/ChatSystem.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Concurrent; 2 | using UpUpServer; 3 | 4 | /// 5 | /// 聊天系统 6 | /// 7 | [AutoSystem] 8 | public class ChatSystem: ISystem 9 | { 10 | private ConcurrentQueue _chatMessages; // 聊天消息缓存 11 | private bool isUpdating = false; 12 | public void Init() 13 | { 14 | Log.Info("ChatSystem init"); 15 | _chatMessages = new ConcurrentQueue(); 16 | } 17 | 18 | public void Update() 19 | { 20 | if (isUpdating) 21 | { 22 | return; 23 | } 24 | 25 | if (_chatMessages.Count<=0) 26 | { 27 | return; 28 | } 29 | isUpdating = true; 30 | List chatList = new List(); 31 | List chatRequests = new List(); 32 | 33 | // 聊天消息合并 34 | while (_chatMessages.TryDequeue(out var chatRequest)) 35 | { 36 | chatRequests.Add(chatRequest); 37 | var chatMessage = (chatRequest.Req as ChatRequest)?.Message; 38 | if (chatMessage != null) 39 | { 40 | chatList.Add(chatMessage); 41 | Log.Warning($"{chatMessage.UserName} say: {chatMessage.Content}"); 42 | } 43 | } 44 | 45 | var res = new ChatResponse(); 46 | res.Messages = chatList; 47 | // 通知所有客户端 48 | Program.Server.ConnManager.Connections.Keys.ToList().ForEach(connKey => 49 | { 50 | var conn = Program.Server.ConnManager.Connections[connKey]; 51 | Request request = new Request(conn, null); 52 | request.Res = res; 53 | Program.Server.MsgManager.AddPackResponse(request, ProtoDefine.ChatRes); 54 | }); 55 | 56 | isUpdating = false; 57 | } 58 | 59 | public void Dispose() 60 | { 61 | } 62 | 63 | public void AddChatRequest(IRequest request) 64 | { 65 | _chatMessages.Enqueue(request); 66 | } 67 | 68 | } -------------------------------------------------------------------------------- /ClientTest/UpPackSerialize.cs: -------------------------------------------------------------------------------- 1 | using System.Buffers; 2 | using System.Runtime.CompilerServices; 3 | 4 | public class UpPackSerialize : SingleClass 5 | { 6 | public UpPackSerialize() 7 | { 8 | } 9 | 10 | public uint GetHeadLen() 11 | { 12 | // len + msgid + data 13 | return 8; 14 | } 15 | 16 | public void Serialize(UpNetPackage msg, ref Memory data) 17 | { 18 | Span spanData = data.Span; 19 | Unsafe.As(ref spanData[0]) = msg.AllLength; 20 | Unsafe.As(ref spanData[4]) = msg.MsgId; 21 | // BitConverter.TryWriteBytes(data, msg.AllLength); 22 | // BitConverter.TryWriteBytes(data.Slice(4), msg.MsgId); 23 | msg.Data.CopyTo(spanData.Slice(8)); 24 | } 25 | 26 | public UpNetPackage? Deserialize(ReadOnlySequence data) 27 | { 28 | var headLen = GetHeadLen(); 29 | 30 | if (data.Length < headLen) 31 | { 32 | Console.WriteLine($"data head length is not enough, need {headLen}, but {data.Length}"); 33 | return null; 34 | } 35 | 36 | Span firstFourBytes = stackalloc byte[4]; // len 37 | Span secondFourBytes = stackalloc byte[4]; // msgid 38 | data.Slice(data.Start, 4).CopyTo(firstFourBytes); 39 | data.Slice(4, 4).CopyTo(secondFourBytes); 40 | uint msgLen; 41 | uint msgId; 42 | msgLen = Unsafe.ReadUnaligned(ref firstFourBytes[0]); 43 | msgId = Unsafe.ReadUnaligned(ref secondFourBytes[0]); 44 | // BitConverter.ToUInt32(firstFourBytes); 45 | 46 | if (data.Length < msgLen) 47 | { 48 | Console.WriteLine($"data length is not enough, need {msgLen}, but {data.Length}"); 49 | return null; 50 | } 51 | 52 | var msgBytes = data.Slice(headLen, (int)msgLen - headLen); 53 | 54 | var msg = new UpNetPackage(); 55 | msg.AllLength = msgLen; 56 | msg.MsgId = msgId; 57 | msg.Data = msgBytes; 58 | 59 | return msg; 60 | } 61 | } -------------------------------------------------------------------------------- /UpUpServer/Core/CoreImpl/JsonRpcRouter.cs: -------------------------------------------------------------------------------- 1 | using System.Buffers; 2 | 3 | namespace UpUpServer; 4 | 5 | using Newtonsoft.Json; 6 | using System.Text; 7 | 8 | public abstract class JsonRpcRouter : IRouter 9 | where T1 : class, IProtocol 10 | where T2 : class, IProtocol, new() 11 | { 12 | public int ErrorCode { get; set; } 13 | public IMsgManager MsgManager { get; set; } = null!; // 添加路由时赋值 14 | public virtual void PreHandle(IRequest request) 15 | { 16 | ErrorCode = 0; 17 | try 18 | { 19 | var rawData = Encoding.UTF8.GetString(request.NetPackage.Data); 20 | request.Req = JsonConvert.DeserializeObject(rawData) as T1; 21 | if (request.Req is null) 22 | { 23 | Log.Error($"JsonRpcRouter.PreHandle: {rawData} is not {typeof(T1).Name}"); 24 | ErrorCode = 1; 25 | return; 26 | } 27 | } 28 | catch (Exception e) 29 | { 30 | Log.Error($"net data paser error exception={e}"); 31 | ErrorCode = 1; 32 | return; 33 | } 34 | 35 | request.Res = new T2(); 36 | } 37 | 38 | public virtual Task Handle(IRequest request) 39 | { 40 | return Task.CompletedTask; 41 | } 42 | 43 | public virtual void PostHandle(IRequest request) 44 | { 45 | if (ErrorCode != 0 || request.Res is NoResponse) 46 | { 47 | return; 48 | } 49 | var res = request.Res as T2; 50 | if (res is null) 51 | { 52 | Log.Error($"JsonRpcRouter.PostHandle: {request.Res} is not {typeof(T2).Name}"); 53 | return; 54 | } 55 | var resData = Encoding.UTF8.GetBytes(JsonConvert.SerializeObject(res)); 56 | request.ResNetPackage = new UpNetPackage 57 | { 58 | MsgId = request.NetPackage.MsgId+1, 59 | Data = new ReadOnlySequence(resData), 60 | }; 61 | request.ResNetPackage.AllLength = (uint)resData.Length+4+4; 62 | MsgManager.AddResponse(request); 63 | } 64 | } -------------------------------------------------------------------------------- /UpUpServer/Core/CoreImpl/UpPackSerialize.cs: -------------------------------------------------------------------------------- 1 | namespace UpUpServer; 2 | 3 | using System.Buffers; 4 | using System.Runtime.CompilerServices; 5 | 6 | public class UpPackSerialize : SingleClass, IPackSerialize 7 | { 8 | public UpPackSerialize() 9 | { 10 | } 11 | 12 | public uint GetHeadLen() 13 | { 14 | // len + msgid + data 15 | return 8; 16 | } 17 | 18 | public void Serialize(INetPackage msg, ref Memory data) 19 | { 20 | Span spanData = data.Span; 21 | Unsafe.As(ref spanData[0]) = msg.AllLength; 22 | Unsafe.As(ref spanData[4]) = msg.MsgId; 23 | // BitConverter.TryWriteBytes(data, msg.AllLength); 24 | // BitConverter.TryWriteBytes(data.Slice(4), msg.MsgId); 25 | msg.Data.CopyTo(spanData.Slice(8)); 26 | } 27 | 28 | public INetPackage Deserialize(ReadOnlySequence data) 29 | { 30 | var headLen = GetHeadLen(); 31 | 32 | if (data.Length < headLen) 33 | { 34 | Log.Error($"data head length is not enough, need {headLen}, but {data.Length}"); 35 | return null; 36 | } 37 | 38 | Span firstFourBytes = stackalloc byte[4]; // len 39 | Span secondFourBytes = stackalloc byte[4]; // msgid 40 | data.Slice(data.Start, 4).CopyTo(firstFourBytes); 41 | data.Slice(4, 4).CopyTo(secondFourBytes); 42 | uint msgLen; 43 | uint msgId; 44 | msgLen = Unsafe.ReadUnaligned(ref firstFourBytes[0]); 45 | msgId = Unsafe.ReadUnaligned(ref secondFourBytes[0]); 46 | // BitConverter.ToUInt32(firstFourBytes); 47 | 48 | if (data.Length < msgLen) 49 | { 50 | Log.Error($"data length is not enough, need {msgLen}, but {data.Length}"); 51 | return null; 52 | } 53 | 54 | var msgBytes = data.Slice(headLen, (int)msgLen - headLen); 55 | 56 | var msg = new UpNetPackage(); 57 | msg.AllLength = msgLen; 58 | msg.MsgId = msgId; 59 | msg.Data = msgBytes; 60 | 61 | return msg; 62 | } 63 | } -------------------------------------------------------------------------------- /UpUpServer/Core/Log/NLogger.cs: -------------------------------------------------------------------------------- 1 | using NLog; 2 | using NLog.Targets; 3 | 4 | namespace UpUpServer; 5 | 6 | public class NLogger : ILog 7 | { 8 | private NLog.Logger logger; 9 | 10 | public void Init() 11 | { 12 | var logPath = ConfigManager.Instance.ServerConfig?.NLogConfigPath ?? @"Conf/nlog.config"; 13 | LogManager.Setup().LoadConfigurationFromFile(logPath); 14 | 15 | // 配置文件变更时生效 16 | LogManager.ConfigurationChanged += (sender, args) => 17 | { 18 | if (args.ActivatedConfiguration != null) 19 | { 20 | //Re apply if config reloaded 21 | UpdateConfig(); 22 | } 23 | }; 24 | this.logger = LogManager.GetCurrentClassLogger(); 25 | } 26 | 27 | public void UpdateConfig() 28 | { 29 | // Do NOT set LogManager.Configuration because that will cause recursion and stackoverflow 30 | // var fileTarget = LogManager.Configuration.FindTargetByName("myTargetName"); 31 | // fileTarget.FileName = "${basedir}/file.log"; 32 | LogManager.ReconfigExistingLoggers(); // Soft refresh 33 | } 34 | 35 | 36 | 37 | public void Trace(string message, string tag = "") 38 | { 39 | this.logger.Trace($"{tag} {message}"); 40 | } 41 | 42 | public void Info(string message, string tag = "") 43 | { 44 | this.logger.Info($"{tag} {message}"); 45 | } 46 | 47 | public void Warning(string message, string tag = "") 48 | { 49 | this.logger.Warn($"{tag} {message}"); 50 | } 51 | 52 | public void Error(string message, string tag = "") 53 | { 54 | this.logger.Error($"{tag} {message}"); 55 | } 56 | 57 | public void Trace(string message, params object[] args) 58 | { 59 | this.logger.Trace(message, args); 60 | } 61 | 62 | public void Info(string message, params object[] args) 63 | { 64 | this.logger.Info(message, args); 65 | } 66 | 67 | public void Warning(string message, params object[] args) 68 | { 69 | this.logger.Warn(message, args); 70 | } 71 | 72 | public void Error(string message, params object[] args) 73 | { 74 | this.logger.Error(message, args); 75 | } 76 | 77 | public void Flush() 78 | { 79 | } 80 | } -------------------------------------------------------------------------------- /ServerTest/RpcHandle/LoginRouter.cs: -------------------------------------------------------------------------------- 1 | using MongoDB.Driver; 2 | using ServerTest.Const; 3 | using ServiceStack.Redis; 4 | using UpUpServer; 5 | 6 | 7 | [AutoRouter(ProtoDefine.LoginReq)] 8 | public class LoginRouter: JsonRpcRouter 9 | { 10 | private IMongoCollection? _collection; 11 | private IRedisClient? _redisClient; 12 | 13 | public override void PreHandle(IRequest request) 14 | { 15 | base.PreHandle(request); 16 | if (this._collection is null) 17 | { 18 | var mongoDb = MongoDBProxy.Instance.CurDB; 19 | _collection = mongoDb.GetCollection("User"); 20 | } 21 | 22 | // if (_redisClient is null) 23 | // { 24 | // _redisClient = RedisDBProxy.Instance.RedisPool.GetClient(); 25 | // _redisClient.Db = 0; 26 | // } 27 | } 28 | public override async Task Handle(IRequest request) 29 | { 30 | await base.Handle(request); 31 | var req = (LoginRequest)request.Req!; 32 | var res = (LoginResponse)request.Res; 33 | 34 | request.Connection.SetUserId(req.UserId); 35 | 36 | res.Data = ""; 37 | 38 | var data = await _collection.FindAsync(x => x.Id == request.Connection.UserId); 39 | var user = data.FirstOrDefault(); 40 | if (user == null) 41 | { 42 | user = new User() 43 | { 44 | Id = request.Connection.UserId, 45 | Uid = (ulong)(Random.Shared.NextInt64()), 46 | Password = req.Password, 47 | Data = "", 48 | DataVersion = 0, 49 | }; 50 | if (_collection != null) await _collection.InsertOneAsync(user); 51 | res.Status = ResponseDefine.LoginNewUser; 52 | res.Data = ""; 53 | res.Uid = user.Uid; 54 | res.DataVersion = 0; 55 | Log.Warning($"new user userId={request.Connection.UserId}"); 56 | } 57 | else 58 | { 59 | res.Status = ResponseDefine.Success; 60 | res.Data = user.Data; 61 | res.Uid = user.Uid; 62 | res.DataVersion = user.DataVersion; 63 | Log.Info($"user login userId={request.Connection.UserId}"); 64 | } 65 | } 66 | } -------------------------------------------------------------------------------- /ClientTest/TcpClient.cs: -------------------------------------------------------------------------------- 1 | using System.Buffers; 2 | using System.Text; 3 | 4 | public class TcpClient 5 | { 6 | public MsgManager MsgManager { get; set; } 7 | public Connection Connection { get; set; } 8 | public string Ip { get; set; } 9 | public int Port { get; set; } 10 | 11 | 12 | public TcpClient(string ip, int port) 13 | { 14 | Ip = ip; 15 | Port = port; 16 | Connection = new Connection(Ip, Port); 17 | MsgManager = new MsgManager(); 18 | } 19 | 20 | public void Start() 21 | { 22 | Connection.Connect(); 23 | Connection.OnReceived += HandleMsg; 24 | MsgManager.StartWorkPool(); 25 | } 26 | 27 | private void HandleMsg(ReadOnlySequence obj) 28 | { 29 | var netPackage = UpPackSerialize.Instance.Deserialize(obj); 30 | if (netPackage is null) 31 | { 32 | return; 33 | } 34 | MsgManager.AddResponse(netPackage); 35 | } 36 | 37 | public void SetOnConnected(Action action) 38 | { 39 | Connection.OnConnected += action; 40 | } 41 | 42 | public void SetOnReceived(Action> action) 43 | { 44 | Connection.OnReceived += action; 45 | } 46 | 47 | public void SetOnClose(Action action) 48 | { 49 | Connection.OnClose += action; 50 | } 51 | 52 | public void Send(uint msgId, T data) 53 | { 54 | var input = Newtonsoft.Json.JsonConvert.SerializeObject(data); 55 | 56 | var netPack = new UpNetPackage(); 57 | netPack.Data = new ReadOnlySequence(Encoding.UTF8.GetBytes(input)); 58 | netPack.MsgId = msgId; 59 | netPack.AllLength = (uint)netPack.Data.Length + 4 + 4; 60 | 61 | Memory bytesData = new byte[netPack.AllLength]; 62 | UpPackSerialize.Instance.Serialize(netPack, ref bytesData); 63 | 64 | Connection.Send(bytesData.Span); 65 | } 66 | 67 | public void Send(Span data) 68 | { 69 | Connection.Send(data); 70 | } 71 | 72 | public async Task SendAsync(Memory data) 73 | { 74 | await Connection.SendAsync(data); 75 | } 76 | 77 | public void AddListener(uint msgId, Action action) 78 | { 79 | MsgManager.RegisterRouter(msgId, action); 80 | } 81 | 82 | public void Close() 83 | { 84 | Connection.Close(); 85 | } 86 | } -------------------------------------------------------------------------------- /UpUpServer/Core/CoreImpl/TimerManager/LoopTimer.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | 3 | namespace UpUpServer 4 | { 5 | public class LoopTimer : Timer 6 | { 7 | /// 8 | /// Parameter is loopTime. 9 | /// 10 | protected Action _onComplete; 11 | public bool executeOnStart { protected set; get; } 12 | 13 | /// 14 | /// How many times does the LoopTimer looped. 15 | /// 16 | public int loopTimes { private set; get; } 17 | int _curLoopTimes { set; get; } 18 | private long _lastActionTime; // 上次执行时间 19 | 20 | 21 | protected virtual void OnComplete() 22 | { 23 | } 24 | 25 | public LoopTimer(bool isPersistence, long interval, Action onComplete, 26 | Action onUpdate, bool executeOnStart, object autoDestroyOwner) 27 | : base(isPersistence, interval, onUpdate, autoDestroyOwner) 28 | { 29 | _onComplete = onComplete; 30 | this.executeOnStart = executeOnStart; 31 | _curLoopTimes = 0; 32 | } 33 | 34 | public override void OnInit() 35 | { 36 | if (executeOnStart) 37 | Update(); 38 | _lastActionTime = GetWorldTime(); 39 | } 40 | 41 | public override void OnRestart() 42 | { 43 | loopTimes = 0; 44 | if (executeOnStart) 45 | Update(); 46 | } 47 | 48 | public override void Update() 49 | { 50 | if (!CheckUpdate()) return; 51 | 52 | bool needUpdate = GetWorldTime() - _lastActionTime >= duration; 53 | if (_onUpdate != null && needUpdate) 54 | { 55 | _curLoopTimes++; 56 | _lastActionTime = GetWorldTime(); 57 | SafeCall(_onUpdate, GetTimeElapsed()); 58 | } 59 | if (loopTimes>0 && _curLoopTimes >= loopTimes) 60 | { 61 | isCompleted = true; 62 | } 63 | } 64 | 65 | 66 | public void Restart(long newInterval) 67 | { 68 | duration = newInterval; 69 | Restart(); 70 | } 71 | 72 | public void Restart(long newInterval, Action newOnComplete, Action newOnUpdate, bool newExecuteOnStart) 73 | { 74 | duration = newInterval; 75 | _onComplete = newOnComplete; 76 | _onUpdate = newOnUpdate; 77 | executeOnStart = newExecuteOnStart; 78 | Restart(); 79 | } 80 | } 81 | } -------------------------------------------------------------------------------- /UpUpServer/Core/Log/Logger.cs: -------------------------------------------------------------------------------- 1 | namespace UpUpServer; 2 | 3 | using System; 4 | using System.Diagnostics; 5 | 6 | public class Logger 7 | { 8 | public static Logger Instance = new Logger(); 9 | 10 | private ILog iLog = new ConsoleLogger(); 11 | 12 | public ILog ILog 13 | { 14 | set 15 | { 16 | this.iLog = value; 17 | this.iLog.Init(); 18 | } 19 | } 20 | 21 | private int logLevel = LogLevel.Info; 22 | 23 | public Logger() 24 | { 25 | this.iLog.Init(); 26 | } 27 | 28 | private bool CheckLogLevel(int level) 29 | { 30 | return logLevel <= level; 31 | } 32 | 33 | public void Trace(string msg, string tag = "") 34 | { 35 | if (!CheckLogLevel(LogLevel.Trace)) 36 | { 37 | return; 38 | } 39 | 40 | StackTrace st = new StackTrace(2, true); 41 | this.iLog.Trace($"{msg}\n{st}", tag); 42 | } 43 | 44 | public void Info(string msg, string tag = "") 45 | { 46 | if (!CheckLogLevel(LogLevel.Info)) 47 | { 48 | return; 49 | } 50 | 51 | this.iLog.Info(msg, tag); 52 | } 53 | 54 | public void Warning(string msg, string tag = "") 55 | { 56 | if (!CheckLogLevel(LogLevel.Warning)) 57 | { 58 | return; 59 | } 60 | 61 | this.iLog.Warning(msg, tag); 62 | } 63 | 64 | public void Error(string msg, string tag = "") 65 | { 66 | // StackTrace st = new StackTrace(2, true); 67 | // this.iLog.Error($"{tag} {msg}\n{st}"); 68 | this.iLog.Error(msg, tag); 69 | } 70 | 71 | public void Error(Exception e) 72 | { 73 | if (e.Data.Contains("StackTrace")) 74 | { 75 | this.iLog.Error($"{e.Data["StackTrace"]}\n{e}"); 76 | return; 77 | } 78 | 79 | string str = e.ToString(); 80 | this.iLog.Error(str); 81 | } 82 | 83 | public void Trace(string message, params object[] args) 84 | { 85 | if (!CheckLogLevel(LogLevel.Trace)) 86 | { 87 | return; 88 | } 89 | 90 | StackTrace st = new StackTrace(2, true); 91 | this.iLog.Trace($"{string.Format(message, args)}\n{st}"); 92 | } 93 | 94 | public void Warning(string message, params object[] args) 95 | { 96 | if (!CheckLogLevel(LogLevel.Warning)) 97 | { 98 | return; 99 | } 100 | 101 | this.iLog.Warning(string.Format(message, args)); 102 | } 103 | 104 | public void Info(string message, params object[] args) 105 | { 106 | if (!CheckLogLevel(LogLevel.Info)) 107 | { 108 | return; 109 | } 110 | 111 | this.iLog.Info(string.Format(message, args)); 112 | } 113 | 114 | public void Error(string message, params object[] args) 115 | { 116 | StackTrace st = new StackTrace(2, true); 117 | string s = string.Format(message, args) + '\n' + st; 118 | this.iLog.Error(s); 119 | } 120 | 121 | public void Flush() 122 | { 123 | this.iLog.Flush(); 124 | } 125 | } -------------------------------------------------------------------------------- /UpUpServer/Utils/ObjectExtension.cs: -------------------------------------------------------------------------------- 1 | 2 | using System.Reflection; 3 | using Newtonsoft.Json; 4 | 5 | namespace UpUpServer 6 | { 7 | public static class ObjectExtension 8 | { 9 | public static T DeepCloneByJson(this T source) where T : new() 10 | { 11 | if (source == null) 12 | { 13 | return default(T); 14 | } 15 | string json = JsonConvert.SerializeObject(source); 16 | T destination = JsonConvert.DeserializeObject(json); 17 | return destination; 18 | } 19 | 20 | public static T DeepCloneByReflection(this T source) where T : new() 21 | { 22 | if (source == null) 23 | { 24 | return default(T); 25 | } 26 | var destination = (T) DeepCopy(source); 27 | return destination; 28 | } 29 | 30 | // public static object DeepCopy(object obj, ISet excludeName = null) 31 | public static object DeepCopy(object obj, ISet excludeTypes = null) 32 | { 33 | if (obj == null) 34 | { 35 | return null; 36 | } 37 | 38 | Type type = obj.GetType(); 39 | 40 | // int bool char 41 | if (type.IsPrimitive || type == typeof(string)) 42 | { 43 | return obj; 44 | } 45 | 46 | if (excludeTypes != null && excludeTypes.Contains(type)) 47 | { 48 | return obj; 49 | } 50 | 51 | if (type.IsArray) 52 | { 53 | Type elementType = Type.GetType( 54 | type.FullName.Replace("[]", string.Empty)); 55 | var array = obj as Array; 56 | Array copiedArray = Array.CreateInstance(elementType, array.Length); 57 | for (int i = 0; i < array.Length; i++) 58 | { 59 | copiedArray.SetValue(DeepCopy(array.GetValue(i), excludeTypes), i); 60 | } 61 | return Convert.ChangeType(copiedArray, obj.GetType()); 62 | } 63 | 64 | object instance = Activator.CreateInstance(obj.GetType()); 65 | foreach (var field in type.GetFields(BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance)) 66 | { 67 | object fieldValue = field.GetValue(obj); 68 | if (fieldValue == null) 69 | { 70 | continue; 71 | } 72 | field.SetValue(instance, DeepCopy(fieldValue, excludeTypes)); 73 | } 74 | 75 | foreach (var property in type.GetProperties(BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance).Where(p => p.CanRead && p.CanWrite && p.GetIndexParameters().Length == 0)) 76 | { 77 | try 78 | { 79 | object propertyValue = property.GetValue(obj, null); 80 | property.SetValue(instance, DeepCopy(propertyValue, excludeTypes), null); 81 | } 82 | catch 83 | { 84 | Log.Warning("Can't copy property: " + property.Name); 85 | } 86 | } 87 | 88 | return instance; 89 | } 90 | } 91 | } -------------------------------------------------------------------------------- /UpUpServer/UnitTest/ServerTest.cs: -------------------------------------------------------------------------------- 1 | using MongoDB.Bson; 2 | using MongoDB.Driver; 3 | using Newtonsoft.Json; 4 | using ServiceStack.Redis; 5 | using static System.Console; 6 | 7 | namespace UpUpServer; 8 | 9 | public class ServerTest 10 | { 11 | public class Role 12 | { 13 | public int Id; 14 | public string Name; 15 | public Dictionary Childs = new Dictionary(); 16 | } 17 | 18 | public class Player 19 | { 20 | public ulong Uid; 21 | public string UserId; 22 | public ulong DataVersion; 23 | public List RoleProfessionIds = new List(); 24 | public Dictionary Roles = new Dictionary(); 25 | public Role DataRole = new Role(); 26 | 27 | } 28 | 29 | public static void RunServerTest() 30 | { 31 | WriteLine("ServerTest"); 32 | Server server = new Server(); 33 | server.Start(); 34 | 35 | ReadKey(); 36 | } 37 | 38 | public static void TestEndian() 39 | { 40 | var a = 0x12345678; 41 | var b = BitConverter.GetBytes(a); 42 | var c = BitConverter.ToInt32(b); 43 | WriteLine($"a:{a}, b:{b}, c:{c}"); 44 | 45 | if (BitConverter.IsLittleEndian) 46 | { 47 | WriteLine("当前系统是小端序"); 48 | } 49 | else 50 | { 51 | WriteLine("当前系统是大端序"); 52 | } 53 | } 54 | 55 | public static void TestMongo() 56 | { 57 | var mongo = new MongoDBProxy(); 58 | mongo.Open("mongodb://localhost:27017", "GameTest"); 59 | var userdb = mongo.CurDB.ListCollectionNames(); 60 | userdb.MoveNext(); 61 | var l =userdb.Current.ToList(); 62 | foreach (var a in l) 63 | { 64 | Log.Info(a); 65 | } 66 | 67 | try 68 | { 69 | var user = mongo.CurDB.GetCollection("User"); 70 | // 取出所有数据 71 | var users = user.AsQueryable().ToList(); 72 | foreach (var u in users) 73 | { 74 | Log.Info(u.ToString()); 75 | } 76 | } 77 | catch (Exception e) 78 | { 79 | WriteLine(e); 80 | throw; 81 | } 82 | 83 | // mongo.CurDB.CreateCollection("GameUser"); 84 | var gameUser = mongo.CurDB.GetCollection("GameUser"); 85 | gameUser.AsQueryable().ToList().ForEach(u => Log.Info($"name:{u.Name} level:{u.Level} {u.ToString()}")); 86 | } 87 | 88 | class GameUser 89 | { 90 | public string Name; 91 | public int Level; 92 | } 93 | 94 | public static void TestRedis() 95 | { 96 | RedisClient redisClient = new RedisClient("127.0.0.1", 6379);//redis服务IP和端口 97 | var res = redisClient.HGetAll("testkey"); 98 | Console.WriteLine(res); 99 | 100 | 101 | var redisManager = new RedisManagerPool("127.0.0.1:6379"); 102 | using (var client = redisManager.GetClient()) 103 | { 104 | var res2 = client.GetAllEntriesFromHash("testkey"); 105 | foreach (var r in res2) 106 | { 107 | Log.Info($"key:{r.Key} value:{r.Value}"); 108 | } 109 | } 110 | 111 | } 112 | 113 | } -------------------------------------------------------------------------------- /ClientTest/MsgManager.cs: -------------------------------------------------------------------------------- 1 | using System.Buffers; 2 | using System.Threading.Channels; 3 | 4 | public class MsgManager 5 | { 6 | public Dictionary> Routers { get; } = new Dictionary>(); 7 | public Channel[] RequestChannel { get; set; } 8 | public Channel[] ResponseChannel { get; set; } 9 | public TcpClient Client { get; set; } 10 | 11 | public int WorkPoolSize = 2; 12 | 13 | public void StartWorkPool() 14 | { 15 | RequestChannel = new Channel[WorkPoolSize]; 16 | for (int i = 0; i < WorkPoolSize; i++) 17 | { 18 | RequestChannel[i] = Channel.CreateUnbounded(); 19 | var i1 = i; 20 | Task.Run(() => ReadWorker(i1)); 21 | } 22 | 23 | ResponseChannel = new Channel[WorkPoolSize]; 24 | for (int i = 0; i < WorkPoolSize; i++) 25 | { 26 | ResponseChannel[i] = Channel.CreateUnbounded(); 27 | var i1 = i; 28 | Task.Run(() => WriteWorker(i1)); 29 | } 30 | } 31 | 32 | private async void ReadWorker(int index) 33 | { 34 | Console.WriteLine($"Start ReadWorker {index}"); 35 | var reader = ResponseChannel[index].Reader; 36 | while (true) 37 | { 38 | var request = await reader.ReadAsync(); 39 | HandleMsg(request.MsgId, request); 40 | } 41 | } 42 | 43 | private async void WriteWorker(int index) 44 | { 45 | Console.WriteLine($"Start WriteWorker {index}"); 46 | var reader = RequestChannel[index].Reader; 47 | while (true) 48 | { 49 | var netPack = await reader.ReadAsync(); 50 | var data = ArrayPool.Shared.Rent((int)netPack.AllLength); 51 | Memory memory = new Memory(data); 52 | UpPackSerialize.Instance.Serialize(netPack, ref memory); 53 | await Client.SendAsync(memory.Slice(0, (int)netPack.AllLength)); 54 | ArrayPool.Shared.Return(data); 55 | } 56 | } 57 | 58 | public void AddRequest(UpNetPackage request) 59 | { 60 | var msgId = request.MsgId; 61 | var index = msgId % WorkPoolSize; 62 | RequestChannel[index].Writer.TryWrite(request); 63 | } 64 | 65 | public void AddResponse(UpNetPackage request) 66 | { 67 | var msgId = request.MsgId; 68 | var index = msgId % WorkPoolSize; 69 | ResponseChannel[index].Writer.TryWrite(request); 70 | } 71 | 72 | public void RegisterRouter(uint msgId, Action router) 73 | { 74 | if (Routers.TryGetValue(msgId, out var r)) 75 | { 76 | Console.WriteLine($"router msgId:{msgId} has been registered"); 77 | } 78 | else 79 | { 80 | Routers.Add(msgId, router); 81 | } 82 | } 83 | 84 | public void UnRegisterRouter(uint msgId) 85 | { 86 | Routers.Remove(msgId); 87 | } 88 | 89 | public void HandleMsg(uint msgId, UpNetPackage request) 90 | { 91 | if (Routers.TryGetValue(msgId, out var router)) 92 | { 93 | // router.Invoke(request); 94 | MainThreadSynchronizationContext.Instance.Post((_) => 95 | { 96 | router.Invoke(request); 97 | }); 98 | } 99 | else 100 | { 101 | Console.WriteLine($"router msgId:{msgId} not found"); 102 | } 103 | } 104 | } -------------------------------------------------------------------------------- /ClientTest/Program.cs: -------------------------------------------------------------------------------- 1 | using System.Buffers; 2 | using System.Text; 3 | using Newtonsoft.Json; 4 | using ServiceStack; 5 | using UpUpServer; 6 | 7 | string ip = "127.0.0.1"; 8 | // string ip = "101.42.178.138"; 9 | int port = 9999; 10 | var tcpClient = new TcpClient(ip, port); 11 | string userId = "1"; 12 | 13 | 14 | // tcpClient.SetOnReceived(data => 15 | // { 16 | // Console.WriteLine("客户端收到了服务端的全部消息: {0}", Encoding.UTF8.GetString(data)); 17 | // }); 18 | 19 | // 其他线程的消息回调,需要设置主线程的同步上下文 20 | SynchronizationContext.SetSynchronizationContext(MainThreadSynchronizationContext.Instance); 21 | 22 | 23 | Task.Run(() => 24 | { 25 | while (true) 26 | { 27 | MainThreadSynchronizationContext.Instance.Update(); 28 | Thread.Sleep(10); 29 | } 30 | }); 31 | 32 | Connect(); 33 | 34 | PrintTips(); 35 | 36 | while (true) 37 | { 38 | var input = Console.ReadLine(); 39 | if (input == "0") 40 | { 41 | break; 42 | } 43 | DisptachInput(input); 44 | Thread.Sleep(2); 45 | } 46 | 47 | void Connect() 48 | { 49 | tcpClient.SetOnConnected(() => 50 | { 51 | Console.WriteLine("客户端连上了服务端"); 52 | }); 53 | tcpClient.SetOnClose(reason => 54 | { 55 | Console.WriteLine("客户端断开了服务端: {0}", reason); 56 | }); 57 | AddListener(); 58 | tcpClient.Start(); 59 | 60 | } 61 | 62 | void AddListener() 63 | { 64 | tcpClient.AddListener(2, data => 65 | { 66 | // 打印当前线程id 67 | Console.WriteLine($"线程:{Thread.CurrentThread.ManagedThreadId} 客户端处理服务端的消息: {Encoding.UTF8.GetString(data.Data)}"); 68 | }); 69 | tcpClient.AddListener(ProtoDefine.LoginRes, data => 70 | { 71 | // 打印当前线程id 72 | Console.WriteLine($"线程:{Thread.CurrentThread.ManagedThreadId} 客户端处理服务端的消息: {Encoding.UTF8.GetString(data.Data)}"); 73 | }); 74 | 75 | tcpClient.AddListener(ProtoDefine.ChatRes, data => 76 | { 77 | var rawData = Encoding.UTF8.GetString(data.Data); 78 | var chatRes = JsonConvert.DeserializeObject(rawData); 79 | foreach (var msg in chatRes.Messages) 80 | { 81 | Console.WriteLine($"【{msg.UserName}】: {msg.Content}"); 82 | } 83 | }); 84 | tcpClient.AddListener(ProtoDefine.GetOnlineInfoRes, data => 85 | { 86 | Console.WriteLine($"线程:{Thread.CurrentThread.ManagedThreadId}【在线玩家信息】 {Encoding.UTF8.GetString(data.Data)}"); 87 | }); 88 | } 89 | 90 | void PrintTips() 91 | { 92 | Console.WriteLine("请输入操作:"); 93 | Console.WriteLine("1. 登陆"); 94 | Console.WriteLine("3. 发送消息"); 95 | Console.WriteLine("13. 获取在线玩家数量"); 96 | Console.WriteLine("0. 退出"); 97 | } 98 | 99 | void DisptachInput(string input) 100 | { 101 | switch (input) 102 | { 103 | case "1": 104 | handle1(); 105 | break; 106 | case "3": 107 | handle3(); 108 | break; 109 | case "13": 110 | handle13(); 111 | break; 112 | default: 113 | Console.WriteLine("ping"); 114 | input = string.Format("{{\"msg\":\"{0}\"}}", input); 115 | SendMsg(1, input); 116 | break; 117 | } 118 | } 119 | 120 | void SendMsg(uint msgId, string input) 121 | { 122 | var netPack = new UpNetPackage(); 123 | netPack.Data = new ReadOnlySequence(Encoding.UTF8.GetBytes(input)); 124 | netPack.MsgId = msgId; 125 | netPack.AllLength = (uint)netPack.Data.Length + 4 + 4; 126 | 127 | Memory data = new byte[netPack.AllLength]; 128 | UpPackSerialize.Instance.Serialize(netPack, ref data); 129 | tcpClient.Connection.Send(data.Span); 130 | } 131 | 132 | void handle1() 133 | { 134 | Console.WriteLine("请输入登陆用户id"); 135 | var input = Console.ReadLine(); 136 | userId = input; 137 | LoginRequest user = new LoginRequest(); 138 | user.UserId = input; 139 | // user.UserName = input+"昵称"; 140 | user.Password = "111"; 141 | input = JsonConvert.SerializeObject(user); 142 | SendMsg(ProtoDefine.LoginReq, input); 143 | } 144 | 145 | void handle3() 146 | { 147 | Console.WriteLine("请输入聊天内容"); 148 | var input = Console.ReadLine(); 149 | ChatRequest chatReq = new ChatRequest(); 150 | chatReq.Message = new ChatMessage(); 151 | chatReq.Message.UserName = userId; 152 | chatReq.Message.Content = input; 153 | chatReq.Message.MsgType = 1; 154 | input = JsonConvert.SerializeObject(chatReq); 155 | SendMsg(ProtoDefine.ChatReq, input); 156 | } 157 | 158 | void handle13() 159 | { 160 | GetOnlineInfoRequest req = new GetOnlineInfoRequest(); 161 | var input = JsonConvert.SerializeObject(req); 162 | SendMsg(ProtoDefine.GetOnlineInfoReq, input); 163 | } 164 | -------------------------------------------------------------------------------- /UpUpServer/Core/CoreImpl/Manager/MsgManager.cs: -------------------------------------------------------------------------------- 1 | using System.Buffers; 2 | using System.Text; 3 | using System.Threading.Channels; 4 | using Newtonsoft.Json; 5 | 6 | namespace UpUpServer; 7 | 8 | public class MsgManager : IMsgManager 9 | { 10 | public Dictionary Routers { get; } = new Dictionary(); 11 | 12 | public Channel[] RequestChannel { get; set; } 13 | 14 | public Channel[] ResponseChannel { get; set; } 15 | 16 | public int WorkPoolSize = 4; 17 | 18 | public IServer Server { get; set; } 19 | 20 | public void StartWorkPool(IServer server) 21 | { 22 | Server = server; 23 | if (ConfigManager.Instance.ServerConfig == null || ConfigManager.Instance.ServerConfig.WorkPoolSize <= 0) 24 | { 25 | Log.Warning($"WorkPoolSize is not set, use default value:{WorkPoolSize}"); 26 | } 27 | else 28 | { 29 | WorkPoolSize = ConfigManager.Instance.ServerConfig.WorkPoolSize; 30 | } 31 | 32 | RequestChannel = new Channel[WorkPoolSize]; 33 | for (int i = 0; i < WorkPoolSize; i++) 34 | { 35 | RequestChannel[i] = Channel.CreateUnbounded(); 36 | var i1 = i; 37 | Task.Run(() => ReadWorker(i1)); 38 | } 39 | 40 | ResponseChannel = new Channel[WorkPoolSize]; 41 | for (int i = 0; i < WorkPoolSize; i++) 42 | { 43 | ResponseChannel[i] = Channel.CreateUnbounded(); 44 | var i1 = i; 45 | Task.Run(() => WriteWorker(i1)); 46 | } 47 | } 48 | 49 | private async void ReadWorker(int index) 50 | { 51 | Log.Info($"Start ReadWorker {index}"); 52 | var reader = RequestChannel[index].Reader; 53 | while (Server.IsRunning) 54 | { 55 | var request = await reader.ReadAsync(); 56 | Log.Info($"receive msgId:{request.NetPackage.MsgId}"); 57 | await HandleMsg(request.NetPackage.MsgId, request); 58 | } 59 | } 60 | 61 | private async void WriteWorker(int index) 62 | { 63 | Log.Info($"Start WriteWorker {index}"); 64 | var reader = ResponseChannel[index].Reader; 65 | while (Server.IsRunning) 66 | { 67 | var request = await reader.ReadAsync(); 68 | var netPack = request.ResNetPackage; 69 | // Memory data = new byte[netPack.AllLength]; 70 | var data = ArrayPool.Shared.Rent((int)netPack.AllLength); 71 | var memory = new Memory(data); 72 | UpPackSerialize.Instance.Serialize(netPack, ref memory); 73 | await request.Connection.SendAsync(memory.Slice(0, (int)netPack.AllLength)); 74 | // request.Connection.Send(memory.Slice(0, (int)netPack.AllLength)); 75 | ArrayPool.Shared.Return(data); 76 | } 77 | } 78 | 79 | public void AddRequest(IRequest request) 80 | { 81 | var msgId = request.NetPackage.MsgId; 82 | var index = msgId % WorkPoolSize; 83 | RequestChannel[index].Writer.TryWrite(request); 84 | } 85 | 86 | public void AddResponse(IRequest request) 87 | { 88 | var msgId = request.NetPackage.MsgId; 89 | var index = msgId % WorkPoolSize; 90 | ResponseChannel[index].Writer.TryWrite(request); 91 | } 92 | 93 | public void AddPackResponse(IRequest request, uint msgId) 94 | { 95 | var resData = Encoding.UTF8.GetBytes(JsonConvert.SerializeObject(request.Res)); 96 | request.ResNetPackage = new UpNetPackage 97 | { 98 | MsgId = msgId, 99 | Data = new ReadOnlySequence(resData), 100 | }; 101 | request.ResNetPackage.AllLength = (uint)resData.Length+4+4; 102 | 103 | var index = msgId % WorkPoolSize; 104 | ResponseChannel[index].Writer.TryWrite(request); 105 | } 106 | 107 | public void RegisterRouter(uint msgId, IRouter router) 108 | { 109 | router.MsgManager = this; 110 | if (Routers.TryGetValue(msgId, out var r)) 111 | { 112 | Log.Warning($"router msgId:{msgId} has been registered"); 113 | } 114 | else 115 | { 116 | Routers.Add(msgId, router); 117 | } 118 | } 119 | 120 | public void UnRegisterRouter(uint msgId) 121 | { 122 | Routers.Remove(msgId); 123 | } 124 | 125 | public async Task HandleMsg(uint msgId, IRequest request) 126 | { 127 | if (Routers.TryGetValue(msgId, out var router)) 128 | { 129 | router.PreHandle(request); 130 | if (router.ErrorCode != 0) 131 | { 132 | Log.Error($"router msgId:{msgId} stop after prehandle, ErrorCode={router.ErrorCode}"); 133 | return; 134 | } 135 | await router.Handle(request); 136 | router.PostHandle(request); 137 | } 138 | else 139 | { 140 | Log.Error($"router msgId:{msgId} not found"); 141 | } 142 | } 143 | } -------------------------------------------------------------------------------- /UpUpServer/Core/CoreImpl/Server.cs: -------------------------------------------------------------------------------- 1 | using System.Reflection; 2 | 3 | namespace UpUpServer; 4 | 5 | using System.Net; 6 | using System.Net.Sockets; 7 | 8 | public class Server : IServer 9 | { 10 | private Socket _listener = null!; 11 | private string _ip = "127.0.0.1"; 12 | private int _port = 9999; 13 | private IPEndPoint _ipEndPoint = null!; 14 | private uint curId = 0; 15 | public bool IsRunning { get; set; } 16 | 17 | public IMsgManager MsgManager { get; set; } 18 | public IConnManager ConnManager { get; set; } 19 | public float RunTime = 0; 20 | 21 | public SystemManager SysManager { get; set; } 22 | 23 | // 更新频率 ms 24 | public int UpdateInterval = 200; 25 | 26 | public Server() 27 | { 28 | ConnManager = new ConnManager(); 29 | MsgManager = new MsgManager(); 30 | SysManager = new SystemManager(); 31 | } 32 | 33 | public async void Start() 34 | { 35 | Log.Info($"server starting..."); 36 | IsRunning = true; 37 | InitReflect(); 38 | 39 | MsgManager.StartWorkPool(this); 40 | 41 | if (ConfigManager.Instance.ServerConfig?.MongoDbUrl!=null && ConfigManager.Instance.ServerConfig.MongoDbUrl!="") 42 | { 43 | MongoDBProxy.Instance.Open(ConfigManager.Instance.ServerConfig.MongoDbUrl, ConfigManager.Instance.ServerConfig.MongoDbName); 44 | } 45 | else 46 | { 47 | Log.Error($"mongo db url is null"); 48 | } 49 | if (ConfigManager.Instance.ServerConfig?.RedisDbUrl!=null && ConfigManager.Instance.ServerConfig.RedisDbUrl!="") 50 | { 51 | RedisDBProxy.Instance.Open(ConfigManager.Instance.ServerConfig.RedisDbUrl); 52 | } 53 | else 54 | { 55 | Log.Error($"redis db url is null"); 56 | } 57 | 58 | _ip = ConfigManager.Instance.ServerConfig?.Ip ?? _ip; 59 | _port = ConfigManager.Instance.ServerConfig?.Port ?? _port; 60 | 61 | _listener = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp); 62 | _ipEndPoint = new IPEndPoint(IPAddress.Parse(_ip), _port); 63 | _listener.Bind(_ipEndPoint); 64 | _listener.Listen(3000); 65 | Log.Info($"server start at {_ip}:{_port}"); 66 | 67 | // 监听连接 68 | new Thread(AcceptAsync).Start(); 69 | // 不可靠的心跳检测 70 | new Thread(CheckConnection).Start(); 71 | 72 | TimerManager.Instance.Init(); 73 | 74 | // 2s 调用一次 75 | UpUpServer.Timer.LoopAction(UpdateInterval, (_ => 76 | { 77 | this.Update(); 78 | })); 79 | } 80 | 81 | public void InitReflect() 82 | { 83 | var types = AppDomain.CurrentDomain.GetAssemblies().SelectMany(x => x.GetTypes()); 84 | var routerTypes = types.Where(t => 85 | t.GetCustomAttributes(typeof(AutoRouterAttribute), false).Length > 0); 86 | 87 | foreach (var type in routerTypes) 88 | { 89 | var attribute = type.GetCustomAttribute(); 90 | this.AddRouter(attribute.ReqId, Activator.CreateInstance(type) as IRouter); 91 | Log.Info($"Add Router: {type.FullName} ReqId: {attribute.ReqId}"); 92 | } 93 | 94 | var systemTypes = types.Where(t => 95 | t.GetCustomAttributes(typeof(AutoSystemAttribute), false).Length > 0); 96 | foreach (var typeSys in systemTypes) 97 | { 98 | var attribute = typeSys.GetCustomAttribute(); 99 | this.AddSystem(typeSys, Activator.CreateInstance(typeSys) as ISystem); 100 | Log.Info($"Add System: {typeSys.FullName}"); 101 | } 102 | } 103 | 104 | public void AcceptAsync() 105 | { 106 | while (IsRunning) 107 | { 108 | Socket clientSocket = _listener.Accept(); 109 | Log.Info($"client connected: {clientSocket.RemoteEndPoint}"); 110 | 111 | Interlocked.Increment(ref curId); 112 | 113 | Connection connection = new Connection(curId, clientSocket, this.MsgManager); 114 | ConnManager.AddConnection(connection); 115 | connection.Start(); 116 | } 117 | } 118 | 119 | public void CheckConnection() 120 | { 121 | while (IsRunning) 122 | { 123 | //每10s 检查一次 124 | Thread.Sleep(1000 * 10); 125 | ConnManager.CheckOnline(); 126 | } 127 | } 128 | 129 | public void Update() 130 | { 131 | SysManager.Update(); 132 | } 133 | 134 | public void Stop() 135 | { 136 | Log.Info($"server stoping..."); 137 | _listener.Close(); 138 | _listener.Dispose(); 139 | 140 | Log.Info($"server stoped"); 141 | } 142 | 143 | public void AddRouter(uint msgId, IRouter router) 144 | { 145 | MsgManager.RegisterRouter(msgId, router); 146 | } 147 | 148 | public void AddSystem(Type type, ISystem system) 149 | { 150 | SysManager.RegistSystem(type, system); 151 | } 152 | 153 | 154 | } -------------------------------------------------------------------------------- /UpUpServer/Core/Log/FileLogger.cs: -------------------------------------------------------------------------------- 1 | namespace UpUpServer; 2 | 3 | using System.Collections.Generic; 4 | 5 | public class FileLogger : ILog 6 | { 7 | private string timeFormat = "yyyy-MM-dd HH:mm:ss.fff"; 8 | private string fileFormat = "yyyyMMdd_hhmmss"; 9 | 10 | private List logArray; 11 | private string logPath = null; 12 | 13 | private int currLogCount = 0; 14 | private string reporterPath = "./"; 15 | 16 | 17 | private int logBufferMaxNumber = 10; 18 | private int curLogBufferNumber = 0; 19 | private int logMaxCapacity = 500; 20 | private int logFileMaxSize = 1024 * 1024 * 1; 21 | 22 | public void Init() 23 | { 24 | logArray = new List(); 25 | reporterPath = ConfigManager.Instance.ServerConfig?.LogPath ?? reporterPath; 26 | if (string.IsNullOrEmpty(logPath)) 27 | { 28 | logPath = reporterPath + "//" + DateTime.Now.ToString(fileFormat) + "_log.txt"; 29 | } 30 | } 31 | 32 | #region 日志接口 33 | 34 | public void Trace(string msg, string tag = "") 35 | { 36 | logArray.Add($"[Trace]{DateTime.Now.ToString(timeFormat)} {tag} {msg}"); 37 | CheckWriteFile(); 38 | } 39 | 40 | public void Info(string msg, string tag = "") 41 | { 42 | logArray.Add($"[Info]{DateTime.Now.ToString(timeFormat)} {tag} {msg}"); 43 | CheckWriteFile(); 44 | 45 | } 46 | 47 | public void Warning(string msg, string tag = "") 48 | { 49 | logArray.Add($"[Warning]{DateTime.Now.ToString(timeFormat)} {tag} {msg}"); 50 | CheckWriteFile(); 51 | 52 | } 53 | 54 | public void Error(string msg, string tag = "") 55 | { 56 | logArray.Add($"[Error]{DateTime.Now.ToString(timeFormat)} {tag} {msg}"); 57 | CheckWriteFile(); 58 | 59 | } 60 | 61 | public void Error(Exception e) 62 | { 63 | logArray.Add(e.ToString()); 64 | CheckWriteFile(); 65 | 66 | } 67 | 68 | public void Trace(string message, params object[] args) 69 | { 70 | logArray.Add(String.Format(message, args)); 71 | CheckWriteFile(); 72 | 73 | } 74 | 75 | public void Warning(string message, params object[] args) 76 | { 77 | logArray.Add(String.Format(message, args)); 78 | CheckWriteFile(); 79 | 80 | } 81 | 82 | public void Info(string message, params object[] args) 83 | { 84 | logArray.Add(String.Format(message, args)); 85 | CheckWriteFile(); 86 | 87 | } 88 | 89 | public void Error(string message, params object[] args) 90 | { 91 | logArray.Add(String.Format(message, args)); 92 | CheckWriteFile(); 93 | 94 | } 95 | 96 | #endregion 97 | 98 | #region 写入文件操作 99 | 100 | public void Flush() 101 | { 102 | SyncLog(); 103 | } 104 | 105 | private void CheckWriteFile() 106 | { 107 | SyncLog(); 108 | 109 | // if (logArray.Count >= logBufferMaxNumber) 110 | // { 111 | // SyncLog(); 112 | // } 113 | } 114 | 115 | private void SyncLog() 116 | { 117 | int len = logArray.Count; 118 | // string logData = string.Join("\r\n", logArray); 119 | SyncToFile(logPath, ""); 120 | logArray.Clear(); 121 | } 122 | 123 | private void SyncToFile(string pathAndName, string info) 124 | { 125 | if (!Directory.Exists(reporterPath)) Directory.CreateDirectory(reporterPath); 126 | 127 | StreamWriter sw; 128 | FileInfo t = new FileInfo(pathAndName); 129 | 130 | if (!t.Exists) 131 | { 132 | sw = t.CreateText(); 133 | } 134 | else 135 | { 136 | if (t.Length > logFileMaxSize) 137 | { 138 | logPath = reporterPath + "//" + DateTime.Now.ToString(fileFormat) + "_log.txt"; 139 | t = new FileInfo(logPath); 140 | sw = t.CreateText(); 141 | } 142 | else 143 | { 144 | sw = t.AppendText(); 145 | } 146 | } 147 | 148 | // sw.Write(info); 149 | for (int i = 0; i < logArray.Count; i++) 150 | { 151 | sw.WriteLine(logArray[i]); 152 | } 153 | sw.Close(); 154 | sw.Dispose(); 155 | } 156 | 157 | [Obsolete] 158 | private void Write(string writeFileData, LogLevel type) 159 | { 160 | if (currLogCount >= logMaxCapacity) 161 | { 162 | logPath = reporterPath + "//" + DateTime.Now.ToString(fileFormat) + "_log.txt"; 163 | logMaxCapacity = 0; 164 | } 165 | 166 | currLogCount++; 167 | 168 | if (!string.IsNullOrEmpty(writeFileData)) 169 | { 170 | writeFileData = DateTime.Now.ToString(timeFormat) + "|" + type.ToString() + "|" + 171 | writeFileData + "\r\n"; 172 | AppendDataToFile(writeFileData); 173 | } 174 | } 175 | 176 | [Obsolete] 177 | private void AppendDataToFile(string writeFileDate) 178 | { 179 | if (!string.IsNullOrEmpty(writeFileDate)) 180 | { 181 | logArray.Add(writeFileDate); 182 | } 183 | 184 | if (logArray.Count % logBufferMaxNumber == 0) 185 | { 186 | SyncLog(); 187 | } 188 | } 189 | 190 | #endregion 191 | } -------------------------------------------------------------------------------- /UpUpServer/Core/CoreImpl/TimerManager/TimerManager.cs: -------------------------------------------------------------------------------- 1 | 2 | namespace UpUpServer 3 | { 4 | 5 | public class TimerManager : SingleClass 6 | { 7 | // UTC时间 8 | public static DateTime Now => DateTime.Now; 9 | // 当前ms (毫秒时间戳) 10 | public static long CurrentMs => (Now.Ticks - 621355968000000000) / 10000; 11 | 12 | private readonly LinkedList 13 | _persistenceTimers = 14 | new LinkedList(); //can not be effected by Timer.xxAllRegisteredTimers() methods 15 | 16 | private readonly LinkedList _timers = new LinkedList(); 17 | 18 | // buffer adding timers so we don't edit a collection during iteration 19 | private readonly List _timersToAdd = new List(); 20 | private readonly List _persistenceTimersToAdd = new List(); 21 | 22 | System.Timers.Timer _sysTimer; // 默认精度500ms 23 | 24 | public long TimerMs; 25 | 26 | public void Init() 27 | { 28 | Timer.InitTimerManager(); 29 | TimerMs = CurrentMs; 30 | _sysTimer = new System.Timers.Timer(500); 31 | _sysTimer.Elapsed += (_, _) => 32 | { 33 | TimerMs = CurrentMs; 34 | this.Update(); 35 | }; 36 | _sysTimer.AutoReset = true; 37 | _sysTimer.Enabled = true; 38 | } 39 | 40 | public void CancelAllTimers() 41 | { 42 | foreach (Timer timer in _timers) 43 | { 44 | timer.Cancel(); 45 | timer._isInManager = false; 46 | } 47 | 48 | foreach (Timer timer in _timersToAdd) 49 | { 50 | timer.Cancel(); 51 | timer._isInManager = false; 52 | } 53 | 54 | _timers.Clear(); 55 | _timersToAdd.Clear(); 56 | } 57 | 58 | public void CancelAllTimersByOwner(object? owner) 59 | { 60 | if (owner is null) return; 61 | CancelAllTimersByOwner(_timers, _timersToAdd, owner); 62 | CancelAllTimersByOwner(_persistenceTimers, _persistenceTimersToAdd, owner); 63 | } 64 | 65 | public void PauseAllTimers() 66 | { 67 | foreach (Timer timer in _timers) 68 | { 69 | timer.Pause(); 70 | } 71 | 72 | foreach (Timer timer in _timersToAdd) 73 | { 74 | timer.Pause(); 75 | } 76 | } 77 | 78 | public void ResumeAllTimers() 79 | { 80 | foreach (Timer timer in _timers) 81 | { 82 | timer.Resume(); 83 | } 84 | 85 | foreach (Timer timer in _timersToAdd) 86 | { 87 | timer.Resume(); 88 | } 89 | } 90 | 91 | public void Register(Timer timer) 92 | { 93 | if (!timer.isPersistence) 94 | _timersToAdd.Add(timer); 95 | else 96 | _persistenceTimersToAdd.Add(timer); 97 | } 98 | 99 | // update all the registered timers on every frame 100 | public void Update() 101 | { 102 | UpdateTimers(); 103 | UpdatePersistenceTimers(); 104 | } 105 | 106 | //Timer 107 | private void UpdateTimers() 108 | { 109 | UpdateTimersInternal(_timers, _timersToAdd); 110 | } 111 | 112 | //PersistenceTimer 113 | private void UpdatePersistenceTimers() 114 | { 115 | UpdateTimersInternal(_persistenceTimers, _persistenceTimersToAdd); 116 | } 117 | 118 | private static void UpdateTimersInternal(LinkedList timers, List timersToAdd) 119 | { 120 | int toAddCount = timersToAdd.Count; 121 | if (toAddCount > 0) 122 | { 123 | for (int i = 0; i < toAddCount; i++) 124 | timers.AddLast(timersToAdd[i]._linkedListNode); 125 | timersToAdd.Clear(); 126 | } 127 | 128 | var node = timers.First; 129 | while (node != null) 130 | { 131 | var timer = node.Value; 132 | timer.Update(); 133 | if (timer.isDone) 134 | { 135 | timer._isInManager = false; 136 | var toRemoveNode = node; 137 | node = node.Next; 138 | //remove 139 | timers.Remove(toRemoveNode); 140 | } 141 | else 142 | node = node.Next; 143 | } 144 | } 145 | 146 | private static void CancelAllTimersByOwner(LinkedList timers, List timersToAdd, Object owner) 147 | { 148 | var node = timers.First; 149 | while (node != null) 150 | { 151 | var timer = node.Value; 152 | if (!timer.isDone && timer.autoDestroyOwner == owner) 153 | { 154 | timer.Cancel(); 155 | timer._isInManager = false; 156 | var toRemoveNode = node; 157 | node = node.Next; 158 | //remove 159 | timers.Remove(toRemoveNode); 160 | } 161 | else 162 | node = node.Next; 163 | } 164 | 165 | for (int i = timersToAdd.Count - 1; i >= 0; i--) 166 | { 167 | var timer = timersToAdd[i]; 168 | if (!timer.isDone && timer.autoDestroyOwner != owner) continue; 169 | timer.Cancel(); 170 | timer._isInManager = false; 171 | //remove 172 | timersToAdd.RemoveAt(i); 173 | } 174 | } 175 | } 176 | } -------------------------------------------------------------------------------- /ClientTest/Connection.cs: -------------------------------------------------------------------------------- 1 | using System.Buffers; 2 | using System.IO.Pipelines; 3 | using System.Net; 4 | using System.Net.Sockets; 5 | using System.Runtime.CompilerServices; 6 | 7 | public class Connection 8 | { 9 | public event Action? OnConnected; 10 | public event Action>? OnReceived; 11 | public event Action? OnClose; 12 | 13 | public readonly IPAddress Ip; 14 | public readonly int Port; 15 | internal readonly Socket Socket; 16 | private readonly Pipe _pipe; 17 | private volatile bool _isDispose; 18 | private readonly int _bufferSize = 10 * 1024; 19 | 20 | public Connection(string ip, int port) 21 | { 22 | Socket = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp); 23 | Socket.NoDelay = true; 24 | // Socket.ReceiveTimeout = 1000 * 60 * 5; //5分钟没收到东西就算超时 25 | Ip = IPAddress.Parse(ip); 26 | Port = port; 27 | _pipe = new Pipe(); 28 | } 29 | 30 | public void Connect() 31 | { 32 | if (Socket.Connected) return; 33 | Socket.Connect(Ip, Port); 34 | Start(); 35 | } 36 | 37 | internal async void Start() 38 | { 39 | SetSocket(); 40 | Task writing = FillPipeAsync(Socket, _pipe.Writer); 41 | Task reading = ReadPipeAsync(_pipe.Reader); 42 | _ = Task.WhenAll(reading, writing); 43 | OnConnected?.Invoke(); 44 | } 45 | 46 | private void SetSocket() 47 | { 48 | _isDispose = false; 49 | Socket.ReceiveBufferSize = _bufferSize; 50 | Socket.SendBufferSize = _bufferSize; 51 | } 52 | 53 | /// 54 | /// Read from socket and write to pipe 55 | /// 56 | /// 57 | /// 58 | async Task FillPipeAsync(Socket socket, PipeWriter writer) 59 | { 60 | try 61 | { 62 | while (!_isDispose && Socket.Connected) 63 | { 64 | // Allocate at least _bufferSize bytes from the PipeWriter. 65 | Memory memory = writer.GetMemory(_bufferSize); 66 | try 67 | { 68 | int bytesRead = await socket.ReceiveAsync(memory, SocketFlags.None); 69 | if (bytesRead == 0) 70 | { 71 | break; 72 | } 73 | 74 | // Tell the PipeWriter how much was read from the Socket. 75 | writer.Advance(bytesRead); 76 | } 77 | catch (SocketException e) 78 | { 79 | //connection ended 80 | if (e.SocketErrorCode == SocketError.ConnectionReset) 81 | { 82 | break; 83 | } 84 | } 85 | catch (Exception ex) 86 | { 87 | Console.WriteLine(ex); 88 | break; 89 | } 90 | 91 | // Make the data available to the PipeReader. 92 | FlushResult result = await writer.FlushAsync(); 93 | 94 | if (result.IsCompleted) 95 | { 96 | break; 97 | } 98 | } 99 | } 100 | catch (SocketException) 101 | { 102 | Close("connection has been closed"); 103 | } 104 | catch (ObjectDisposedException) 105 | { 106 | Close("connection has been closed"); 107 | } 108 | catch (Exception ex) 109 | { 110 | Close($"{ex.Message}\n{ex.StackTrace}"); 111 | } 112 | 113 | // By completing PipeWriter, tell the PipeReader that there's no more data coming. 114 | await writer.CompleteAsync(); 115 | Close("connection has been closed"); 116 | } 117 | 118 | /// 119 | /// Read from pipe and process 120 | /// 121 | /// 122 | async Task ReadPipeAsync(PipeReader reader) 123 | { 124 | while (true) 125 | { 126 | ReadResult result = await reader.ReadAsync(); 127 | ReadOnlySequence buffer = result.Buffer; 128 | while (HandleStickyPack(ref buffer, out ReadOnlySequence packet)) 129 | { 130 | // Process callback 131 | try 132 | { 133 | OnReceived?.Invoke(packet); 134 | } 135 | catch (Exception ex) 136 | { 137 | Console.WriteLine(ex); 138 | } 139 | } 140 | 141 | // Tell the PipeReader how much of the buffer has been consumed. 142 | reader.AdvanceTo(buffer.Start); 143 | 144 | // Stop reading if there's no more data coming. 145 | if (result.IsCompleted) 146 | { 147 | break; 148 | } 149 | } 150 | 151 | // Mark the PipeReader as complete. 152 | await reader.CompleteAsync(); 153 | } 154 | 155 | bool HandleStickyPack(ref ReadOnlySequence buffer, out ReadOnlySequence packet) 156 | { 157 | //first 4 bytes is a uint represents length of packet 158 | if (buffer.Length < UpPackSerialize.Instance.GetHeadLen()) 159 | { 160 | packet = default; 161 | return false; 162 | } 163 | 164 | uint length; 165 | 166 | // if (buffer.IsSingleSegment) 167 | // { 168 | // var b = buffer.FirstSpan[0]; 169 | // length = Unsafe.ReadUnaligned(ref b); 170 | // } 171 | // else 172 | { 173 | Span firstFourBytes = stackalloc byte[4]; 174 | buffer.Slice(buffer.Start, 4).CopyTo(firstFourBytes); 175 | length = Unsafe.ReadUnaligned(ref firstFourBytes[0]); 176 | } 177 | 178 | if (buffer.Length < length) 179 | { 180 | packet = default; 181 | return false; 182 | } 183 | 184 | packet = buffer.Slice(buffer.Start, length); 185 | buffer = buffer.Slice(length); 186 | return true; 187 | } 188 | 189 | public void Send(Span buffer) 190 | { 191 | try 192 | { 193 | if (!_isDispose) 194 | { 195 | Socket.Send(buffer); 196 | } 197 | } 198 | catch 199 | { 200 | Close("connection has been closed"); 201 | } 202 | } 203 | 204 | public async ValueTask SendAsync(Memory data) 205 | { 206 | try 207 | { 208 | if (!_isDispose) 209 | { 210 | await Socket.SendAsync(data, SocketFlags.None); 211 | } 212 | } 213 | catch 214 | { 215 | Close("connection has been closed"); 216 | } 217 | } 218 | 219 | public void Close(string msg = "closed manually") 220 | { 221 | if (!_isDispose) 222 | { 223 | _isDispose = true; 224 | try 225 | { 226 | try 227 | { 228 | Socket.Close(); 229 | } 230 | catch 231 | { 232 | //ignore 233 | } 234 | 235 | Socket.Dispose(); 236 | GC.SuppressFinalize(this); 237 | } 238 | catch (Exception) 239 | { 240 | //ignore 241 | } 242 | 243 | OnClose?.Invoke(msg); 244 | } 245 | } 246 | } -------------------------------------------------------------------------------- /UpUpServer/Utils/TimeUtil.cs: -------------------------------------------------------------------------------- 1 | namespace UpUpServer; 2 | 3 | public static class TimeUtil 4 | { 5 | private static readonly long epoch = new DateTime(1970, 1, 1, 0, 0, 0, DateTimeKind.Utc).Ticks; 6 | 7 | // 精确毫秒 1677401019571.67 8 | public static double GetMillTimeStamp() 9 | { 10 | TimeSpan timeSpan = DateTime.Now - new DateTime(1970, 1, 1, 0, 0, 0, 0); 11 | return timeSpan.TotalMilliseconds; 12 | } 13 | 14 | // 毫秒 1677372219570 15 | public static long NowMs() 16 | { 17 | return (DateTime.UtcNow.Ticks - epoch) / 10000; 18 | } 19 | 20 | // 秒 1677372219 21 | public static long NowSeconds() 22 | { 23 | return (DateTime.UtcNow.Ticks - epoch) / 10000000; 24 | } 25 | } 26 | 27 | /* 28 | 获得当前系统时间: DateTime dt = DateTime.Now; 29 | Environment.TickCount可以得到“系统启动到现在”的毫秒值 30 | DateTime now = DateTime.Now; 31 | Console.WriteLine(now.ToString("yyyy-MM-dd")); //按yyyy-MM-dd格式输出s 32 | Console.WriteLine(dt.ToString()); // 26/11/2009 AM 11:21:30 33 | Console.WriteLine(dt.ToFileTime().ToString()); // 129036792908014024 34 | // Converts the value of the current System.DateTime object to a Windows file time 35 | Console.WriteLine(dt.ToFileTimeUtc().ToString()); // 129036792908014024 36 | // Converts the value of the current System.DateTime object to a Windows file time 37 | Console.WriteLine(dt.ToLocalTime().ToString()); // 26/11/2009 AM 11:21:30 38 | // Converts the value of the current System.DateTime object to local time. 39 | Console.WriteLine(dt.ToLongDateString().ToString()); // 2009年11月26日 40 | Console.WriteLine(dt.ToLongTimeString().ToString()); // AM 11:21:30 41 | Console.WriteLine(dt.ToOADate().ToString()); // 40143.4732731597 42 | Console.WriteLine(dt.ToShortDateString().ToString()); // 26/11/2009 43 | Console.WriteLine(dt.ToShortTimeString().ToString()); // AM 11:21 44 | Console.WriteLine(dt.ToUniversalTime().ToString()); // 26/11/2009 AM 3:21:30 45 | Console.WriteLine(dt.Year.ToString()); // 2009 46 | Console.WriteLine(dt.Date.ToString()); // 26/11/2009 AM 12:00:00 47 | Console.WriteLine(dt.DayOfWeek.ToString()); // Thursday 48 | Console.WriteLine(dt.DayOfYear.ToString()); // 330 49 | Console.WriteLine(dt.Hour.ToString()); // 11 50 | Console.WriteLine(dt.Millisecond.ToString()); // 801 (毫秒) 51 | Console.WriteLine(dt.Minute.ToString()); // 21 52 | Console.WriteLine(dt.Month.ToString()); // 11 53 | Console.WriteLine(dt.Second.ToString()); // 30 54 | Console.WriteLine(dt.Ticks.ToString()); // 633948312908014024 55 | 56 | Console.WriteLine(dt.TimeOfDay.ToString()); // 12:29:51.5181524 57 | // Gets the time of day for this instance. 58 | // 返回 A System.TimeSpan that represents the fraction of the day that has elapsed since midnight. 59 | Console.WriteLine(dt.ToString()); // 26/11/2009 PM 12:29:51 60 | Console.WriteLine(dt.AddYears(1).ToString()); // 26/11/2010 PM 12:29:51 61 | Console.WriteLine(dt.AddDays(1.1).ToString()); // 27/11/2009 PM 2:53:51 62 | Console.WriteLine(dt.AddHours(1.1).ToString()); // 26/11/2009 PM 1:35:51 63 | Console.WriteLine(dt.AddMilliseconds(1.1).ToString()); //26/11/2009 PM 12:29:51 64 | Console.WriteLine(dt.AddMonths(1).ToString()); // 26/12/2009 PM 12:29:51 65 | Console.WriteLine(dt.AddSeconds(1.1).ToString()); // 26/11/2009 PM 12:29:52 66 | Console.WriteLine(dt.AddMinutes(1.1).ToString()); // 26/11/2009 PM 12:30:57 67 | Console.WriteLine(dt.AddTicks(1000).ToString()); // 26/11/2009 PM 12:29:51 68 | Console.WriteLine(dt.CompareTo(dt).ToString()); // 0 69 | Console.WriteLine(dt.Add(new TimeSpan(1,0,0,0)).ToString()); // 加上一个时间段 70 | (注: 71 | System.TimeSpan为一个时间段,构造函数如下 72 | public TimeSpan(long ticks); // ticks: A time period expressed in 100-nanosecond units. 73 | //nanosecond:十亿分之一秒 new TimeSpan(10,000,000) 为一秒 74 | public TimeSpan(int hours, int minutes, int seconds); 75 | public TimeSpan(int days, int hours, int minutes, int seconds); 76 | public TimeSpan(int days, int hours, int minutes, int seconds, int milliseconds); 77 | ) 78 | Console.WriteLine(dt.Equals("2005-11-6 16:11:04").ToString()); // False 79 | Console.WriteLine(dt.Equals(dt).ToString()); // True 80 | Console.WriteLine(dt.GetHashCode().ToString()); // 1103291775 81 | Console.WriteLine(dt.GetType().ToString()); // System.DateTime 82 | Console.WriteLine(dt.GetTypeCode().ToString()); // DateTime 83 | 84 | long Start = Environment.TickCount; //单位是毫秒 85 | long End = Environment.TickCount; 86 | Console.WriteLine("Start is : "+Start); 87 | Console.WriteLine("End is : "+End); 88 | Console.WriteLine("The Time is {0}",End-Start); 89 | Console.WriteLine(dt.GetDateTimeFormats('s')[0].ToString()); //2009-11-26T13:29:06 90 | Console.WriteLine(dt.GetDateTimeFormats('t')[0].ToString()); //PM 1:29 91 | Console.WriteLine(dt.GetDateTimeFormats('y')[0].ToString()); //2009年11月 92 | Console.WriteLine(dt.GetDateTimeFormats('D')[0].ToString()); //2009年11月26日 93 | Console.WriteLine(dt.GetDateTimeFormats('D')[1].ToString()); //星期四, 26 十一月, 2009 94 | Console.WriteLine(dt.GetDateTimeFormats('D')[2].ToString()); //26 十一月, 2009 95 | Console.WriteLine(dt.GetDateTimeFormats('D')[3].ToString()); //星期四 2009 11 26 96 | Console.WriteLine(dt.GetDateTimeFormats('M')[0].ToString()); //26 十一月 97 | Console.WriteLine(dt.GetDateTimeFormats('f')[0].ToString()); //2009年11月26日 PM 1:29 98 | Console.WriteLine(dt.GetDateTimeFormats('g')[0].ToString()); //26/11/2009 PM 1:29 99 | Console.WriteLine(dt.GetDateTimeFormats('r')[0].ToString()); //Thu, 26 Nov 2009 13:29:06 GMT 100 | (注: 101 | 常用的日期时间格式: 102 | 格式 说明 输出格式 103 | d 精简日期格式 MM/dd/yyyy 104 | D 详细日期格式 dddd, MMMM dd, yyyy 105 | f 完整格式 (long date + short time) dddd, MMMM dd, yyyy HH:mm 106 | F 完整日期时间格式 (long date + long time) dddd, MMMM dd, yyyy HH:mm:ss 107 | g 一般格式 (short date + short time) MM/dd/yyyy HH:mm 108 | G 一般格式 (short date + long time) MM/dd/yyyy HH:mm:ss 109 | m,M 月日格式 MMMM dd 110 | s 适中日期时间格式 yyyy-MM-dd HH:mm:ss 111 | t 精简时间格式 HH:mm 112 | T 详细时间格式 HH:mm:ss 113 | ) 114 | 115 | Console.WriteLine(string.Format("{0:d}", dt)); //28/12/2009 116 | Console.WriteLine(string.Format("{0:D}", dt)); //2009年12月28日 117 | Console.WriteLine(string.Format("{0:f}", dt)); //2009年12月28日 AM 10:29 118 | Console.WriteLine(string.Format("{0:F}", dt)); //2009年12月28日 AM 10:29:18 119 | Console.WriteLine(string.Format("{0:g}", dt)); //28/12/2009 AM 10:29 120 | Console.WriteLine(string.Format("{0:G}", dt)); //28/12/2009 AM 10:29:18 121 | Console.WriteLine(string.Format("{0:M}", dt)); //28 十二月 122 | Console.WriteLine(string.Format("{0:R}", dt)); //Mon, 28 Dec 2009 10:29:18 GMT 123 | Console.WriteLine(string.Format("{0:s}", dt)); //2009-12-28T10:29:18 124 | Console.WriteLine(string.Format("{0:t}", dt)); //AM 10:29 125 | Console.WriteLine(string.Format("{0:T}", dt)); //AM 10:29:18 126 | Console.WriteLine(string.Format("{0:u}", dt)); //2009-12-28 10:29:18Z 127 | Console.WriteLine(string.Format("{0:U}", dt)); //2009年12月28日 AM 2:29:18 128 | Console.WriteLine(string.Format("{0:Y}", dt)); //2009年12月 129 | Console.WriteLine(string.Format("{0}", dt)); //28/12/2009 AM 10:29:18 130 | Console.WriteLine(string.Format("{0:yyyyMMddHHmmssffff}", dt)); //200912281029182047 131 | 132 | 计算2个日期之间的天数差 133 | DateTime dt1 = Convert.ToDateTime("2007-8-1"); 134 | DateTime dt2 = Convert.ToDateTime("2007-8-15"); 135 | TimeSpan span = dt2.Subtract(dt1); 136 | int dayDiff = span.Days ; 137 | 138 | 计算某年某月的天数 139 | int days = DateTime.DaysInMonth(2009, 8); 140 | days = 31; 141 | 142 | 给日期增加一天、减少一天 143 | DateTime dt =DateTime.Now; 144 | dt.AddDays(1); //增加一天 dt本身并不改变 145 | dt.AddDays(-1);//减少一天 dt本身并不改变 146 | */ -------------------------------------------------------------------------------- /UpUpServer/Core/CoreImpl/Connection.cs: -------------------------------------------------------------------------------- 1 | using System.Buffers; 2 | using System.IO.Pipelines; 3 | using System.Net; 4 | using System.Net.Sockets; 5 | using System.Runtime.CompilerServices; 6 | using System.Text; 7 | 8 | namespace UpUpServer; 9 | 10 | public class Connection : IConnection 11 | { 12 | readonly Socket Socket; 13 | public IMsgManager MsgManager { get; set; } 14 | 15 | public event Action? OnConnected; 16 | public event Action>? OnReceived; 17 | public event Action? OnClose; 18 | 19 | public readonly IPAddress Ip; 20 | public readonly int Port; 21 | 22 | private readonly Pipe _pipe; 23 | 24 | private volatile bool _isDispose; 25 | 26 | //默认10K的缓冲区空间 27 | private readonly int _bufferSize = 10 * 1024; 28 | 29 | public uint Id { get; set; } = 0; 30 | public string UserId { get; set; } = ""; 31 | 32 | public bool IsConnected 33 | { 34 | get => Socket.Connected; 35 | } 36 | 37 | internal Connection(uint id, Socket socket, IMsgManager msgManager) 38 | { 39 | Id = id; 40 | Socket = socket; 41 | IPEndPoint? remoteIpEndPoint = socket.RemoteEndPoint as IPEndPoint; 42 | Ip = remoteIpEndPoint?.Address!; 43 | Port = remoteIpEndPoint?.Port ?? 0; 44 | _pipe = new Pipe(); 45 | MsgManager = msgManager; 46 | } 47 | 48 | public async void Start() 49 | { 50 | SetSocket(); 51 | Task writing = FillPipeAsync(Socket, _pipe.Writer); 52 | Task reading = ReadPipeAsync(_pipe.Reader); 53 | _ = Task.WhenAll(reading, writing); 54 | OnConnected?.Invoke(); 55 | } 56 | 57 | public void SetUserId(string userId) 58 | { 59 | UserId = userId; 60 | } 61 | 62 | private void SetSocket() 63 | { 64 | _isDispose = false; 65 | Socket.ReceiveBufferSize = _bufferSize; 66 | Socket.SendBufferSize = _bufferSize; 67 | } 68 | 69 | /// 70 | /// Read from socket and write to pipe 71 | /// 72 | /// 73 | /// 74 | async Task FillPipeAsync(Socket socket, PipeWriter writer) 75 | { 76 | try 77 | { 78 | while (!_isDispose && Socket.Connected) 79 | { 80 | // Allocate at least _bufferSize bytes from the PipeWriter. 81 | Memory memory = writer.GetMemory(_bufferSize); 82 | try 83 | { 84 | int bytesRead = await socket.ReceiveAsync(memory, SocketFlags.None); 85 | if (bytesRead == 0) 86 | { 87 | break; 88 | } 89 | 90 | // Tell the PipeWriter how much was read from the Socket. 91 | writer.Advance(bytesRead); 92 | } 93 | catch (SocketException e) 94 | { 95 | //connection ended 96 | if (e.SocketErrorCode == SocketError.ConnectionReset) 97 | { 98 | break; 99 | } 100 | } 101 | catch (Exception ex) 102 | { 103 | Console.WriteLine(ex); 104 | break; 105 | } 106 | 107 | // Make the data available to the PipeReader. 108 | FlushResult result = await writer.FlushAsync(); 109 | 110 | if (result.IsCompleted) 111 | { 112 | break; 113 | } 114 | } 115 | } 116 | catch (SocketException e) 117 | { 118 | Log.Warning($"SocketException closed, ip={Ip} exception={e.Message}"); 119 | Close(); 120 | } 121 | catch (ObjectDisposedException e) 122 | { 123 | Log.Warning($"ObjectDisposedException closed, ip={Ip} exception={e.Message}"); 124 | Close(); 125 | } 126 | catch (Exception e) 127 | { 128 | Log.Warning($"Exception closed, ip={Ip} exception={e.Message}"); 129 | Close(); 130 | } 131 | 132 | // By completing PipeWriter, tell the PipeReader that there's no more data coming. 133 | await writer.CompleteAsync(); 134 | Log.Info($"client close connection, ip={Ip}"); 135 | Close(); // "connection has been closed" 136 | } 137 | 138 | /// 139 | /// Read from pipe and process 140 | /// 141 | /// 142 | async Task ReadPipeAsync(PipeReader reader) 143 | { 144 | while (true) 145 | { 146 | ReadResult result = await reader.ReadAsync(); 147 | ReadOnlySequence buffer = result.Buffer; 148 | while (HandleStickyPack(ref buffer, out ReadOnlySequence packet)) 149 | { 150 | try 151 | { 152 | Request request = new Request(this, UpPackSerialize.Instance.Deserialize(packet)); 153 | MsgManager.AddRequest(request); 154 | } 155 | catch (Exception ex) 156 | { 157 | Console.WriteLine(ex); 158 | this.Close(); 159 | } 160 | } 161 | 162 | reader.AdvanceTo(buffer.Start); 163 | if (result.IsCompleted) 164 | { 165 | break; 166 | } 167 | } 168 | 169 | // Mark the PipeReader as complete. 170 | await reader.CompleteAsync(); 171 | } 172 | 173 | bool HandleStickyPack(ref ReadOnlySequence buffer, out ReadOnlySequence packet) 174 | { 175 | //first 4 bytes is a uint represents length of packet 176 | if (buffer.Length < UpPackSerialize.Instance.GetHeadLen()) 177 | { 178 | packet = default; 179 | return false; 180 | } 181 | 182 | uint length; 183 | 184 | // if (buffer.IsSingleSegment) 185 | // { 186 | // var b = buffer.FirstSpan[0]; 只会读取第一个byte,读的长度有问题 187 | // length = Unsafe.ReadUnaligned(ref b); 188 | // } 189 | // else 190 | { 191 | Span firstFourBytes = stackalloc byte[4]; 192 | buffer.Slice(buffer.Start, 4).CopyTo(firstFourBytes); 193 | length = Unsafe.ReadUnaligned(ref firstFourBytes[0]); 194 | } 195 | 196 | if (buffer.Length < length) 197 | { 198 | packet = default; 199 | return false; 200 | } 201 | 202 | packet = buffer.Slice(buffer.Start, length); 203 | buffer = buffer.Slice(length); 204 | return true; 205 | } 206 | 207 | bool TryParsePacket(ref ReadOnlySequence buffer, out ReadOnlySequence packet) 208 | { 209 | //first 4 bytes is a uint represents length of packet 210 | if (buffer.Length < 4) 211 | { 212 | packet = default; 213 | return false; 214 | } 215 | 216 | uint length; 217 | //read length uint (this length includes the length of the length, 4 bytes) 218 | if (buffer.IsSingleSegment) 219 | { 220 | var b = buffer.FirstSpan[0]; 221 | length = Unsafe.ReadUnaligned(ref b); 222 | } 223 | else 224 | { 225 | Span firstFourBytes = stackalloc byte[4]; 226 | buffer.Slice(buffer.Start, 4).CopyTo(firstFourBytes); 227 | length = Unsafe.ReadUnaligned(ref firstFourBytes[0]); 228 | } 229 | 230 | // Read the packet 231 | if (buffer.Length < length) 232 | { 233 | packet = default; 234 | return false; 235 | } 236 | 237 | packet = buffer.Slice(4, length - 4); 238 | buffer = buffer.Slice(length); 239 | return true; 240 | } 241 | 242 | public void Send(Memory data) 243 | { 244 | try 245 | { 246 | if (!_isDispose) 247 | { 248 | Socket.Send(data.Span); 249 | } 250 | } 251 | catch 252 | { 253 | Close(); // "connection has been closed" 254 | } 255 | } 256 | 257 | public async ValueTask SendAsync(Memory data) 258 | { 259 | try 260 | { 261 | if (!_isDispose) 262 | { 263 | await Socket.SendAsync(data, SocketFlags.None); 264 | } 265 | } 266 | catch 267 | { 268 | Close(); // "connection has been closed" 269 | } 270 | } 271 | 272 | public void Close() 273 | { 274 | if (!_isDispose) 275 | { 276 | _isDispose = true; 277 | try 278 | { 279 | try 280 | { 281 | Socket.Close(); 282 | } 283 | catch 284 | { 285 | //ignore 286 | } 287 | 288 | Socket.Dispose(); 289 | GC.SuppressFinalize(this); 290 | } 291 | catch (Exception) 292 | { 293 | //ignore 294 | } 295 | 296 | OnClose?.Invoke(); 297 | } 298 | } 299 | } -------------------------------------------------------------------------------- /UpUpServer/Core/CoreImpl/TimerManager/Timer.cs: -------------------------------------------------------------------------------- 1 | 2 | namespace UpUpServer 3 | { 4 | public abstract class Timer 5 | { 6 | #region 定时器属性 7 | /// 8 | /// How long the timer takes to complete from start to finish. (ms) 9 | /// 10 | public long duration { get; protected set; } 11 | 12 | /// 13 | /// whether the timer is persistence. 14 | /// when set to true, this timer is unaffected by pause and cancel operations. If this timer is attached to an object, the timer will also be destroyed when the object is destroyed. 15 | /// 16 | public bool isPersistence { get; } 17 | 18 | public bool isCompleted { get; protected set; } 19 | 20 | public bool isPaused 21 | { 22 | get { return this._timeElapsedBeforePause.HasValue; } 23 | } 24 | 25 | public bool isCancelled 26 | { 27 | get { return this._timeElapsedBeforeCancel.HasValue; } 28 | } 29 | 30 | public bool isDone 31 | { 32 | get { return this.isCompleted || this.isCancelled || this.isOwnerDestroyed; } 33 | } 34 | 35 | public object? autoDestroyOwner { get; } 36 | public bool hasAutoDestroyOwner { get; } 37 | private bool isOwnerDestroyed 38 | { 39 | get 40 | { 41 | if (!hasAutoDestroyOwner || autoDestroyOwner==null) return false; 42 | if (!_timeElapsedBeforeAutoDestroy.HasValue) 43 | _timeElapsedBeforeAutoDestroy = GetTimeElapsed(); 44 | return true; 45 | } 46 | } 47 | 48 | #endregion 49 | 50 | #region 静态创建方法 51 | 52 | public static DelayTimer DelayAction(long duration, Action onComplete, 53 | Object autoDestroyOwner = null) 54 | { 55 | return DelayActionInternal(false, duration, onComplete, null, autoDestroyOwner); 56 | } 57 | public static DelayTimer DelayAction(long duration, Action onComplete, Action onUpdate, 58 | Object autoDestroyOwner = null) 59 | { 60 | return DelayActionInternal(false, duration, onComplete, onUpdate, autoDestroyOwner); 61 | } 62 | 63 | public static LoopTimer LoopAction(long interval, Action onUpdate, 64 | bool executeOnStart = false, Object autoDestroyOwner = null) 65 | { 66 | return LoopActionInternal(false, interval, null, onUpdate, executeOnStart, 67 | autoDestroyOwner); 68 | } 69 | public static LoopTimer LoopAction(long interval, Action onComplete, Action onUpdate, 70 | bool executeOnStart = false, Object autoDestroyOwner = null) 71 | { 72 | return LoopActionInternal(false, interval, onComplete, onUpdate, executeOnStart, 73 | autoDestroyOwner); 74 | } 75 | 76 | public static DelayTimer PersistenceDelayAction(long duration, Action onComplete, 77 | Action onUpdate, Object autoDestroyOwner = null) 78 | { 79 | return DelayActionInternal(true, duration, onComplete, onUpdate, autoDestroyOwner); 80 | } 81 | 82 | public static LoopTimer PersistenceLoopAction(long interval, Action onComplete, Action onUpdate, 83 | bool executeOnStart = false, Object autoDestroyOwner = null) 84 | { 85 | return LoopActionInternal(true, interval, onComplete, onUpdate, executeOnStart, 86 | autoDestroyOwner); 87 | } 88 | 89 | 90 | 91 | /// 92 | /// Restart a timer. The main benefit of this over the method on the instance is that you will not get 93 | /// a if the timer is null. 94 | /// 95 | /// The timer to restart. 96 | public static void Restart(Timer timer) 97 | { 98 | if (timer != null) 99 | { 100 | timer.Restart(); 101 | } 102 | } 103 | 104 | /// 105 | /// Cancels a timer. The main benefit of this over the method on the instance is that you will not get 106 | /// a if the timer is null. 107 | /// 108 | /// The timer to cancel. 109 | public static void Cancel(Timer timer) 110 | { 111 | if (timer != null) 112 | { 113 | timer.Cancel(); 114 | } 115 | } 116 | 117 | /// 118 | /// Pause a timer. The main benefit of this over the method on the instance is that you will not get 119 | /// a if the timer is null. 120 | /// 121 | /// The timer to pause. 122 | public static void Pause(Timer timer) 123 | { 124 | if (timer != null) 125 | { 126 | timer.Pause(); 127 | } 128 | } 129 | 130 | /// 131 | /// Resume a timer. The main benefit of this over the method on the instance is that you will not get 132 | /// a if the timer is null. 133 | /// 134 | /// The timer to resume. 135 | public static void Resume(Timer timer) 136 | { 137 | if (timer != null) 138 | { 139 | timer.Resume(); 140 | } 141 | } 142 | 143 | public static void CancelAllRegisteredTimersByOwner(Object owner) 144 | { 145 | if (_manager != null) 146 | { 147 | _manager.CancelAllTimersByOwner(owner); 148 | } 149 | 150 | // if the manager doesn't exist, we don't have any registered timers yet, so don't 151 | // need to do anything in this case 152 | } 153 | 154 | public static void CancelAllRegisteredTimers() 155 | { 156 | if (_manager != null) 157 | { 158 | _manager.CancelAllTimers(); 159 | } 160 | 161 | // if the manager doesn't exist, we don't have any registered timers yet, so don't 162 | // need to do anything in this case 163 | } 164 | 165 | public static void PauseAllRegisteredTimers() 166 | { 167 | if (_manager != null) 168 | { 169 | _manager.PauseAllTimers(); 170 | } 171 | 172 | // if the manager doesn't exist, we don't have any registered timers yet, so don't 173 | // need to do anything in this case 174 | } 175 | 176 | public static void ResumeAllRegisteredTimers() 177 | { 178 | if (_manager != null) 179 | { 180 | _manager.ResumeAllTimers(); 181 | } 182 | 183 | // if the manager doesn't exist, we don't have any registered timers yet, so don't 184 | // need to do anything in this case 185 | } 186 | 187 | #endregion 188 | 189 | #region Public Methods 190 | 191 | /// 192 | /// Restart a timer that is in-progress or done. The timer's on completion callback will not be called. 193 | /// 194 | public void Restart() 195 | { 196 | //auto destroy. return 197 | if (isOwnerDestroyed) return; 198 | 199 | isCompleted = false; 200 | _startTime = GetWorldTime(); 201 | _lastUpdateTime = _startTime; 202 | _timeElapsedBeforeCancel = null; 203 | _timeElapsedBeforePause = null; 204 | _timeElapsedBeforeAutoDestroy = null; 205 | OnRestart(); 206 | Register(); 207 | } 208 | 209 | 210 | /// 211 | /// Stop a timer that is in-progress or paused. The timer's on completion callback will not be called. 212 | /// 213 | public void Cancel() 214 | { 215 | if (this.isDone) 216 | { 217 | return; 218 | } 219 | 220 | this._timeElapsedBeforeCancel = this.GetTimeElapsed(); 221 | this._timeElapsedBeforePause = null; 222 | } 223 | 224 | /// 225 | /// Pause a running timer. A paused timer can be resumed from the same point it was paused. 226 | /// 227 | public void Pause() 228 | { 229 | if (this.isPaused || this.isDone) 230 | { 231 | return; 232 | } 233 | 234 | this._timeElapsedBeforePause = this.GetTimeElapsed(); 235 | } 236 | 237 | /// 238 | /// Continue a paused timer. Does nothing if the timer has not been paused. 239 | /// 240 | public void Resume() 241 | { 242 | if (!this.isPaused || this.isDone) 243 | { 244 | return; 245 | } 246 | 247 | this._timeElapsedBeforePause = null; 248 | } 249 | 250 | /// 251 | /// Get how many seconds/frame have elapsed since the start of this timer's current cycle. 252 | /// 253 | /// The number of seconds that have elapsed since the start of this timer's current cycle, i.e. 254 | /// the current loop if the timer is looped, or the start if it isn't. 255 | /// 256 | /// If the timer has finished running, this is equal to the duration. 257 | /// 258 | /// If the timer was cancelled/paused, this is equal to the number of seconds that passed between the timer 259 | /// starting and when it was cancelled/paused. 260 | public long GetTimeElapsed() 261 | { 262 | if (this.isCompleted) 263 | { 264 | return this.duration; 265 | } 266 | 267 | return this._timeElapsedBeforeCancel ?? 268 | this._timeElapsedBeforePause ?? 269 | this._timeElapsedBeforeAutoDestroy ?? 270 | this.GetWorldTime() - this._startTime; 271 | } 272 | 273 | /// 274 | /// Get how many seconds/frame remain before the timer completes. 275 | /// 276 | /// The number of seconds that remain to be elapsed until the timer is completed. A timer 277 | /// is only elapsing time if it is not paused, cancelled, or completed. This will be equal to zero 278 | /// if the timer completed. 279 | public long GetTimeRemaining() 280 | { 281 | return this.duration - this.GetTimeElapsed(); 282 | } 283 | 284 | /// 285 | /// Get how much progress the timer has made from start to finish as a ratio. 286 | /// 287 | /// A value from 0 to 1 indicating how much of the timer's duration has been elapsed. 288 | public long GetRatioComplete() 289 | { 290 | return this.GetTimeElapsed() / this.duration; 291 | } 292 | 293 | /// 294 | /// Get how much progress the timer has left to make as a ratio. 295 | /// 296 | /// A value from 0 to 1 indicating how much of the timer's duration remains to be elapsed. 297 | public long GetRatioRemaining() 298 | { 299 | return this.GetTimeRemaining() / this.duration; 300 | } 301 | 302 | #endregion 303 | 304 | #region Private Static Methods 305 | 306 | public static void InitTimerManager() 307 | { 308 | if (_manager != null) return; 309 | // create a manager object to update all the timers if one does not already exist. 310 | _manager = TimerManager.Instance; 311 | } 312 | 313 | private static DelayTimer DelayActionInternal(bool isPersistence, long duration, Action onComplete, 314 | Action? onUpdate, Object autoDestroyOwner) 315 | { 316 | //Check 317 | if (duration <= 0) 318 | { 319 | SafeCall(onUpdate, 0); 320 | SafeCall(onComplete); 321 | return null; 322 | } 323 | 324 | var timer = new DelayTimer(isPersistence, duration, onComplete, onUpdate, autoDestroyOwner); 325 | timer.Init(); 326 | return timer; 327 | } 328 | 329 | private static LoopTimer LoopActionInternal(bool isPersistence, long interval, Action onComplete, Action onUpdate, 330 | bool executeOnStart, Object autoDestroyOwner) 331 | { 332 | var timer = new LoopTimer(isPersistence, interval, onComplete, onUpdate, executeOnStart, autoDestroyOwner); 333 | timer.Init(); 334 | return timer; 335 | } 336 | 337 | #endregion 338 | 339 | #region Private Static Properties/Fields 340 | 341 | // responsible for updating all registered timers 342 | private static TimerManager _manager; 343 | 344 | #endregion 345 | 346 | #region Private/Protected Properties/Fields 347 | 348 | 349 | // whether the timer is in TimeManager 350 | public bool _isInManager; 351 | 352 | protected Action? _onUpdate; 353 | protected long _startTime; 354 | protected long _lastUpdateTime; 355 | 356 | // for pausing, we push the start time forward by the amount of time that has passed. 357 | // this will mess with the amount of time that elapsed when we're cancelled or paused if we just 358 | // check the start time versus the current world time, so we need to cache the time that was elapsed 359 | // before we paused/cancelled/autoDestroy 360 | private long? _timeElapsedBeforeCancel; 361 | private long? _timeElapsedBeforePause; 362 | private long? _timeElapsedBeforeAutoDestroy; 363 | 364 | public readonly LinkedListNode _linkedListNode; 365 | 366 | #endregion 367 | 368 | #region Constructor (use static method to create new timer) 369 | 370 | static Timer() 371 | { 372 | InitTimerManager(); 373 | } 374 | 375 | protected Timer(bool isPersistence, long duration, Action? onUpdate, 376 | object autoDestroyOwner) 377 | { 378 | this.isPersistence = isPersistence; 379 | this.duration = duration; 380 | this._onUpdate = onUpdate; 381 | 382 | this.autoDestroyOwner = autoDestroyOwner; 383 | this.hasAutoDestroyOwner = autoDestroyOwner != null; 384 | 385 | _linkedListNode = new LinkedListNode(this); 386 | } 387 | 388 | #endregion 389 | 390 | #region Private/Protected Methods 391 | 392 | private void Init() 393 | { 394 | _startTime = GetWorldTime(); 395 | _lastUpdateTime = _startTime; 396 | Register(); 397 | OnInit(); 398 | } 399 | 400 | private void Register() 401 | { 402 | if (_isInManager) return; 403 | _isInManager = true; 404 | _manager.Register(this); 405 | } 406 | 407 | protected long GetFireTime() 408 | { 409 | return _startTime + duration; 410 | } 411 | 412 | protected virtual long GetWorldTime() 413 | { 414 | // 获得当前时间 415 | return TimerManager.Instance.TimerMs; 416 | // return TimerManager.CurrentMs; 417 | 418 | } 419 | 420 | public virtual void OnInit() 421 | { 422 | } 423 | 424 | public abstract void Update(); 425 | 426 | public virtual void OnRestart() 427 | { 428 | } 429 | 430 | protected bool CheckUpdate() 431 | { 432 | if (isDone) return false; 433 | 434 | if (isPaused) 435 | { 436 | var curTime = GetWorldTime(); 437 | _startTime += curTime - _lastUpdateTime; 438 | _lastUpdateTime = curTime; 439 | return false; 440 | } 441 | 442 | _lastUpdateTime = GetWorldTime(); 443 | return true; 444 | } 445 | 446 | protected static void SafeCall(Action? action) 447 | { 448 | if (action is null) return; 449 | try 450 | { 451 | action(); 452 | } 453 | catch (Exception e) 454 | { 455 | Log.Error(e); 456 | } 457 | } 458 | 459 | protected static void SafeCall(Action? action, T arg) 460 | { 461 | if (action is null) return; 462 | try 463 | { 464 | action(arg); 465 | } 466 | catch (Exception e) 467 | { 468 | Log.Error(e); 469 | } 470 | } 471 | 472 | protected static TResult SafeCall(Func? func) 473 | { 474 | if (func is null) return default; 475 | try 476 | { 477 | return func(); 478 | } 479 | catch (Exception e) 480 | { 481 | Log.Error(e); 482 | return default; 483 | } 484 | } 485 | 486 | protected static TResult SafeCall(Func? func, T arg) 487 | { 488 | if (func is null) return default; 489 | try 490 | { 491 | return func(arg); 492 | } 493 | catch (Exception e) 494 | { 495 | Log.Error(e); 496 | return default; 497 | } 498 | } 499 | 500 | #endregion 501 | 502 | 503 | 504 | } 505 | } 506 | --------------------------------------------------------------------------------