# ServerFrameworkCpp **Repository Path**: wujehy/server-framework-cpp ## Basic Information - **Project Name**: ServerFrameworkCpp - **Description**: 一个C++ 的 C/S 架构的框架 - **Primary Language**: C++ - **License**: AGPL-3.0 - **Default Branch**: master - **Homepage**: None - **GVP Project**: No ## Statistics - **Stars**: 2 - **Forks**: 4 - **Created**: 2020-09-11 - **Last Updated**: 2025-04-01 ## Categories & Tags **Categories**: web-dev-toolkits **Tags**: None ## README # ServerFrameworkCpp #### 介绍 一个c++ 实现的 C/S 架构 使用 std + boost 进行搭建了跨平台 ,易集成, 模块化 使用裁剪后的 boost 1.70 日志系统基于对glog 框架无内存泄漏 适用于初学者,初级商业项目. ## 基本教程:添加一个简单的模块 添加一个业务模块只需要2步,继承基类实现业务 第二步,注册模块到管理器. *注:目前的模块存放到Mod目录下,可以阅览源码查看* 创建一个类 TestMod.cpp /TestMod.h 继承 GeeJoan::BaseMod 基类, 并实现 init mod_type procMessage ```c++ //file : TestMod.h // 集成基类实现模块的功能 namespace GeeJoan { class TestMod : public BaseMod { public: void init() override; ModTypeEnum mod_type() override; void procMessage(const HanderDataType *hander, StatusCodeTypeEnum &code, std::string &resposeBody) override; }; } /* * // 如果不想用GeeJoan 命名空间就 如下 class TestMod:public GeeJoan::BaseMod{ public: GeeJoan::ModTypeEnum mod_type() override; void procMessage(const GeeJoan::HanderDataType *hander, GeeJoan::StatusCodeTypeEnum &code, std::string &resposeBody) override; void init() override; }; */ ``` ```c++ //file : TestMod.cpp // 引入基本的头 #include //基础类型头 #include "TestMod.h" // 模块头 #include "NetworkProtocol.h" //网络包业务类型头  // 这个是日志系统的的 #include "glog/logging.h" using namespace GeeJoan; // 返回模块的类型 ModTypeEnum TestMod::mod_type() { return ModType_TestMod; } // 当收到该类型的业务的时候会触发这个方法 // hander 本次包的基本信息,如当前链接id,业务类型 ,业务子类型,以及客户端的消息等 // code 是 StatusCodeTypeEnum 返回状态码的枚举类型 // resposeBody 是要返回给客户端的数据 // 处理过程就是当收到这个数据包的时候 填充 处理过程的code 和需要返回给客户端的 resposeBody 即可 void TestMod::procMessage(const HanderDataType *hander, StatusCodeTypeEnum &code, std::string &resposeBody) { LOGINFO << " TestMod::procMessage client[" << int( hander->clientid ) << "] subtype = " << hander->subtype << " msg = " << hander->message; if (hander->subtype == OpTestModSubType_Test2) { // 当子业务是Test2 的时候 code = Status_SUCCESS; // 返回成功 resposeBody = "end"; //返回end } else { // 同上 其他均返回pong code = Status_SUCCESS; resposeBody = "pong"; } // 如果觉得复杂, 注释上面的代码直接使用 下面的两行实现一个响应操作, 不处理子类型 // code = Status_SUCCESS; // resposeBody = "pong"; } ``` 实现完业务,在管理器注册 ```c++ void ModManager::registerMOD() { auto do_reg_fun = [&](BaseMod *mod, bool auto_del = true) { LOGINFO << " register type " << mod->mod_type(); auto pt = m_mod_map.insert( {mod->mod_type(), mod} ); if (pt.second == false && auto_del == true) { delete mod; } return pt.second; }; // TODO 注册你的模块 do_reg_fun( new TestMod ); // 测试模块 do_reg_fun( new AuthMod ); //鉴权模块 // NOTE wujehy 注册 新的业务 do_reg_fun( new KeyValueMod ); // KV } ``` 然后编译运行 Test模块的业务就完成了,下载 预览版可以运行体验,windows 10, ubuntu20.04 (依赖库基本解决,如果不能解决自行源码编译 , 推荐使用 linux 系统或者mingw) 目前支持的客户端指令 ``` >>> calltest1 --- > pong 回包测试 >>> calltest2 --- > 压力测试 发送10000 条包 然后发end 包结束 >>> closelog ---> 关闭日志 , 测试 calltest2 的时候关掉可以减少输出 >>> showlog ---> 展示日志 , 重新打开日志 >>> showtime ---> 查看 calltest2 测试用时 >>> q ---> 退出程序 >>> ls ---> 查看支持的 指令列表 >>> set ---> 设置一个 key—value >>> get ---> 读取 key 的值 ``` # 基本概述 仓库下载: git clone https://gitee.com/wujehy/server-framework-cpp 仓库初始化: git submodule update --init --recursive 编译步骤 : ``` mkdir build cd build cmake .. cmake --build . ``` 服务端实现: tests/testServer.cpp MainServer/* 客户端实现: tests/testClient.cpp SubClient/* # 测试 目前测私案例仅有 Test 模块 如果需要实例项目将移植到GeeJoanServerCpp 完整实现 先运行服务端 默认端口号10000 #### 性能测试日志: 数据仅供参考 , 处理结果可以打开日志 查看 单位 纳秒: 请求数: 10000 ``` >>>closelog >>>calltest2 send start = 1600053802950994>>> recv taskid = 16 time = 1600053803713844 >>>showtime start Time = 1600053802950994 last time = 1600053803713844 useTime = 762850 >>> ``` 测试请求数量 10000 + 1 条 ```c++ void Test2Function(GeeJoan::ClientService *client) { auto callbackStatus = [](int status ){ LOGINFO<<"semd callback status = " << status; }; cache_startTime = getTimeMicro(); std::cout <<" send start = " <send(ModType_TestMod , OpTestModSubType_Test1 , "test1" ,callbackStatus); } client->send(ModType_TestMod , OpTestModSubType_Test2 , "end" ,callbackStatus); } ``` ### 服务端测试案例: ```c++ AppManager appManager; //设置端口 appManager.setPort(10000); appManager.setLogPath("."); appManager.init_local(); app = &appManager; signal(SIGINT, [](int s) { LOGINFO << " exit "; app->stop(); }); signal(SIGTERM, [](int s) { LOGINFO << " exit "; app->stop(); }); app->init_network(); app->run(); ``` ### 客户端操作: 启动 ``` $ ./Client 127.0.0.1 10000  ``` 启动日志 ``` init input `ls` show command list >>> I20200913 03:34:46.232203 36651 ClientService.cpp:191] init client I20200913 03:34:46.232262 36651 ClientModManager.cpp:28] register type 99 I20200913 03:34:46.232318 36653 ThreadPool.cpp:52] ThreadPool::work() .... ``` 客户端支持短线重链,重连日志: 断线后5s 周期重联展, 实现代码: ClientService.cpp::do_reconnect() ``` I20200913 03:36:19.037448 36655 ClientService.cpp:103] close id I20200913 03:36:19.037559 36655 ClientService.cpp:201] reconnect doing ... I20200913 03:36:19.037601 36655 ClientService.cpp:114] ip = 127.0.0.1 port 10000 I20200913 03:36:19.037636 36655 ClientService.cpp:26] ClientService::do_connect I20200913 03:36:24.037806 36655 ClientService.cpp:31] do_connect I20200913 03:36:24.037829 36655 ClientService.cpp:44] link fail I20200913 03:36:24.037847 36655 ClientService.cpp:201] reconnect doing ... I20200913 03:36:24.037858 36655 ClientService.cpp:114] ip = 127.0.0.1 port 10000 I20200913 03:36:24.037870 36655 ClientService.cpp:26] ClientService::do_connect ``` 查看已支持指令: ``` >>> ls cmd : calltest1 cmd : ls ``` 因为日志的原因 ">>>" 总是会被覆盖,能输入就直接输入即可 cmd 后面对应 指令执行的动作  注册方法在战: tests/testClient.cpp 的 void registerFunction()  函数具体操作在 ClientCmdFunction 实现 执行测试方法 客户端日志: ``` >>>calltest1 >>>I20200913 03:40:56.144132 36656 testClient.cpp:82] start input I20200913 03:40:56.144166 36655 ClientService.cpp:134] send doing msg = ca test1 I20200913 03:40:56.144222 36655 ClientService.cpp:137] send doing write_in_progress = 0 I20200913 03:40:56.144248 36655 ClientService.cpp:141] call send doing I20200913 03:40:56.144266 36655 ClientCmdFunction.cpp:15] semd callback status = 0 I20200913 03:40:56.144287 36655 ClientService.cpp:156] do write I20200913 03:40:56.144443 36655 ClientService.cpp:172] do do_write false I20200913 03:40:56.145383 36655 ClientService.cpp:85] do_read start I20200913 03:40:56.146044 36653 ClientModManager.cpp:47] find register mod type = 97 I20200913 03:40:56.146811 36653 TestClientMod.cpp:19] TestClientMod::procMessage modtype = 99,subtype = 97,ctaskid = 0,code = 0,len = 4 ``` 服务端日志: ``` I20200913 03:40:56.144481 36841 Connection.cpp:74] recv msg [0]:ca test1 I20200913 03:40:56.144974 36847 Connection.cpp:87] TODO proc message : ca test1 I20200913 03:40:56.145028 36847 ModManager.cpp:61] find register mod type = 99 I20200913 03:40:56.145056 36847 TestMod.cpp:23] TestMod::procMessage client[0] subtype = a msg = test1 I20200913 03:40:56.145169 36847 Connection.cpp:108] send [0] : ca test I20200913 03:40:56.145200 36847 NetworkManager.cpp:87] send success ``` 客户端的数据包会被路由到服务器指定的模块, 所以只需要在指定模块实现业务即可 ## 模块注册 客户端测试基类 :BaseClientMod 客户端更具情况自行修改 , 建议参见服务端的模式 服务端模块基类: BaseMod 这个定下来了 服务端的积累 开放接口 描述模块类型 以及处理分发器 继承BaseMod 之后,  ```c++ class BaseMod : public BaseComponent { public: /** * @brief 标记该模块类型是的接口 * @return */ virtual ModTypeEnum mod_type() = 0; /** * @brief 处理消息 * @param hander 客户端向服务端请求的数据包 * @param code 填入需要相应的状态码, 不填默认则为 Fail * @param resposeBody 需要返回给客户端的消息体 */ virtual void procMessage(const HanderDataType *hander, StatusCodeTypeEnum &code, std::string &resposeBody) = 0; /** * @brief 初始化完成统一分配全局上下文, 默认直接传递 * @param context */ virtual void init_complete(Global_Context *context); }; ``` 模块基本组建类型接口: 服务端的所有模块都要继承这个接口, 统一初始化以及全局上下文 传递 ,以及内存释放的析构函数的显式声明 , 保证所有的组建可以内存释放成功 #### 注册模块 模块实现完成 ,在ModManager 将模块注册进去 , 此时正模块的指针管理权转交给 管理器.,由管理器统一管理统一释放 #### 服务端接收到客户端的包 接受到客户端的包的时候, 数据包会发送到 该模块的procMessage 如果没有找到模块路由则会自行释放任务包的内存. ```c++ struct HanderDataType { uint8_t clientid; ///< 服务端储存的客户端id编号 uint8_t modtype; ///< 当前业务模块类型 uint8_t subtype; ///< 业务子类型 uint8_t ctaskid; ///< 客户端标记的任务id std::string message; }; ``` #### 基本回包的例子: 收到包后自行通过业务判断进行message 的处理 然后通过 globalContext->networkManager->sendMsg 吧回包的内容"test" 返回 给客户端 ```c++ void TestMod::procMessage(std::unique_ptr hander) { LOGINFO << " TestMod::procMessage client[" << int( hander->clientid ) << "] subtype = " << hander->subtype << " msg = " << hander->message; std::string message; globalContext->networkManager->sendMsg( std::move( hander ), Status_SUCCESS, "test" ); } ``` 网络接口: 将 hander 转交给 回包 方法,并给定处理状态以及 回包内容之 , 客户端即可获取到回包内容 ``` class BaseNetwork { friend class Connection; virtual int remove_client(int id) = 0; public: /** * @brief 服务端向客户端发送消息 * @param hander 句柄 * @param code 状态码 * @param msg 内容主体 * @param callback 发送状态回调 * @return */ virtual int sendMsg(std::unique_ptr hander, StatusCodeTypeEnum code, const std::string &msg, std::function callback = nullptr) = 0; }; ``` 同理, 客户端的消息路由到: ``` void TestClientMod::procMessage(std::unique_ptr hander) { LOGINFO << " TestClientMod::procMessage " << ResposeDataPackageToString( *hander ); } ``` 日志: ``` I20200913 03:40:56.146811 36653 TestClientMod.cpp:19] TestClientMod::procMessage modtype = 99,subtype = 97,ctaskid = 0,code = 0,len = 4 ``` ### 自定义业务: MainServer/include/NetworkProtocol.h 自行添加与实现 模块并注册则可以实现功能扩展 最大业务类型 255 个, 单个业务子模块 255 ``` // 网络协议 精简 为 前两个 8 bit 为业务 号 和子业务号 enum ModTypeEnum { ModType_AuthMod = 1, // ModType_TestMod = 99, // 为了方便测试这个是 'c' }; enum OpTestModSubTypeEnum { OpTestModSubType_Test1 = 97, // 同上 'a' }; ``` ### 新增业务实现 简单的 Key-Value 缓存服务 KeyValueMod.h