🌈欢迎来到实战项目专栏 ~~ 基于 WebSocket 的多用户网页五子棋
- 🌍博客主页 :张小姐的猫~江湖背景
- 🔥所属专栏 :C++项目 ~ AI大模型接入SDK
- 作者水平很有限,如果发现错误,可在评论区指正,感谢🙏

Websocketpp
- [🌈欢迎来到实战项目专栏 ~~ 基于 WebSocket 的多用户网页五子棋](#🌈欢迎来到实战项目专栏 ~~ 基于 WebSocket 的多用户网页五子棋)
- 👀项目结构设计
- 👀项目流程图
- 👀实用工具类模块代码实现
- 📢写在最后

👀项目结构设计
项目模块划分说明
项目的实现,咱们将其划分为三个大模块来进行:
- 数据管理模块:基于Mysql数据库进行用户数据的管理
- 前端界面模块:基于JS实现前端页面(注册,登录,游戏⼤厅,游戏房间)的动态控制以及与服务器的通信
- 业务处理模块:搭建WebSocket服务器与客户端进行通信,接收请求并进行业务处理
在这⾥回顾⼀下我们要实现的项目功能,我们要实现的是一个在线五子棋对战服务器,提供用户通过浏览器进行用户注册,登录,以及实时匹配,对战,聊天等功能。
而如果要实现这些功能,那么就需要对业务处理模块再次进行细分为多个模块来实现各个功能
业务处理模块的子模块划分:
- 网络通信模块:基于websocketpp库实现Http&WebSocket服务器的搭建,提供网络通信功能。
- 会话管理模块:对客户端的连接进行cookie&session管理,实现http短连接时客户端身份识别功能。
- 在线管理模块:对进入游戏大厅与游戏房间中用户进行管理,提供用户是否在线以及获取用户连接的功能。
- 房间管理模块:为匹配成功的用户创建对战房间,提供实时的五子棋对战与聊天业务功能。
- 用户匹配模块:根据天梯分数不同进行不同层次的玩家匹配,为匹配成功的玩家创建房间并加入房间
👀项目流程图
玩家用户角度流程图:

服务器流程结构图:

👀实用工具类模块代码实现
实用工具类模块主要是负责提前实现一些项目中会用到的边缘功能代码,提前实现好了就可以在项目中用到的时候直接使用了
📅日志宏封装
实现程序日志打印:实现一些宏函数,能够辅助我们进行日志信息的打印
cpp
[08:29:32-main.cc-22] 文件打开失败
time_t time(NULL):获取系统时间戳struct tm *localtime(time_t * t):将时间戳转化成一个结构体char *strftime(char *buf, int max, char *format, struct tm *tm);:把时间结构体格式化成我们可读的时间字符串int fprintf(FILE *fp,char *format,...):按照指定格式,把数据写入指定的文件流(FILE )中
cpp
#ifndef LOGGER_HPP
#define LOGGER_HPP
#include <time.h>
#include <stdio.h>
#define INF 0
#define DBG 1
#define ERR 2
#define DEFAULT_LOG_LEVEL DBG
#define LOG(level, format, ...) do{\
if (DEFAULT_LOG_LEVEL > level) break;\
time_t t = time(NULL);\
struct tm* tm = localtime(&t);\
char buf[128];\
strftime(buf, sizeof(buf), "%H:%M:%S", tm);\
fprintf(stdout, "[%s - %s:%d]" format "\n", buf, __FILE__, __LINE__, ##__VA_ARGS__);\
}while(0)
#define INF_LOG(format, ...) LOG(INF, format, ##__VA_ARGS__)
#define DBG_LOG(format, ...) LOG(DBG, format, ##__VA_ARGS__)
#define ERR_LOG(format, ...) LOG(ERR, format, ##__VA_ARGS__)
#endif
Mysql-API封装
分别对数据库的连接 &初始化 、句柄的销毁 、语句的执行 进行封装
数据库的连接 &初始化:
- 初始化句柄
- 链接服务器
- 设置字符集
- 选择数据库
cpp
class MySQLUtil
{
public:
static MYSQL * mysql_create(const std::string & host,
const std::string & username,
const std::string & password,
const std::string & dbname,
uint16_t port = 3306)
{
//初始化
MYSQL * conn = mysql_init(nullptr);
if (!conn)
{
ERR_LOG("mysql_init error");
return nullptr;
}
//链接mysql
if (!mysql_real_connect(conn,
host.c_str(),
username.c_str(),
password.c_str(),
dbname.c_str(),
port, nullptr, 0))
{
ERR_LOG("mysql_real_connect error: %s", mysql_error(conn));
return nullptr;
}
//选择字符集
if (mysql_set_character_set(conn, "utf8") != 0)
{
ERR_LOG("mysql_set_character_set error: %s", mysql_error(conn));
return nullptr;
}
return conn;
}
static bool mysql_exec(MYSQL * conn, const std::string & sql)
{
if (mysql_query(conn, sql.c_str()))
{
ERR_LOG("mysql_query error: %s", mysql_error(conn));
mysql_destory(conn);
return false;
}
return true;
}
static void mysql_destory(MYSQL * conn)
{
if (conn != nullptr)
{
mysql_close(conn);
}
return;
}
};
Jsoncpp-API封装
封装了两个函数:序列化和反序列化
cpp
class JSONUtil
{
public:
static bool serialize(const Json::Value &root, std::string &str)
{
Json::StreamWriterBuilder builder;
std::unique_ptr<Json::StreamWriter> writer(builder.newStreamWriter());
std::stringstream ss;
int ret = writer->write(root, &ss);
if (ret != 0)
{
ERR_LOG("json serialize error");
return false;
}
str = ss.str();
return true;
}
static bool deserialize(const std::string &str, Json::Value &root)
{
Json::CharReaderBuilder builder;
std::unique_ptr<Json::CharReader> reader(builder.newCharReader());
std::string errs;
bool ret = reader->parse(str.c_str(), str.c_str() + str.size(), &root, &errs);
if (ret == false)
{
ERR_LOG("json deserialize error: %s", errs.c_str());
return false;
}
return true;
}
};
String-Split封装
实现了字符串分割的功能!
cpp
class StringUtil
{
public:
static int split(const std::string &src, const std::string &sep, std::vector<std::string> &res)
{
//123,123,,,123
size_t pos = 0, idx = 0; //遍历找到分隔符
while(idx < src.size())
{
pos = src.find(sep, idx);
if (pos == std::string::npos)
{
//没有找到分隔符,全部加到结果中
res.push_back(src.substr(idx));
return res.size();
}
if(pos == idx) //分隔符在开头
{
//分隔符在开头
idx = pos + sep.size();
continue;
}
//找到了分隔符
res.push_back(src.substr(idx, pos - idx));
idx = pos + sep.size();
}
return res.size();
}
};
File-read封装
封装对文件数据的读取功能(对html文件数据进行读取!)
cpp
class FileUtil
{
public:
static bool read(const std::string &filename, std::string &body)
{
//以二进制打开文件
std::ifstream ifs(filename, std::ios::binary);
if (!ifs.is_open())
{
ERR_LOG("open file error: %s", filename.c_str());
return false;
}
//获取文件大小
size_t fsize = 0;
ifs.seekg(0, std::ios::end);//把读指针移到文件末尾
fsize = ifs.tellg(); //获取当前读位置
ifs.seekg(0, std::ios::beg);//把读指针移到文件开头
body.resize(fsize);
//将文件所有数据读取出来
ifs.read(&body[0], fsize);
if(ifs.good() == false)
{
ERR_LOG("read file error: %s", filename.c_str());
ifs.close();
return false;
}
ifs.close();
return true;
}
};
📢写在最后
接下来登场的是 数据管理模块

