文章目录:
1.开发背景
五子棋是一种传统的棋种,它不仅容易上手,老少皆宜,而且趣味横生,引人入胜:它不仅能增强思维能力,提高智力,而且富含哲理,有助于修身养性。为了将自己所学到的知识更系统地用起来,也为了检测自己对Linux相关知识的掌握情况,所以这款网络对战五子棋项目就诞生了~~
2.项目功能&&效果展示
① 支持用户登陆、注册功能
② 支持用户在线匹配功能
③ 游戏结束后,支持重新匹配功能
④将对局信息持久化存储在数据库中
用户登录界面:
用户注册界面:
游戏界面:
对局情况:
3.开发环境&&主要技术
开发环境
① Linux平台
② gcc/g++:7.3.1版本
③ C++语言
④ MySQL数据库
主要技术
① 多线程
② 互斥锁
③ 条件变量
④ JSON
⑤ AJAX异步请求
⑥ MySQL数据库
4.项目模块&&设计思路
- HTTP模块:用来接收来自网络的请求,使用到开源库 httplib
- 用户管理模块:① 管理用户信息② 用户信息持久化(room_player.hpp)
- 房间管理模块:针对两个匹配用户创建房间,保存游戏属性(room_player.hpp)
- 会话模块:针对登录用户创建唯一的会话ID,通过Cookie返回给浏览器(session.hpp )
- 数据库管理模块:database.hpp
- 工具模块:tools.hpp
- 项目驱动模块:webgobang.hpp、webgobang.cpp
设计思路:
此项目大体的数据流向是前端通过AJAX请求将JSON格式的数据传送到后端,后端通过httplib库中的方法获取到前端当中的JSON数据,然后对该数据进行相应操作后,将结果返回给前端,前端根据返回的结果,走相应的逻辑,并向用户反馈一个结果
项目架构图:
5.项目主要功能实现
5.1 登陆注册
用户点击登录按钮后,前端会对用户输入的邮箱和密码进行获取,将数据转为JSON格式(序列化),并通过AJAX,采用POST的http请求方法将该数据发送到后端去,后端校验该数据之后,将其持久化到数据库中,并且根据输入的邮箱和密码生成相应的MD5码作为当前用户的cookie信息返回回去。当前端接收到http服务端的响应后,会回调success:function(data)函数,做出相应的操作;
后端代码如下:
http_svr_.Post("/login", [this](const Request& res, Response& resp){
//1.校验用户提交的账号和密码(同数据库当中的数据进行校验)
Json::Reader r;
Json::Value v;
r.parse(res.body, v);
//将请求正文中的内容反序列化
cout << res.body << endl;
//在数据库中校验邮箱、密码,若正确返回用户id
int user_id = this->db_svr_->QueryUserExist(v);
//2.组织http响应进行回复(json)
string tmp = "";
if(user_id > 0)
{
Session sess(v, user_id);
string session_id = sess.GetSessionId();
//回复的会话格式 JSESSIONID=xxxxx
tmp = "JSESSIONID=" + session_id;
all_sess_->SetSessionInfo(session_id, sess);
//放到用户管理类当中, 进行管理
//当用户登录之后, 将登录的用户保存在map当中
this->pm_->InsertPlayer2Map(user_id);
}
Json::Value resp_json;
resp_json["status"] = user_id <= 0 ? false:true;
//将该Json-Value对象序列化 并进行响应
resp.body = this->Serializa(resp_json);
resp.set_header("Set-Cookie", tmp.c_str());
resp.set_header("Content-Type", "application/json");
});
注册逻辑与登录逻辑类似,此处不再详解
5.2 匹配功能
此游戏玩家一共有三个状态在线状态(ONLINE)、匹配状态(MATCH)、游戏状态(PLAYING),当玩家登录上来后会将用户的状态改为在线状态,当玩家点击开始匹配按钮时,玩家状态会变为MATCHING,并且将其加入匹配池(vector) 当中,唤醒匹配线程进行匹配,如果匹配池中匹配玩家数量 < 2,则匹配线程(从匹配池中获取玩家进行匹配的线程) 不进行匹配,如果匹配池中匹配玩家数量为奇数,则匹配池中最后一个玩家不进行匹配,等待下一次匹配,其余玩家两两配对。
匹配池:使用vector用来存放MATCH状态的玩家
匹配线程:从匹配池中获取到两个玩家信息后,为这两名玩家创建一个房间,并将这两名玩家的状态变为PALYING状态,为其分配创建出的房间号和对应所执的黑棋/白棋。
将玩家放入匹配池代码如下:
int PushPlayer2MatchPool(int user_id)
{
//1.设置用户的状态为MATCHING状态
pm_->SetUserStatus(user_id, MATCHING);
//2.放到匹配池当中
pthread_mutex_lock(&vec_lock_);
match_pool_.push_back(user_id);
match_pool_num_++;
printf("match_pool_num_: %d\n", match_pool_num_);
pthread_mutex_unlock(&vec_lock_);
//通知匹配线程工作
pthread_cond_broadcast(&vec_cond_);
return 0;
}
匹配线程代码如下:
static void* MatchServer(void* arg)
{
pthread_detach(pthread_self());
WebGobang* wg = (WebGobang*)arg;
while(1)
{
pthread_mutex_lock(&wg->vec_lock_);
while(wg->match_pool_num_ < 2)
{
pthread_cond_wait(&wg->vec_cond_, &wg->vec_lock_);
printf("i am MatchServer, i working\n");
}
//匹配池当中的人数一定为大于等于2
// 奇数: 一定由一个玩家要轮空
// 偶数:两两进行匹配
int last_id = -1;
auto& vec