# 在线五子棋对战 **Repository Path**: yingke-software-association/online-gomoku-battle ## Basic Information - **Project Name**: 在线五子棋对战 - **Description**: 基于WebSocketpp编写的在线五子棋对战网游 - **Primary Language**: Unknown - **License**: Not specified - **Default Branch**: master - **Homepage**: None - **GVP Project**: No ## Statistics - **Stars**: 0 - **Forks**: 1 - **Created**: 2025-04-24 - **Last Updated**: 2025-05-04 ## Categories & Tags **Categories**: Uncategorized **Tags**: None ## README # 项目介绍 ## **Websocketpp库** ### 一、协议介绍 WebSocket 协议本质是基于 **TCP** 的应用层协议,通常由 **HTTP 协议** 切换而来,核心流程如下: 1. **TCP 连接建立** 通过三次握手建立底层 TCP 连接,随后基于 HTTP 协议进行首次通信,客户端向服务端发送 **协议升级请求**。 **请求示例**: ```http GET /ws HTTP/1.1 Connection: Upgrade # 告知服务器升级协议 Upgrade: WebSocket # 明确升级为 WebSocket 协议 sec-WebSocket-Version: 13 # 指定 WebSocket 协议版本 sec-WebSocket-Key: dGhlIHNhbXBsZSBub25jZQ== # 随机生成的 Base64 编码密钥 ``` 2. **服务端响应升级** 服务端验证请求合法后,返回 **101 Switching Protocols** 状态码,完成协议切换。 **响应示例**: ```http HTTP/1.1 101 Switching Protocols Connection: Upgrade Upgrade: WebSocket Sec-WebSocket-Accept: s3pPLMBiTxaQ9kYGzzhZRbK+xOo= # 由客户端密钥 + 固定GUID哈希生成 ``` **切换完成后**,客户端与服务端通过 **WebSocket 帧格式** 直接通信,不再依赖 HTTP 协议。 ### 二、使用 WebSocketpp 搭建简单 Demo 服务器 #### **编码流程** 1. **实例化服务器对象** 创建 `websocketpp` 服务端实例,用于管理连接和事件循环: ```cpp using WebSocketServer = websocketpp::server; WebSocketServer server; // 初始化服务端对象 ``` 2. **禁用内置日志系统** 关闭默认日志输出(推荐集成自定义日志库): ```cpp server.set_access_channels(websocketpp::log::alevel::none); // 禁止所有日志 ``` 3. **初始化 Asio 异步框架** 配置底层网络通信引擎(Asio 是 C++ 异步 I/O 框架,WebSocketpp 基于此实现): ```cpp server.init_asio(); // 初始化 Asio 句柄 server.set_reuse_addr(true); // 允许端口重用(避免端口占用问题) ``` 4. **绑定监听地址和端口** 指定服务端监听的网络接口和端口(如监听所有 IP 的 9090 端口): ```cpp server.listen(9090); // 监听端口 ``` 5. **注册事件回调函数** 定义以下核心事件的处理逻辑(通过 `std::bind` 绑定参数): - **HTTP 请求处理**:`set_http_handler`(处理未升级为 WebSocket 的 HTTP 请求) - **连接建立**:`set_open_handler`(WebSocket 握手成功时触发) - **连接关闭**:`set_close_handler`(连接断开时触发) - **消息接收**:`set_message_handler`(接收到客户端消息时触发) 6. **启动服务端** 开启事件循环,等待客户端连接和消息处理: ```cpp server.run(); // 阻塞运行,处理异步事件 ``` #### **完整代码示例** ```cpp #include #include #include #include // 定义服务端类型(基于 Asio 非 TLS 配置) using WebSocketServer = websocketpp::server; // 回调函数前置声明 void handle_http(WebSocketServer::connection_hdl, WebSocketServer*); void handle_open(WebSocketServer::connection_hdl, WebSocketServer*); void handle_close(WebSocketServer::connection_hdl, WebSocketServer*); void handle_message(WebSocketServer::connection_hdl, WebSocketServer::message_ptr, WebSocketServer*); int main() { // 1. 创建服务端实例 WebSocketServer server; // 2. 禁用内置日志(避免冗余输出) server.set_access_channels(websocketpp::log::alevel::none); // 3. 初始化 Asio 框架并配置端口重用 server.init_asio(); server.set_reuse_addr(true); // 4. 绑定监听端口(9090) if (server.listen(9090) != websocketpp::error::no_error) { std::cerr << "Failed to listen on port 9090" << std::endl; return 1; } // 5. 注册事件回调(使用 std::placeholders 占位符传递参数) server.set_http_handler( std::bind(&handle_http, std::placeholders::_1, &server) ); server.set_open_handler( std::bind(&handle_open, std::placeholders::_1, &server) ); server.set_close_handler( std::bind(&handle_close, std::placeholders::_1, &server) ); server.set_message_handler( std::bind(&handle_message, std::placeholders::_1, std::placeholders::_2, &server) ); // 6. 启动服务端(阻塞直至手动终止) std::cout << "WebSocket server started on port 9090" << std::endl; server.run(); return 0; } ``` ## 项目核心工具类设计与实现 ### 一、MySQL 数据库工具类(`ns_mysql` 命名空间) #### 核心特性 - **连接管理**:支持初始化重试、自动重连机制,确保数据库连接稳定性 - **心跳检测**:基于定时器实现连接状态监控,自动处理超时重连 - **线程安全**:封装底层 `mysql` 句柄操作,提供线程安全的查询接口 #### 类结构解析 ##### `MySQL` 类 | **成员变量** | **说明** | |--------------------|--------------------------------------------------------------------------| | `MYSQL* _my` | 底层 MySQL 句柄,通过 `mysql_init`/`mysql_real_connect` 初始化 | | `STATU _statu` | 连接状态枚举(`INIT_ERROR`/`INIT_SUCCESS`/`CONNECTED`/`DISCONNECTED`) | | `_host/_user/_password` | 数据库连接参数(主机、用户名、密码) | | `_database/_port` | 数据库名及端口号 | ##### 核心方法 1. **初始化与连接** - `RetryInit()`:重试初始化 MySQL 句柄(默认 3 次),失败时终止程序 - `RetryConnect()`:基于当前参数重连数据库,自动恢复连接状态 - `ConnectMysql()`:设置连接参数并发起首次连接 2. **数据操作** - `Query(const string& sql)`:执行无结果集 SQL(如 `INSERT/UPDATE/DELETE`) - `Query(const string& sql, vector* res, int* filed)`:执行带结果集查询,返回字段数及行数据 #### 代码实现:连接重试机制 ```cpp // 初始化句柄(支持重试) void MySQL::RetryInit() { while (_my == nullptr && _init_cnt > 0) { _my = mysql_init(nullptr); // 创建 MySQL 句柄 if (_my == nullptr) _init_cnt--; // 初始化失败则减少重试次数 } if (_init_cnt == 0 && _my == nullptr) { // 初始化耗尽仍失败 _statu = STATU::INIT_ERROR; LOG(FATAL, "MySQL句柄初始化失败,重试次数耗尽"); abort(); // 终止程序 } _statu = STATU::INIT_SUCCESS; // 初始化成功 } // 自动重连逻辑 bool MySQL::RetryConnect() { if (_statu != STATU::INIT_SUCCESS) return false; // 未完成初始化不允许连接 // 尝试连接直至成功或重试次数耗尽 while (mysql_real_connect(_my, _host.c_str(), _user.c_str(), _password.c_str(), _database.c_str(), _port, nullptr, 0) == nullptr && _conn_cnt > 0) { _conn_cnt--; // 减少重试次数 LOG(WARNING, "MySQL连接失败,剩余重试次数:%d", _conn_cnt); std::this_thread::sleep_for(std::chrono::seconds(1)); // 等待1秒后重试 } if (_conn_cnt == 0) return false; // 重试失败 _conn_cnt = default_init_cnt; // 重置重试次数 _statu = STATU::CONNECTED; // 更新连接状态 return true; } ``` ### 二、JSON 数据处理工具类(`JSONTOOL` 命名空间) #### 核心功能 基于 `Jsoncpp` 库实现 **JSON 序列化与反序列化**,提供简洁的静态接口,支持 UTF-8 编码处理,适用于网络传输数据格式转换及配置文件解析。 #### 关键方法 | **方法名** | **功能** | **参数说明** | **返回值** | |-----------------------|-----------------------------------|-------------------------------------------|------------------| | `Serialization()` | 对象转 JSON 字符串 | `value`:输入 `Json::Value` 对象
`jsonstr`:输出字符串指针 | `true` 成功,`false` 失败 | | `DelSerialization()` | JSON 字符串转对象 | `value`:输出 `Json::Value` 对象指针
`jsonstr`:输入字符串 | `true` 成功,`false` 失败 | #### 代码示例:序列化流程 ```cpp // 将 Json::Value 序列化为 UTF-8 字符串 bool JsonTool::Serialization(Json::Value& value, std::string* jsonstr) { Json::StreamWriterBuilder builder; builder["emitUTF8"] = true; // 生成 UTF-8 编码的 JSON 字符串 std::stringstream ss; std::unique_ptr writer(builder.newStreamWriter()); // 写入字符串流,失败时记录错误日志 if (writer->write(value, &ss) != 0) { LOG(ERROR, "JSON序列化失败,数据格式可能有误"); return false; } *jsonstr = ss.str(); // 获取序列化结果 return true; } // 反序列化 JSON 字符串到 Json::Value bool JsonTool::DelSerialization(Json::Value* value, const std::string& jsonstr) { Json::CharReaderBuilder builder; builder["emitUTF8"] = true; // 支持 UTF-8 编码输入 std::unique_ptr reader(builder.newCharReader()); std::string errstr; // 存储解析错误信息 // 解析字符串,失败时输出详细错误 if (!reader->parse(jsonstr.c_str(), jsonstr.c_str() + jsonstr.size(), value, &errstr)) { LOG(ERROR, "JSON反序列化失败:%s", errstr.c_str()); return false; } return true; } ``` ### 三、日志系统工具类(`ns_log` 命名空间) #### 核心设计 1. **多维度日志**:支持 **DEBUG/INFO/WARNING/ERROR/FATAL** 五级日志级别 2. **输出模式**:可动态切换 **屏幕输出** 或 **文件输出**(默认 `Log.txt`) 3. **线程安全**:通过互斥锁保证多线程环境下日志写入不冲突 4. **单例模式**:全局唯一日志实例,避免资源重复创建 #### 核心组件 ##### `Log` 类 - **关键成员**: - `_type`:输出模式(`_SCREEN_TYPE_`/`_FILE_TYPE`) - `_logfile`:日志文件路径(默认 `Log.txt`) - `_mtx`:互斥锁(保护日志写入操作) - `_isopen/_flag`:日志过滤开关及级别(关闭时输出所有日志) - **核心方法**: - `LogMessage()`:格式化日志信息(包含时间、进程、文件/行号)并写入目标 - `ModPrintFormat()`:切换输出模式(屏幕/文件) - `EnableFiltration(int level)`:开启过滤,仅输出指定级别及以上日志 ##### 日志宏定义 ```cpp // 基础日志宏(自动获取文件/行号) #define LOG(level, format, ...) \ _ptr->LogMessage(level, __FILE__, __LINE__, format, ##__VA_ARGS__) // 屏幕输出专用宏(先切换模式再输出) #define LOG_TO_SCREEN(level, format, ...) \ do { _ptr->ModPrintFormat(_SCREEN_TYPE_); LOG(level, format, ##__VA_ARGS__); } while(0) // 文件输出模式切换宏 #define LOG_TO_FILE() \ _ptr->ModPrintFormat(_FILE_TYPE) ``` #### 代码实现:线程安全的日志写入 ```cpp // 日志信息格式化与写入(加锁保证线程安全) void Log::LogMessage(int level, std::string filename, int filenumber, const char *format, ...) { if (_isopen && level < _flag) return; // 过滤低于指定级别的日志 Logmessage msg; msg._level = LevelToString(level); // 转换日志级别为字符串 msg._pid = getpid(); // 获取当前进程ID msg._filename = filename.substr(filename.find_last_of('/') + 1); // 提取短文件名(去除路径) msg._filenumber = filenumber; // 记录代码行号 msg._curr_time = GetCurrTime(); // 获取当前时间(YYYY-MM-DD HH:MM:SS) // 处理可变参数(如 printf 格式字符串) va_list args; va_start(args, format); char buffer[1024]; vsnprintf(buffer, sizeof(buffer), format, args); va_end(args); msg._logmessage = buffer; // 加锁写入(保证多线程安全) std::unique_lock lock(_mtx); switch (_type) { case _SCREEN_TYPE_: LogMsgToScreen(msg); break; case _FILE_TYPE: LogMsgToFile(msg); break; } } // 获取当前时间(字符串格式) std::string Log::GetCurrTime() { time_t now = time(nullptr); struct tm* tm_info = localtime(&now); char buf[64]; strftime(buf, sizeof(buf), "%Y-%m-%d %H:%M:%S", tm_info); // 格式化为标准时间字符串 return buf; } ``` ### 四、字符串处理工具类(`util::StringTool`) #### **核心功能** 基于 `Boost` 库实现灵活的字符串分割功能,支持 **多分隔符处理** 和 **空格压缩选项**,适用于日志解析、配置文件处理等场景。 #### **关键方法** | **方法名** | **功能** | **参数说明** | **返回值** | |------------------|-------------------------------------------|--------------------------------------------------------------------------------|------------------| | `Split()` | 按指定分隔符分割字符串 | `prev`:待分割字符串
`res`:输出字符串向量
`sep`:分隔符(支持多字符)
`flag`:是否压缩连续分隔符(默认 `true`) | 分割后的子串数量 | #### **代码实现:多分隔符字符串分割** ```cpp namespace util { class StringTool { public: // 字符串分割(支持多分隔符及空格压缩) static size_t Split(const std::string &prev, std::vector *res, const std::string &sep, bool flag) { // 使用 Boost 算法库实现高效分割 // `is_any_of` 支持多字符分隔符(如 ",; " 可同时分割逗号、分号、空格) // `token_compress_on` 压缩连续分隔符为单个,`off` 则保留原始间隔 flag ? boost::split(*res, prev, boost::is_any_of(sep), boost::algorithm::token_compress_on) : boost::split(*res, prev, boost::is_any_of(sep), boost::algorithm::token_compress_off); return res->size(); // 返回有效子串数量 } }; } ``` ### 五、文件操作工具类(`util::FileTool`) #### **核心功能** 提供文件读写、属性检测、目录操作等基础功能,集成日志记录错误信息,支持 **Boost Filesystem** 和系统调用,适用于文件系统管理场景。 #### **核心方法** | **方法名** | **功能** | **典型场景** | |---------------------------|-------------------------------------------|---------------------------------------| | `Write()` | 追加写入文件 | 日志文件记录、配置文件更新 | | `Read()` | 读取文件内容到字符串 | 读取配置文件、加载资源文件 | | `IsExists()` | 判断文件/目录是否存在 | 操作前校验路径有效性 | | `GetAllFilesWithExtensionInPath()` | 递归获取指定后缀的文件列表 | 资源扫描、批量文件处理 | | `CreateDir()` | 创建目录(单级) | 初始化目录结构 | #### **代码实现:递归文件扫描** ```cpp // 递归获取指定路径下所有指定后缀的文件 bool FileTool::GetAllFilesWithExtensionInPath(const std::string &path, std::vector *res, const std::string &suffix) { namespace fs = boost::filesystem; fs::path root(path); if (!fs::exists(root)) { // 路径无效 LOG(ERROR, "用户指定的%s路径是无效的!", path.c_str()); return false; } // 遍历所有子文件(递归模式) fs::recursive_directory_iterator end; for (fs::recursive_directory_iterator iter(root); iter != end; ++iter) { if (!fs::is_regular_file(*iter)) continue; // 跳过目录和特殊文件 if (iter->path().extension() == suffix) { // 匹配后缀(如 ".txt", ".log") res->push_back(iter->path().string()); // 记录完整路径 } } return true; } ``` ### 六、用户信息管理类(`db::UserInfo`) #### **核心功能** 封装用户数据的数据库操作,基于 `util::MySQLTool` 实现 **线程安全** 的增删改查,支持用户注册、登录、积分更新等业务逻辑,对应数据库表结构如下: ```sql CREATE TABLE IF NOT EXISTS user ( id INT PRIMARY KEY AUTO_INCREMENT, username VARCHAR(32) UNIQUE KEY NOT NULL, password VARCHAR(64) NOT NULL, score INT, -- 天梯积分 total_cnt INT, -- 总对局数 win_cnt INT -- 胜利局数 ); ``` #### **关键方法** | **方法名** | **功能** | **线程安全** | **数据库操作** | |------------------|-------------------------------------------|--------------|---------------------------------| | `Insert()` | 新增用户(密码自动加密) | 是 | `INSERT` | | `Login()` | 用户登录验证(返回完整用户信息) | 是 | `SELECT` | | `FindUser()` | 按用户ID或用户名查询用户信息 | 是(锁保护) | `SELECT` | | `AddScoreAndCnt()` | 胜利后更新积分和对局统计 | 是 | `UPDATE` | | `ReduceScore()` | 失败后扣减积分并更新对局统计 | 是 | `UPDATE` | #### **代码实现:线程安全的用户查询** ```cpp // 按用户名查找用户(带互斥锁保护) bool UserInfo::FindUser(const std::string &name, Json::Value &user) { const char* SQL = R"(SELECT id,score,total_cnt,win_cnt FROM user WHERE username='%s';)"; char buff[BUFF_SIZE]; sprintf(buff, SQL, name.c_str()); std::vector res; int filed = 0; { std::lock_guard lock(_mtx); // 作用域锁保证线程安全 _mysql.Query(buff, &res, &filed); } if (res.size() != 1) { LOG(DEBUG, "没有查询到用户%s的信息", name.c_str()); return false; } // 填充用户信息到 Json::Value user["id"] = std::stoi(res[0][0]); user["score"] = std::stoi(res[0][1]); user["total_cnt"] = std::stoi(res[0][2]); user["win_cnt"] = std::stoi(res[0][3]); return true; } ``` ### 七、单元测试示例 #### **测试场景** 通过 `UserInfo` 类验证用户注册、登录、信息查询及积分更新逻辑,确保数据库操作的正确性和线程安全性。 #### **测试用例** ```cpp // 单元测试:用户新增 void Unity1(UserInfo& ctl, Json::Value& user) { user["username"] = "昨夜微凉"; user["password"] = "12345678"; ctl.Insert(user); // 执行插入操作 std::string str; util::JsonTool::Serialization(user, &str); std::cout << "用户:" << str << "新增成功" << std::endl; } // 单元测试:用户登录及信息获取 void Unity2(UserInfo& ctl, Json::Value& user) { user["username"] = "昨夜微凉"; user["password"] = "12345678"; if (ctl.Login(user)) { // 登录验证 std::string str; util::JsonTool::Serialization(user, &str); std::cout << "登录成功,用户信息:" << str << std::endl; } } // 单元测试:积分更新逻辑 void Unity5(UserInfo& ctl, Json::Value& user) { ctl.AddScoreAndCnt(3, 50); // 胜利一局,增加50积分 ctl.ReduceScore(3, 10); // 失败一局,扣减10积分 ctl.FindUser(3, user); // 查询更新后的用户信息 std::string str; util::JsonTool::Serialization(user, &str); std::cout << "积分更新后:" << str << std::endl; } ``` ### TODO