# Engine **Repository Path**: chess_star/Engine ## Basic Information - **Project Name**: Engine - **Description**: 通用服务器引擎(常用库封装) - **Primary Language**: C++ - **License**: MIT - **Default Branch**: master - **Homepage**: None - **GVP Project**: No ## Statistics - **Stars**: 0 - **Forks**: 47 - **Created**: 2024-09-12 - **Last Updated**: 2024-09-12 ## Categories & Tags **Categories**: Uncategorized **Tags**: None ## README # Engine C++服务器编程底层库 ## 特点 1. Windows,Linux双平台(Windows下为静态库,主要方便开发者调试;Linux下为动态库,用于生产环境部署) 2. 基本包含集成服务器常用模块(数学、文件系统、配置、日志、网络、脚本、时间、多线程等) 3. 二次开发无平台配置,无其他依赖 4. 基于C++11开发 ## 使用 项目使用[xmake](https://xmake.io/#/)管理,使用方法详见xmake手册 > 注:Linux下建议使用GCC的`-Wl,-rpath,.`连接选项指定运行期动态连接库的优先查找目录,以方便分发部署 ## 集成第三方说明 1. Zip使用[miniz](https://github.com/richgel999/miniz/releases/download/2.1.0/miniz-2.1.0.zip) v2.1.0. 2. [Lua](http://www.lua.org/ftp/lua-5.3.5.tar.gz) v5.3.5. 3. 集成[Jsoncpp](https://github.com/open-source-parsers/jsoncpp) v1.8.4. 4. [hiredis](https://github.com/wasppdotorg/hiredis-for-windows) v0.13.3. ## 模块 ### 程序模型 ```cpp #include class GameApp : public Application { public: GameApp() {} virtual bool OnInit(const Commandline & cmd) override { if (cmd.Has("--debug")) { Logger::Instance().Initialize("game", "logs", Logger::Debug); } else { Logger::Instance().Initialize("game", "logs", Logger::Info); } /// 锁帧 LockFPS(20); /// 填写其他初始化逻辑,当返回false时程序直接退出 return true; } virtual void OnBreath() override { /// 这里填写需要每帧更新的逻辑 } }; RUN_APP(GameApp) ``` ### 网络模型 头文件`socket.h`,一个简单的客户端实现: ```cpp class Client : public IOListener { public: Client() : _socket(new Socket()) { } ~Client() { Close(); delete _socket; } bool Connect(const std::string & ip, int port) { bool succ = _socket->Connect(ip, port); if (succ) BindIO(_socket->ctx, IO_READ); //! 我们只需要网络有数据来时通知 return succ; } /// 实现数据可读时消息处理 virtual void OnReadable() override { char buf[1024] = {0}; int readed = 0; int recv = 0; while (true) { recv = _socket->Recv(buf, 1024); if (recv > 0) { //! TODO: process received data. } else if (recv == 0) { break; } else { Close(recv); break; } } } void Close(int reason = 0) { UnbindIO(); //! 关闭之前请取消IO事件监听 _socket->Close(); } bool Send(const char * p, size_t s) { return _socket->Send(p, s); } private: Socket * _socket; } ``` ### 脚本 > 1. 设计原则:Lua只负责逻辑,对象生存管理交由C++(可以注册管理到Lua) > 2. 涉及到Get操作,需要try...catch以捕获类型异常(C++注册到Lua的接口内Get不需要,调用函数不需要) > 3. Property可以为地址方式,也可以为Getter(TG (void))、Setter(void (TS))方式注册 > 4. Method必须为`int (*f)(LuaState &)` > 5. Lua不可用于多线程,只能在主线程中使用,但可以使用协程。 ```cpp #include /// 注册公共变量或函数到Lua GLua.Register("GameSetting") //! 所有下面注册的属性或函数放在GameSetting中 .Property("nPlayerCounter", &GPlayerCount, false) //! 以地址方式注册属性,同时设置不可写 .Property("nTime", &GetTime) //! 以Getter方式注册属性,同时不注册属性的写方法(不可写) .Method("GetAById", &GetAById); //! 注册全局Lua方法 /// 注册C++类到Lua GLua.Register("LuaA") .Property("nId", &A::id, false) .Property("sName", &A::GetName, &A::SetName) .Method("Msg", &A::SendMessage); try { A * p = GLua.Get("me"); // 需要使用try,因为可能类型不匹配 } catch (...) {} if (GLua.Is("me")) {} // 不需要try GLua.Set("me", new A) // 不需要try GLua.Call("GameSetting", "GetAById", false, 100); // 不需要try int GetAById(LuaState & r) { int n = r.Get(1); // 不需要try ... } ``` LUA中扩展C++注册的类或名空间(注只能扩展方法,不可扩展属性) ```lua -- 扩展名空间的方法 function XXX.yyy() end -- 扩展类静态方法 function LuaA.Test() print("hehe") end -- 扩展类成员方法,注意:这里用的是':',因为需要使用self function LuaA.apis:YYY() end ``` 内置的其他基本函数 | 函数 | 功能 | | --- | --- | | print(...) | 使用Logger重载的print接口[Logger::Level::Info] | | print_err(...) | 使用Logger重载的print接口[Logger::Level::Error] | | loadbits(n, start, end) -> integer | 读取一个int32中[start, end]字节表示的值 | | setbits(n, start, end, v) -> integer | 设置一个int32中[start, end]字节表示的值 | | json.encode(v) -> string | 将lua变量序列化成json字串 | | json.decode(s) -> var | 将json字串反序列化成lua值 | | scheduler.timer(delay, func[, is_loop]) -> integer(id) | 注册一个定时器 | | scheduler.task(hour, min, sec, func) -> integer(id) | 注册一个每天hour:min:sec执行的操作 | | scheduler.is_valid(id) -> bool | 测试一个定时器或计划是否存在 | | scheduler.remain(id) -> double | 返回一个定时器或计划还需要多少毫秒运行 | | scheduler.cancel(id) | 取消一个定时器或计划任务 | ### 内存池 > 1. 由于本人能力有限,经实际效率测试,目前仅保留非线程安全的对象Pool(Pool.h) > 2. Pool加锁后可用于多线程,但经测试效率还不及系统的new,但Linux下相差不大,如果考虑到无内存碎片的优点,可以自行添加。 > 3. 如果采用Application的模型,Pool基本上是够用的。因为逻辑主要在主线程的Tick中触发 ### 线程池模型 First. 编写线程内的具体工作类,继承IThreadJob. ```cpp #include class DemoTask : public IThreadJob { public: DemoTask(...) { ... } //! 这里为该工作参数初始化 virtual ~DemoTask() { ... } //! 这里为工作结束时清理操作 virtual void OnRun() { ... } //! 工作的具体内容 private: ... //! 参数声明 }; ``` Second. 创建线程池及工作对象容器 ```cpp int main() { Threads workers(4); //! 创建含有一个4个工作线程的容器 /// 增加100个并发任务(多余的会暂时等待空闲线程) for (int i = 0; i < 100; ++i) { workers.AddJob(...); // 传入工作需要的参数,这里自动调用 new DemoTask(...); } /// 等待所有的工作结束,如果不执行该操作,mgr超出生存期时会放弃未执行的任务。 workers.Wait(); return 0; } ``` ### Redis客户端 比较简单,请自行阅读`redis.h`,`redis.cc` ### 定时任务 1. C++接口 ```cpp /// 添加一个500毫秒后执行的定时器 GScheduler.Add(500, [](uint64_t id) { printf("Timer's id : %llu", id); }); /// 添加一个每500毫秒执行一次的定时器 GScheduler.Add(500, [](uint64_t id) { printf("Timer's id : %llu", id); }, true); /// 注册每天05:00:00时执行的计划任务 GScheduler.Add(5, 0, 0, [](uint64_t id) { printf("Task's id : %llu", id); }); /// 是否存在定时器或计划任务 bool valid = GScheduler.IsValid(timer_id); /// 取得一个定时器或计划多少毫秒后执行 double left = GScheduler.GetRemainTime(timer_id); /// 取消一个定时器或计划任务 GScheduler.Cancel(timer_id); ``` 2. Lua接口 ```lua scheduler.timer(500, function(id) end); scheduler.timer(500, function(id) end, true); scheduler.task(5, 0, 0, function(id) end); scheduler.is_valid(timer_id); scheduler.remain(timer_id); scheduler.cancel(timer_id); ``` ### 日志 1. 多线程安全 2. 日志使用之前可以初始化(不是必要的,但建议初始化—) 日志生成的结构说明 RootOfLogs 指定的日志根目录 |-- 20160803 首先日志会根据“年月日”分文件夹 | |-- main_01_00_00.000.log 其次日志会按指定大小分文件记录,文件名为指定的"Name_时_分_秒.毫秒.log" | |-- main_01_27_18.193.log ```cpp /// 初始化日志。日志名为main, 放在logs目录下,输出等级为DEBUG,每个文件最大为4M Logger::Instance().Initialize("main", "logs", Logger::Debug, 4 * 1024 * 1024); /// 写日志 LOG_INFO("Hello"); LOG_DEBUG("Hello %d", 2); LOG_ERR("Error : %s", "Test"); LOG_WARN("You have a warning"); ``` ### HASH 头文件 `Crypto.h` 1. CRC32算法:`uint32_t CalcCRC(const char * mem, size_t size, uint32_t pre_crc = 0)` 2. BKDRHash算法:`uint32_t CalcHash(const char * mem, size_t size)` 3. MD5算法:`class MD5` 4. SHA-1算法:`class SHA1` 5. HMAC-SHA1算法:`class HMAC_SHA1` ### 工具库 1. 单例模型:Singleton (singleton.h) 2. 生存域模型:ScopeGuard (scope_gurad.h) 3. ZIP压缩/解压缩算法:Zip(zip.h) 4. 字符串操作:string_tools.h ### 配置库 1. CSV文件的读取见:CsvFile(csv.h) 2. INI文件的读取见:IniFile(ini.h) 3. JSON文件,使用jsoncpp:Json::Value, Json::Reader (json.h) 4. 命令行解析: Flags (flags.h) ### 系统相关 头文件`os.h`。包含: * 高精度时间: OS::Tick() OS::Now() OS::GetTimeZone() OS::ParseDataTime(year, month, day, hour, min, sec) * 文件系统: OS::Exists(path) OS::CreateDir(dir) OS::GetCWD() OS::SetCWD(path) OS::GetFullPath(path) OS::GetDirName(path) OS::GetFileName(path) OS::GetFiles(path, recursive) * 创建GUID/uuid OS::CreateID();