大家好我是Halcyon.平安欢迎文末添加好友交流共同进步一、模块概述二、完整源码 — gobang.cpp三、主函数解析 — main()四、测试函数解析4.1 测试函数对照表4.2 各测试函数详解五、编译脚本 — makefile六、项目架构总结6.1 模块依赖全景图6.2 模块职责总览6.3 请求处理链路汇总6.4 线程模型6.5 数据流七、设计讨论 — 项目的设计亮点与可改进之处7.1 设计亮点7.2 可改进之处八、设计总结一、模块概述gobang.cpp是整个项目的入口文件只有短短 122 行核心就是一个main函数——创建服务器对象、启动服务器。文件中还保留了若干测试函数mysql_test、json_test、string_test、file_test、db_test、online_test是开发过程中用来验证各模块是否正常工作的单元测试不是正式代码的一部分。配套的makefile负责编译整个项目将所有.hpp头文件和gobang.cpp一起编译链接为可执行文件。文件功能说明gobang.cpp主入口创建gobang_server实例启动服务器监听 8888 端口makefile编译脚本g -stdc11编译所有源码并链接依赖库二、完整源码 — gobang.cpp#includeroom.hpp#includeutil.hpp#includeonline.hpp#includesession.hpp#includematcher.hpp#includeserver.hpp#defineHOST127.0.0.1#definePORT3306#defineUSERroot#definePASS123456#defineDBNAMEgobangvoidmysql_test(){MYSQL*mysqlmysql_util::mysql_create(HOST,USER,PASS,DBNAME,PORT);constchar*sqlinsert into stu values(null, 小明, 18, 53, 68, 87);;boolretmysql_util::mysql_exec(mysql,sql);if(retfalse){return;}mysql_util::mysql_destroy(mysql);}voidjson_test(){Json::Value root;std::string body;root[姓名]小明;root[年龄]18;root[成绩].append(98);root[成绩].append(97.5);root[成绩].append(88.5);json_util::serialize(root,body);DLOG(%s,body.c_str());Json::Value val;json_util::unserialize(body,val);std::cout姓名val[姓名].asCString()std::endl;std::cout年龄: val[年龄].asInt()std::endl;intszval[成绩].size();for(inti0;isz;i){std::cout成绩val[成绩][i].asFloat()std::endl;}}voidstring_test(){std::string str123,234,,,345,456;std::vectorstd::stringarry;string_util::split(str,,,arry);for(autos:arry){DLOG(%s,s.c_str());}}voidfile_test(){std::string filename./makefile;std::string body;file_util::read(filename,body);std::coutbodystd::endl;}voiddb_test(){user_tableut(HOST,USER,PASS,DBNAME,PORT);Json::Value user;//user[username] 熊大;//user[password] 123123;//ut.insert(user);boolretut.lose(1);//std::string body;//json_util::serialize(user,body);//std::cout body std::endl;}voidonline_test(){online_manager om;wsserver_t::connection_ptr conn;uint64_tuid2;om.enter_game_hall(uid,conn);if(om.is_in_hall(uid)){DLOG(IN GAME HALL);}else{DLOG(NOT IN GAME HALL);}om.exit_game_hall(uid);if(om.is_in_hall(uid)){DLOG(IN GAME HALL);}else{DLOG(NOT IN GAME HALL);}}intmain(){// 创建五子棋服务器gobang_serversrv(HOST,USER,PASS,DBNAME,PORT);// 启动服务器监听 8888 端口srv.start(8888);return0;}三、主函数解析 — main()intmain(){// 创建五子棋服务器gobang_serversrv(HOST,USER,PASS,DBNAME,PORT);// 启动服务器监听 8888 端口srv.start(8888);return0;}只有两行核心代码但背后触发了整个系统的初始化链第一行gobang_server srv(...)— 创建服务器gobang_server 构造函数 → user_table 构造 — 创建 MySQL 连接 → online_manager 构造 — 初始化两张空哈希表 → room_manager 构造 — 初始化 _next_rid1 → session_manager 构造 — 保存 wsserver_t 指针 → matcher 构造 — 启动三个匹配线程立即开始死循环等待 → 注册四个回调 — http/open/close/message第二行srv.start(8888)— 启动服务器start(8888) → _wssrv.listen(8888) — 监听 8888 端口 → _wssrv.start_accept() — 开始接受连接 → _wssrv.run() — 进入事件循环阻塞run()是阻塞调用内部是boost::asio::io_service::run()的事件循环。当有新连接、新消息、定时器触发等事件时框架自动调用注册的回调函数。main函数在run()返回前不会继续执行。四、测试函数解析文件中保留了 6 个测试函数分别对应 6 个模块的独立验证。开发时可以单独调用某个测试函数不需要启动整个服务器。4.1 测试函数对照表函数测试模块验证内容mysql_test()mysql_utilMySQL 连接创建、SQL 执行、连接销毁json_test()json_utilJSON 序列化/反序列化、中文支持、数组操作string_test()string_util字符串分割连续分隔符、空字段处理file_test()file_util文件读取以 makefile 自身为测试文件db_test()user_table数据库用户操作注册/登录/胜败更新online_test()online_manager在线用户管理加入大厅/判断在线/退出大厅4.2 各测试函数详解mysql_test() — MySQL 工具类测试voidmysql_test(){MYSQL*mysqlmysql_util::mysql_create(HOST,USER,PASS,DBNAME,PORT);constchar*sqlinsert into stu values(null, 小明, 18, 53, 68, 87);;boolretmysql_util::mysql_exec(mysql,sql);if(retfalse){return;}mysql_util::mysql_destroy(mysql);}测试 MySQL 连接的创建→执行 SQL→销毁的完整流程。SQL 语句是往一个stu表插入数据开发测试用的表不是正式的user表。json_test() — JSON 工具类测试voidjson_test(){Json::Value root;root[姓名]小明;root[年龄]18;root[成绩].append(98);root[成绩].append(97.5);root[成绩].append(88.5);json_util::serialize(root,body);DLOG(%s,body.c_str());Json::Value val;json_util::unserialize(body,val);std::cout姓名val[姓名].asCString()std::endl;// ...}测试序列化和反序列化的完整性——构造一个包含中文字符串、整数、浮点数数组的 JSON 对象序列化后再反序列化打印结果验证是否一致。特别注意中文字符支持emitUTF8: true在json_util::serialize中设置。string_test() — 字符串分割测试voidstring_test(){std::string str123,234,,,345,456;std::vectorstd::stringarry;string_util::split(str,,,arry);}测试字符串分割的边界情况——输入123,234,,,345,456连续三个逗号之间会产生空字段。string_util::split的实现会跳过空字段pos idx时continue最终结果应该是[123, 234, 345, 456]。file_test() — 文件读取测试voidfile_test(){std::string filename./makefile;std::string body;file_util::read(filename,body);std::coutbodystd::endl;}以项目自身的makefile作为测试文件验证file_util::read能否正确读取整个文件内容。db_test() — 数据库用户表测试voiddb_test(){user_tableut(HOST,USER,PASS,DBNAME,PORT);Json::Value user;boolretut.lose(1);}测试user_table的操作。注释掉的代码是注册测试插入用户熊大当前激活的是lose(1)— 将 uid1 的玩家天梯分减 30、总场数 1。每次运行这个测试uid1 的玩家的分数都会减少 30。online_test() — 在线用户管理测试voidonline_test(){online_manager om;wsserver_t::connection_ptr conn;// 空指针uint64_tuid2;om.enter_game_hall(uid,conn);// 加入大厅if(om.is_in_hall(uid)){DLOG(IN GAME HALL);// 应该打印这行}om.exit_game_hall(uid);// 退出大厅if(om.is_in_hall(uid)){DLOG(IN GAME HALL);// 不应该打印}}测试在线用户管理的基本流程加入大厅 → 判断在线应该是 true→ 退出大厅 → 判断在线应该是 false。注意conn是空指针但在这个测试中不会用到连接对象只是测试哈希表的增删查功能。五、编译脚本 — makefile.PHONY:clean all: gobang gobang: gobang.cpp server.hpp logger.hpp online.hpp db.hpp util.hpp room.hpp matcher.hpp session.hpp g -stdc11 $^ -o $ -lmysqlclient -ljsoncpp -lpthread -lboost_system clean: rm -f gobang解析行说明.PHONY:clean声明 clean 是伪目标不会被同名文件干扰all: gobang默认目标编译生成 gobang 可执行文件gobang: gobang.cpp server.hpp ...依赖列表所有源文件因为 hpp 全是头文件实现修改任何一个都要重新编译g -stdc11 $^ -o $使用 C11 标准编译$^是所有依赖文件$是目标文件名-lmysqlclient链接 MySQL 客户端库-ljsoncpp链接 JsonCpp 库-lpthread链接 POSIX 线程库matcher 的三个线程需要-lboost_system链接 Boost.System 库WebSocket/Asio 依赖clean: rm -f gobang清理编译产物为什么所有 .hpp 都是依赖项因为本项目采用头文件实现模式——所有类的声明和实现都写在.hpp文件中没有.cpp实现文件。任何.hpp的修改都可能导致gobang.cpp的编译结果不同所以必须把所有.hpp都列为依赖。如果使用传统的声明在 .h、实现在 .cpp的模式makefile 可以更精细地控制增量编译。六、项目架构总结6.1 模块依赖全景图┌─────────────────┐ │ gobang.cpp │ │ (main 入口) │ └────────┬────────┘ │ ┌────────▼────────┐ │ server.hpp │ │ (顶层调度中心) │ └──┬──┬──┬──┬──┬──┘ │ │ │ │ │ ┌────────────┘ │ │ │ └────────────┐ ▼ ▼ ▼ ▼ ▼ ┌──────────┐ ┌──────────────┐ ┌──────────┐ │ matcher │ │ session.hpp │ │ room │ │ .hpp │ │ (会话管理) │ │ .hpp │ └──┬───┬───┘ └──────┬───────┘ └──┬───┬───┘ │ │ │ │ │ │ └───────┬───────┘────────┐ │ │ ▼ ▼ ▼ ▼ │ ┌──────────┐ ┌──────────┐ ┌──────────────┐ │ │ room.hpp │ │ db.hpp │ │ online.hpp │◄──┘ │(房间管理)│ │(用户表) │ │(在线用户管理)│ └──────────┘ └──────────┘ └──────────────┘ │ │ │ └─────┬─────┴────────────────┘ ▼ ┌──────────┐ │ util.hpp │ │(工具类集) │ └────┬─────┘ ▼ ┌──────────┐ │logger.hpp│ │(日志宏) │ └──────────┘6.2 模块职责总览模块文件核心职责关键技术日志模块logger.hpp提供 INF/DBG/ERR 三级日志宏宏定义、__FILE__/__LINE__工具类模块util.hppMySQL/JSON/字符串/文件 四个工具类 wsserver_t类型定义MySQL C API、JsonCpp、fstream数据库模块db.hpp用户表 CRUD注册/登录/查询/胜败更新SQL 拼接、mutex 保护连接会话管理模块session.hppCookie-Session 身份验证、定时销毁、代校验机制recursive_mutex、WebSocket 定时器在线用户模块online.hpp大厅/房间在线状态管理uid→连接映射unordered_map、mutex房间模块room.hpp游戏房间棋盘、胜负判定、聊天、广播 房间管理器shared_ptr、vector 棋盘匹配模块matcher.hpp三段位匹配队列、阻塞/唤醒、匹配线程condition_variable、thread、list服务器模块server.hppHTTP 路由 WebSocket 回调 全流程串联WebSocket、std::bind主入口gobang.cpp创建服务器、启动监听—6.3 请求处理链路汇总HTTP 请求短连接客户端 → HTTP 请求 → http_callback() ├─ POST /reg → reg() → _ut.insert() → 返回注册结果 ├─ POST /login → login() → _ut.login() _sm.create_session() → Set-Cookie ├─ GET /info → info() → Cookie→Session→_ut.select_by_id() → 返回用户信息 └─ 其他 → file_handler() → file_util::read() → 返回静态文件WebSocket 大厅长连接客户端 → WebSocket /hall ├─ 连接建立 → wsopen_game_hall() → 身份验证 → _om.enter_game_hall() → session 永久化 ├─ 收到消息 → wsmsg_game_hall() │ ├─ match_start → _mm.add(uid) — 加入匹配队列 │ └─ match_stop → _mm.del(uid) — 取消匹配 └─ 连接断开 → wsclose_game_hall() → _om.exit_game_hall() → session 恢复定时WebSocket 房间长连接客户端 → WebSocket /room ├─ 连接建立 → wsopen_game_room() → 身份验证 → _rm.get_room_by_uid() → _om.enter_game_room() ├─ 收到消息 → wsmsg_game_room() → rp-handle_request() │ ├─ put_chess → handle_chess() → check_win() → broadcast() │ └─ chat → handle_chat() → broadcast() └─ 连接断开 → wsclose_game_room() → _om.exit_game_room() → _rm.remove_room_user()6.4 线程模型本项目共有以下线程线程数量启动位置职责Asio 事件循环线程1srv.start()→run()处理所有 HTTP/WebSocket I/O 事件普通段位匹配线程1matcher构造函数处理 score 2000 的玩家匹配高手段位匹配线程1matcher构造函数处理 2000 ≤ score 3000 的匹配大神段位匹配线程1matcher构造函数处理 score ≥ 3000 的玩家匹配定时器回调线程若干Asio 内部管理Session 超时销毁合计4——6.5 数据流客户端 服务器 数据库 │ │ │ │ POST /login │ │ │ {username, password} │ │ │ ─────────────────────► │ _ut.login() │ │ │ ──────────────────────────────────► │ │ │ ◄────────────────────────────────── │ │ │ 创建 Session, Set-Cookie │ │ ◄───────────────────── │ │ │ │ │ │ WebSocket /hall │ │ │ ─────────────────────► │ 身份验证 → enter_game_hall │ │ │ │ │ {optype: match_start} │ │ │ ─────────────────────► │ _mm.add(uid) │ │ │ 查分数 ──────────────────────────► │ │ │ ◄────────────────────────────────── │ │ │ push 到匹配队列 │ │ │ │ │ │ 匹配线程两人出队 │ │ │ create_room() │ │ {optype: match_success} │ │ │ ◄───────────────────── │ conn-send() │ │ │ │ │ WebSocket /room │ │ │ ─────────────────────► │ get_room_by_uid → enter_game_room │ │ │ │ │ {optype: put_chess} │ │ │ ─────────────────────► │ handle_chess → check_win │ │ │ _ut.win() / _ut.lose() ──────────► │ │ {winner: xxx} │ │ │ ◄───────────────────── │ broadcast() │七、设计讨论 — 项目的设计亮点与可改进之处7.1 设计亮点亮点体现分层清晰每个 .hpp 负责一个独立职责模块间通过指针/引用松耦合server.hpp 只做串联不实现业务逻辑shared_ptr 管理生命周期room_ptr 在多处引用哈希表、消息线程、匹配线程引用计数归零时自动销毁无内存泄漏三段位匹配生产者-消费者模型 条件变量线程安全且零 CPU 空转Session 代校验通过 timer_gen 防止旧定时器回调误删新 Session解决了定时器替换的竞态问题Cookie-Session登录用 HTTP设置 Cookie实时通信用 WebSocket读 Cookie职责分离合理7.2 可改进之处方面当前实现改进方向敏感词过滤string::find(垃圾)单词匹配DFA / Trie 树支持上千个敏感词的高效匹配分段匹配固定三段位同段内无排序可以加入等待时间因素——等待越久扩大匹配范围跨段匹配数据库连接单连接 mutex连接池多个 MySQL 连接减少锁竞争room 线程安全依赖单线程事件循环无内部锁如果改为多线程处理 WebSocket 消息room 需要加锁错误处理DLOG 打印日志后 return可以引入统一的错误码体系方便前端区分不同错误类型头文件实现所有代码在 .hpp 中大型项目应拆分为 .h 声明 .cpp 实现加快编译速度安全性密码 MD5 存储Cookie 无签名使用 bcrypt/argon2 哈希Cookie 加 HMAC 签名防篡改八、设计总结本文是整个系列博客的最后一篇从gobang.cpp的入口函数出发回顾了整个项目的架构和模块间协作。整个项目从底到顶分为四层第一层基础设施logger.hpp util.hpp — 日志、MySQL、JSON、字符串、文件工具 第二层数据层 db.hpp session.hpp online.hpp — 数据库操作、会话管理、在线状态 第三层业务层 room.hpp matcher.hpp — 游戏逻辑、匹配系统 第四层调度层 server.hpp gobang.cpp — HTTP/WebSocket 路由、全流程串联九篇博客对应九个模块从最底层的日志工具到最顶层的入口函数逐层讲解了每个模块的设计思路、实现细节和调用关系。通过这个项目可以完整地学习到一个 C 后端实时通信项目的架构设计和编码实践。