"你经过我每个灿烂时刻,我才真正学会如你般自由~"
本项目是一款网页版的五子棋对战游戏,其主要支持以下几种功能:
● ⽤⼾管理: 实现⽤⼾注册, ⽤⼾登录、获取⽤⼾信息、⽤⼾天梯分数记录、⽤⼾比赛场次记录等.
● 匹配对战: 实现两个玩家在⽹⻚端根据天梯分数匹配游戏对⼿,并进⾏五⼦棋游戏对战的功能.
● 聊天功能: 实现两个玩家在下棋的同时可以进⾏实时聊天的功能.
● HTTP/WebSocket/Websocket++ ● JsonCpp ● Mysql ● C++11 ● HTML/CSS/JS/AJAX
# root:
yum install wget
# 进入到该目录
cd /etc/yum.repos.d
# 配置 yum源
sudo wget -O /etc/yum.repos.d/CentOS-Base.repo http://mirrors.aliyun.com/repo/Centos-8.repo
# 安装SCL软件源
sudo yum install centos-release-scl-rh centos-release-scl
# 安装epel软件源
sudo yum -y install epel-release
# 安装lrzsz传输⼯具
sudo yum install lrzsz -y
# 安装⾼版本gcc/g++编译器
sudo yum install devtoolset-8-gcc devtoolset-8-gcc-c++ -y
// 设置开机 时自动启用
echo "source /opt/rh/devtoolset-7/enable" >> ~/.bashrc
// "~" 表示的是当前用户的主目录
source ~/.bashrc
# 安装gdb调试器
sudo yum install gdb -y
# 安装git
sudo yum install git -y
# 安装cmake
sudo yum install cmake
# 安装boost库
sudo yum install boost-devel.x86_64 -y
# 安装Jsoncpp库
sudo yum install jsoncpp-devel -y
# 获取mysql官⽅yum源
wget http://repo.mysql.com/mysql57-community-release-el7-10.noarch.rpm
# 安装mysql官⽅yum源
sudo rpm -ivh mysql57-community-release-el7-10.noarch.rpm
# 安装Mysql数据库服务
sudo install -y mysql-community-server
# 安装Mysql开发包
sudo yum install -y mysql-community-devel
# 启动Mysql
systemctl start mysqld.service
# 设置开机项
systemctl enable mysqld.service
# 停止服务
service mysqld stop
rpm --import https://repo.mysql.com/RPM-GPG-KEY-mysql-2022
首次登录时,会在MySQL里的日志文件中生成一个临时密码:
# 也可以使用这个命令直接获取
sudo grep 'temporary password' /var/log/mysqld.log
# 查看mysql 密码策略
show variables like 'validate_password%';
# 设置密码强度
set global validate_password_policy = 0 (密码强度)
# 设置密码长度
set global validate_password_length = 1 (长度)
# 修改密码 才能进行后面的操作
alter user 'root'@'%' Identified by 'XXXXXXX';
如果你已经忘记密码了,你可以进入 /etc/my.cnf文件中设置:
[mysqld]: skip-grant-tables
● 配置 '/etc/my.cnf' 字符集
[client]
default-character-set=utf8
[mysql]
default-character-set=utf8
[mysqld]
character-set-server=utf8
# 远方拉取库
git clone https://github.com/zaphoyd/websocketpp.git
# 进入websocketpp目录
cd /websocketpp mkdir build
# 在build目录下:
cmake -DCMAKE_INSTALL_PREFIEX=/usr ../
# 生成临时文件都会放在 makefile文件
# 安装websocketpp
sudo make install
验证是否安装成功:
进入/websocketpp/examples
关于什么是WebSocket可以看看这篇文章:
我们来谈谈websocket_websocket 线程_RNGWGzZs的博客-CSDN博客
该项目牵涉到数据库的常用操作,数据序列化反序列化,文件读写等操作,所以我们先对这些操作进行一个封装,方便之后接口的调用。
其中包含了创建初始化 mysql连接句柄,以及sql语句的执行和句柄销毁。
class mysql_util
{
public:
// 用创建句柄+初始化+连接
static MYSQL *mysql_create(
const std::string host,
const std::string username,
const std::string password,
const std::string db_name,
uint16_t port = 3306)
{
// 初始化
MYSQL *mysql = mysql_init(nullptr);
if (mysql == nullptr)
{
ERR_LOG("mysql init failed");
return nullptr;
}
// 库连接
if (mysql_real_connect(mysql, host.c_str(), username.c_str(), password.c_str(), db_name.c_str(), port, nullptr, 0) == nullptr)
{
ERR_LOG("mysql connect server failed:%s", mysql_error(mysql));
mysql_close(mysql);
return nullptr;
}
// 设置字符集
if (mysql_set_character_set(mysql, "utf8") != 0)
{
ERR_LOG("mysql charset failed:%s", mysql_error(mysql));
mysql_close(mysql);
return nullptr;
}
return mysql;
}
// 执行sql
static bool mysql_exec(MYSQL *mysql, const std::string &sql)
{
int ret = mysql_query(mysql, sql.c_str());
if (ret != 0)
{
ERR_LOG("SQL:%s ERROR:%s", sql.c_str(), mysql_error(mysql));
return false;
}
return true;
}
// 销毁句柄释放资源
static void mysql_release(MYSQL *mysql)
{
if (mysql == nullptr)
return;
mysql_close(mysql);
}
};
包含传输数据的序列化和反序列化过程。
class json_util
{
public:
static bool serialize(const Json::Value &value, std::string &res)
{
Json::StreamWriterBuilder swb;
std::unique_ptr sw(swb.newStreamWriter());
std::stringstream ss;
int ret = sw->write(value, &ss);
if (ret != 0)
{
ERR_LOG("json serialization faild!");
return false;
}
res = ss.str();
return true;
}
static bool unserialize(const std::string &str, Json::Value &value)
{
Json::CharReaderBuilder crb;
std::unique_ptr cb(crb.newCharReader());
bool ret = cb->parse(str.c_str(), str.c_str() + str.size(), &value, nullptr);
if (ret == false)
{
ERR_LOG("json unserialize failed!");
return false;
}
return true;
}
};
class string_util
{
public:
static int split(const std::string &in, const std::string &sep, std::vector &arry)
{
arry.clear();
// idx:表示切分字符串的位置
size_t idx = 0;
while (idx < in.size())
{
size_t pos = in.find(sep, idx);
if (pos == std::string::npos)
{
// 虽然原字符串没有sep 但 pos-in != 0
arry.push_back(in.substr(idx));
break;
}
// "出现多个相连sep: 33,,,,"
if (pos == idx) {
idx += sep.size();
continue;
}
res.push_back(src.substr(idx, pos - idx));
idx = pos + sep.size();
}
return arry.size();
}
};
class file_util
{
public:
static bool read_file(const std::string filename, std::string &body)
{
std::fstream fs(filename, std::ios::in | std::ios::binary);
if (!fs.is_open())
{
ERR_LOG("file open faild:%s", filename.c_str());
return false;
}
// 使用偏移量的方法 读取整个文件
// seekg:设置读取偏移指针的位置
// tellg:返回当前position
fs.seekg(0, std::ios::end);
body.resize(fs.tellg());
fs.seekg(std::ios::beg); // 回到文件开头
fs.read(&body[0], body.size());
// good:Check whether state of stream is good
if (fs.good() == false)
{
ERR_LOG("read file content faild:%s", filename.c_str());
fs.close();
return false;
}
fs.close();
return true;
}
};
用户管理模块中,我们需要为玩家存储他们的各项身份信息,比如:id,玩家名,天梯分数,总场数,胜场数……这些数据都是存储在磁盘上的,由强大的Mysql进行管理。
另外,为了取得这些数据,我们实现一个类来完成,user_table类就是为了获取数据库中的数据的。
在线⽤⼾管理,是对于当前游戏⼤厅和游戏房间中的⽤⼾进⾏管理。它需要承担对在线用户以及正在进行五子棋对战的玩家进行管理。并通过一定的策略对玩家进行 "保活" 查询。
这是该项目功能的主逻辑。游戏房间模块是在两个玩家成功匹配后生成的。该房间需要支持游戏玩法和实时聊天的功能。
http是一种无状态的短连接协议。所谓无状态,就是不知道这条信息或者这份数据是谁发送的。我们可以试想一下,两个正在进行游戏的玩家,会在一张棋盘上落子,客户端点击发送黑白棋子,但在计算机看无非就是一串01序列,压根不知道这条消息是属于谁的。
由此,服务器为每个⽤⼾浏览器创建⼀个会话对象(session),在需要保存⽤⼾数据时,服务器程序可以把⽤⼾数据写到⽤⼾浏览器独占的session中,当⽤⼾使⽤浏览器访问该程序时,就会携带这个sessio信息,该程序可以从⽤⼾的session中读取该⽤⼾的历史数据,识别该连接对应的⽤⼾,并为⽤⼾提供服务。
在这里我们需要设计一个session类,并且这个session类不能一直存在,当用户结束对局或者用户下线等,该session对象就需要被清理掉。
五⼦棋对战的玩家匹配是根据⾃⼰的天梯分数进⾏匹配的,实现玩家匹配的思想⾮常简单,为不同的档次设计各⾃的匹配队列,当⼀个队列中的玩家数量⼤于 等于2的时候,则意味着同⼀档次中,有2个及以上的⼈要进⾏实战匹配,则出队队列中的前两个⽤⼾,并为之创建游戏房间准备对战。
剩余的模块属于前端知识,诸如登录页面、注册页面、游戏大厅等等,也就不过多赘述了。
日志打印:
#ifndef __MY_LOG__
#define __MY_LOG__
#include
#include
#include
#include
// 定义日志类等级
#define INF 0
#define DBG 1
#define ERR 2
#define FATAL 3
// 打印日志等级显示等级
#define LOG_LEVEL INF
#define MSGBLOCK 1024
// [线程ID 时间 文件:行数][参数]
#define LOG(level, format, ...) \
do \
{ \
if (level < LOG_LEVEL) \
break; \
time_t t = time(nullptr); \
struct tm *ltm = localtime(&t); \
char timebuf[32]; \
strftime(timebuf, sizeof(timebuf), "%H:%M:%S", ltm); \
fprintf(stdout, "[%p %s %s:%d] " format "\n", (void*)pthread_self(), timebuf, __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
用户管理模块实现
#ifndef __MY_DB__
#define __MY_DB__
#include
#include
#include "Util/util.hpp"
#define SQLSPACE 4096
// 与数据库进行数据交互
class user_table
{
private:
MYSQL *_mysql; // mysql句柄
std::mutex _mtx;
public:
// 创建表
user_table(
const std::string &host,
const std::string &username,
const std::string &password,
const std::string &dbname,
uint16_t port)
{
_mysql = Util::mysql_util::mysql_create(host, username, password, dbname, port);
DBG_LOG("成功连接数据库");
assert(_mysql != nullptr);
}
~user_table()
{
Util::mysql_util::mysql_release(_mysql);
_mysql = nullptr;
}
public:
// 注册用户
bool insert(const Json::Value root)
{
// id,username,passwd,rank,场次,胜场
#define INSERT_USER "insert user values (null,'%s',password('%s'),1000,0,0);"
if (root["username"].isNull() || root["password"].isNull())
{
DBG_LOG("register user deficit...");
return false;
}
// 格式化sql语句
char sql[SQLSPACE] = {0};
sprintf(sql, INSERT_USER, root["username"].asCString(), root["password"].asCString());
// 执行sql
bool ret = Util::mysql_util::mysql_exec(_mysql, sql);
if (ret == false)
{
DBG_LOG("insert user info failed!!\n");
return false;
}
return true;
}
// 登录验证
bool login(Json::Value root)
{
if (root["username"].isNull() || root["password"].isNull())
{
DBG_LOG("login information deficit...");
return false;
}
// 以用户名和密码共同作为查询过滤条件,查询到数据则表示用户名密码一致,没有信息则用户名密码错误
#define LOGIN_USER "select id,rank,total_count, win_count where username='%s' and password=password('%s');"
char sql[SQLSPACE] = {0};
sprintf(sql, LOGIN_USER, root["username"].asCString(), root["password"].asCString());
MYSQL_RES *res = nullptr;
{
// 因为要进行查表 但害怕查询过程中对表机进行了更改
std::unique_lock lockguard(_mtx);
bool ret = Util::mysql_util::mysql_exec(_mysql, sql);
if (ret == false)
{
DBG_LOG("user login failed...");
return false;
}
// 该数据查询可能为空 也可能有
res = mysql_store_result(_mysql);
if (res == nullptr)
{
DBG_LOG("have no user info...");
return false;
}
// 数据如果不唯一 mysql_num_rows:获取结果几行数据
int row_num = mysql_num_rows(res);
if (row_num >= 1)
{
DBG_LOG("the user information is not unique!");
return false;
}
}
// 玩家登录信息后 可以对它查询的信息进行回显
// mysql_fetch_row: 获取这一行数据 这里返回的是一个二级指针
// data: id,rank,total_count, win_count
MYSQL_ROW row = mysql_fetch_row(res);
root["id"] = (Json::UInt64)std::stol(row[0]);
root["rank"] = (Json::UInt64)std::stol(row[1]);
root["total_count"] = std::stoi(row[2]);
root["win_count"] = std::stoi(row[3]);
// 资源清理
mysql_free_result(res);
return true;
}
// 通过用户名查询信息
bool select_by_name(const std::string &name, Json::Value &user)
{
#define SELECT_BY_NAME "selecet id,rank,total_count,win_count from user where username='%s';"
char sql[SQLSPACE] = {0};
sprintf(sql, SELECT_BY_NAME, name.c_str());
MYSQL_RES *res = nullptr;
{
std::unique_lock lockguard(_mtx);
bool ret = Util::mysql_util::mysql_exec(_mysql, sql);
if (ret == false)
{
DBG_LOG("get user by name failed...");
return false;
}
res = mysql_store_result(_mysql);
if (res == nullptr)
{
DBG_LOG("have no user info");
return false;
}
int row_num = mysql_num_rows(res);
if (row_num >= 1)
{
DBG_LOG("the user information is not unique!");
return false;
}
}
// id,rank,total_count,win_count
MYSQL_ROW row = mysql_fetch_row(res);
user["id"] = (Json::UInt64)std::stol(row[0]);
user["rank"] = (Json::UInt64)std::stol(row[1]);
user["total_count"] = std::stoi(row[2]);
user["win_count"] = std::stoi(row[3]);
mysql_free_result(res);
return true;
}
// 通过id获取⽤⼾信息
bool select_by_ID(uint64_t &id, Json::Value &user)
{
#define SELECT_BY_ID "select username,rank,total_count,win_count from user where id=%d;"
char sql[SQLSPACE] = {0};
sprintf(sql, SELECT_BY_ID, id);
MYSQL_RES *res = nullptr;
{
std::unique_lock lockguard(_mtx);
bool ret = Util::mysql_util::mysql_exec(_mysql, sql);
if (ret == false)
{
DBG_LOG("get user by id failed...");
return false;
}
res = mysql_store_result(_mysql);
if (res == nullptr)
{
DBG_LOG("have no user info");
return false;
}
int row_num = mysql_num_rows(res);
if (row_num >= 1)
{
DBG_LOG("the user information is not unique!");
return false;
}
}
// username,rank,total_count,win_count
MYSQL_ROW row = mysql_fetch_row(res);
user["id"] = (Json::UInt64)id;
user["username"] = row[0];
user["rank"] = (Json::UInt64)std::stol(row[1]);
user["total_count"] = std::stoi(row[2]);
user["win_count"] = std::stoi(row[3]);
mysql_free_result(res);
return true;
}
// 胜利时天梯分数增加30分,战斗场次增加1,胜利场次增加1
bool win(uint64_t id)
{
#define WIN "update user set rank=rank+30,total_count=total_count+1,win_count=win_count+1 where id=%d;"
char sql[SQLSPACE] = {0};
sprintf(sql, WIN, id);
bool ret = Util::mysql_util::mysql_exec(_mysql, sql);
if (ret == false)
{
DBG_LOG("update win user info failed!");
return false;
}
return true;
}
// 失败时天梯分数减少30,战斗场次增加1,其他不变
bool lose(uint64_t id)
{
#define LOSE "update user set rank=rank-30, total_count=total_count+1 where id=%d;"
char sql[SQLSPACE] = {0};
sprintf(sql, LOSE, id);
bool ret = Util::mysql_util::mysql_exec(_mysql, sql);
if (ret == false)
{
DBG_LOG("update win user info failed!");
return false;
}
return true;
}
};
#endif
匹配队列:
#ifndef __MY_MATCHER__
#define __MY_MATCHER__
#include "Util/util.hpp"
#include "log.hpp"
#include "db.hpp"
#include "room.hpp"
#include
#include
#include
template
class match_queue
{
private:
// 用链表而不直接使用queue是因为我们有中间删除数据的需要
std::list _list;
std::mutex _mtx;
std::condition_variable _cond;
public:
int size()
{
std::unique_lock lock_guarud(_mtx);
return _list.size();
}
bool empty()
{
std::unique_lock lock_guarud(_mtx);
return _list.empty();
}
// 阻塞线程
void wait()
{
std::unique_lock lock_guarud(_mtx);
_cond.wait(lock_guarud);
}
void push(const T &data)
{
std::unique_lock lock_guarud(_mtx);
_list.push_back(data);
_cond.notify_all();
}
bool pop(T &data)
{
std::unique_lock lock_guarud(_mtx);
if (_list.empty() == nullptr)
{
return false;
}
// 输出型参数
data = _list.front();
_list.pop_front();
return true;
}
// 移除指定数据
void remove(T &data)
{
std::unique_lock lock_guarud(_mtx);
_list.remove(data);
}
};
class matcher
{
private:
/*普通选手匹配队列*/
match_queue _q_normal;
/*高手匹配队列*/
match_queue _q_high;
/*大神匹配队列*/
match_queue _q_super;
room_manager *_room;
user_table *_user_table;
online_manager *_online_manager;
/*对应三个匹配队列的处理线程*/
std::thread _th_normal;
std::thread _th_high;
std::thread _th_super;
public:
void handler_match(match_queue &mq)
{
while (1)
{
// 如果使用if 当被唤醒后 不会进行判断
while (mq.size() < 2)
mq.wait();
// 2. 走下来代表人数够了,出队两个玩家
uint64_t uid1, uid2;
bool ret = mq.pop(uid1);
if (ret == false)
continue;
ret = mq.pop(uid2);
if (ret == false)
{
// 重新排入队列
this->add(uid1);
continue;
}
// 校验两个玩家是否在线,如果有人掉线,则要吧另一个人重新添加入队列
wsserver_t::connection_ptr conn1 = _online_manager->get_conn_from_game_hall(uid1);
if (conn1.get() == nullptr)
{
this->add(uid2);
continue;
}
wsserver_t::connection_ptr conn2 = _online_manager->get_conn_from_game_hall(uid1);
if (conn2.get() == nullptr)
{
this->add(uid1);
continue;
}
// 说明玩家正常
room_ptr rp = _room->create_room(uid1, uid2);
if (rp.get() == nullptr)
{
this->add(uid1);
this->add(uid2);
continue;
}
// 5. 对两个玩家进行响应
Json::Value resp;
resp["optype"] = "match_success";
resp["result"] = true;
std::string body;
Util::json_util::serialize(resp, body);
conn1->send(body);
conn2->send(body);
}
}
void th_normal_entry() { return handler_match(_q_normal); }
void th_high_entry() { return handler_match(_q_high); }
void th_super_entry() { return handler_match(_q_super); }
public:
matcher(room_manager *rm, user_table *ut, online_manager *om)
: _room(rm), _user_table(ut), _online_manager(om),
_th_normal(std::thread(&matcher::th_normal_entry, this)),
_th_high(std::thread(&matcher::th_high_entry, this)),
_th_super(std::thread(&matcher::th_super_entry, this))
{
DBG_LOG("游戏匹配模块初始化完毕....");
}
bool add(uint64_t uid)
{
// 根据玩家的天梯分数,来判定玩家档次,添加到不同的匹配队列
Json::Value user;
bool ret = _user_table->select_by_ID(uid, user);
if (ret == false)
{
DBG_LOG("获取玩家:%d 信息失败!!", uid);
return false;
}
int score = user["score"].asInt64();
if (score < 2000)
{
_q_normal.push(uid);
}
else if (score < 3000)
{
_q_high.push(uid);
}
else
{
_q_super.push(uid);
}
return true;
}
bool del(uint64_t uid)
{
Json::Value user;
bool ret = _user_table->select_by_ID(uid, user);
if (ret == false)
{
DBG_LOG("获取玩家:%d 信息失败!!", uid);
return false;
}
int score = user["score"].asInt();
if (score < 2000)
{
_q_normal.remove(uid);
}
else if (score >= 2000 && score < 3000)
{
_q_high.remove(uid);
}
else
{
_q_super.remove(uid);
}
return true;
}
};
#endif
在线链接管理:
#ifndef __MY_ONLINE__
#define __MY_ONLINE__
#include "Util/util.hpp"
#include
#include
// asio_no_tls:未加密
#include
#include
// 服务器类型
typedef websocketpp::server wsserver_t;
// 判断用户是否在线 ---> 是否连接到了大厅
// 游戏房间连接
class online_manager
{
private:
// 取出wsserver_t::connection_ptr 这个是 玩家通信的链接
// 游戏大厅 and 对战房间
std::unordered_map _game_hall;
std::unordered_map _game_room;
std::mutex _mtx;
public:
// 进入游戏大厅:将玩家设置进 大厅在线容器中
void enter_game_hall(uint64_t uid, const wsserver_t::connection_ptr &conn)
{
std::unique_lock lockguard(_mtx);
_game_hall.insert({uid, conn});
}
void exit_game_hall(uint64_t uid)
{
std::unique_lock lockguard(_mtx);
_game_hall.erase(uid);
}
// 进入游戏房间
void enter_game_room(uint64_t uid, const wsserver_t::connection_ptr &conn)
{
std::unique_lock lockguard(_mtx);
_game_room.insert({uid, conn});
}
void exit_game_room(uint64_t uid)
{
std::unique_lock lockguard(_mtx);
_game_room.erase(uid);
}
// 判断是否在大厅中
bool is_in_game_hall(uint64_t uid)
{
std::unique_lock lockguard(_mtx);
auto iter = _game_hall.find(uid);
if (iter == _game_hall.end())
return false;
return true;
}
bool is_in_game_room(uint64_t uid)
{
std::unique_lock lockguard(_mtx);
auto iter = _game_room.find(uid);
if (iter == _game_room.end())
return false;
return true;
}
// 获取用户链接
wsserver_t::connection_ptr get_conn_from_game_hall(uint64_t uid)
{
std::unique_lock lockguard(_mtx);
auto iter = _game_hall.find(uid);
if (iter == _game_hall.end())
return wsserver_t::connection_ptr();
return iter->second;
}
wsserver_t::connection_ptr get_conn_from_game_room(uint64_t uid)
{
std::unique_lock lockguard(_mtx);
auto iter = _game_room.find(uid);
if (iter == _game_room.end())
return wsserver_t::connection_ptr();
return iter->second;
}
};
#endif
房间管理:
#ifndef __MY_ROOM__
#define __MY_ROOM__
#include "Util/util.hpp"
#include "log.hpp"
#include "online.hpp"
#include "db.hpp"
#include
// 一些必要的宏观定义
#define BOARD_ROW 15
#define BOARD_COL 15
#define CHESS_WHITE 1
#define CHESS_BLACK 2
const int dx[8] = {0, 0, 1, -1, -1, 1, 1, -1};
const int dy[8] = {-1, 1, 0, 0, 1, 1, -1, -1};
// 两种房间状态
typedef enum
{
GAME_START,
GAME_OVER
} room_status;
class room
{
private:
uint64_t _room_id;
room_status _statu;
uint64_t _white_id;
uint64_t _black_id;
int _player_count; // 玩家个数
int _chess_count; // 走棋个数
user_table *_user_tb;
online_manager *_online_user;
std::vector> _board; // 棋盘
public:
room(uint64_t room_id, user_table *user_tb, online_manager *online_user)
: _statu(GAME_START), _player_count(0), _chess_count(0),
_user_tb(user_tb), _online_user(online_user),
_board(BOARD_ROW, std::vector(BOARD_COL, 0))
{
DBG_LOG("room create success:%lu", _room_id);
}
~room()
{
DBG_LOG("%lu 房间销毁成功!!", _room_id);
}
public:
uint64_t room_id() { return _room_id; }
room_status statu() { return _statu; }
int player_count() { return _player_count; }
void add_white_user(uint64_t uid)
{
_white_id = uid;
_player_count++;
}
void add_black_user(uint64_t uid)
{
_black_id = uid;
_player_count++;
}
uint64_t get_white_id() { return _white_id; }
uint64_t get_black_id() { return _black_id; }
private:
uint64_t check_win(int chess_row, int chess_col, int chess_color)
{
// (0,1) -> 横向
// (1,0) -> 纵向
// (-1,1)-> 正斜
// (-1,-1) ->反斜
if (five(chess_row, chess_col, 0, 1, chess_color) ||
five(chess_row, chess_col, 1, 0, chess_color) ||
five(chess_row, chess_col, -1, 1, chess_color) ||
five(chess_row, chess_col, -1, -1, chess_color))
{
return chess_col == _white_id ? _white_id : _black_id;
}
return 0;
}
bool five(int row, int col, int row_off, int col_off, int color)
{
// row和col是下棋位置,row_off和col_off是偏移量,也是方向
int count = 1;
// 正向->向右
int search_row = row + row_off;
int search_col = col + col_off;
while (search_row >= 0 && search_row < BOARD_ROW &&
search_col >= 0 && search_col < BOARD_COL &&
_board[search_row][search_col] == color)
{
count++;
search_row += row_off;
search_col += col_off;
}
// 反向 -> 向左
search_row = row + row_off;
search_col = col + col_off;
while (search_row >= 0 && search_row < BOARD_ROW &&
search_col >= 0 && search_col < BOARD_COL &&
_board[search_row][search_col] == color)
{
count++;
search_row -= row_off;
search_col -= col_off;
}
return (count >= 5);
}
public:
// 房间退出
void handler_exit(uint64_t uid)
{
// 任意一方掉线 房间退出
Json::Value json_resp;
if (_statu == GAME_START)
{
uint64_t winner_id = (Json::UInt64)(uid == _white_id ? _black_id : _white_id);
json_resp["optype"] = "put_chess";
json_resp["result"] = true;
json_resp["reason"] = "对⽅掉线,不战⽽胜!";
json_resp["room_id"] = (Json::UInt64)_room_id;
json_resp["uid"] = (Json::UInt64)uid;
// 走棋 返回给前端的
json_resp["row"] = -1;
json_resp["col"] = -1;
json_resp["winner"] = (Json::UInt64)winner_id;
// 统计积分表
uint64_t loser_id = winner_id == _white_id ? _white_id : _black_id;
_user_tb->win(winner_id);
_user_tb->lose(loser_id);
_statu = GAME_OVER;
broadcast(json_resp);
}
_player_count--;
return;
}
// 总的请求函数:可能还是下棋 可能是消息处理
void handler_request(Json::Value &req)
{
Json::Value json_resp;
// 1. 校验房间号是否匹配
uint64_t room_id = req["room_id"].asInt64();
if (room_id != _room_id)
{
json_resp["optype"] = req["optype"].asString();
json_resp["result"] = false;
json_resp["reason"] = "房间号不匹配";
return broadcast(json_resp);
}
// 2.根据不同的请求类型调用不同的处理函数
if (req["optype"].asString() == "put_chess")
{
json_resp = handler_chess(req);
// 出现结果了
if (json_resp["winner"].asInt64() != 0)
{
uint64_t winner_id = json_resp["winner"].asUInt64();
uint64_t loser_id = winner_id == _white_id ? _black_id : _white_id;
_user_tb->win(winner_id);
_user_tb->lose(loser_id);
_statu = GAME_OVER;
}
}
else if (req["optype"].asString() == "chat")
{
json_resp = handler_chat(req);
}
else
{
// 未知请求
json_resp["optype"] = req["optype"].asString();
json_resp["result"] = false;
json_resp["reason"] = "未知请求类型";
}
// 进行广播 这里是测试
// std::string body;
// Util::json_util::serialize(json_resp, body);
// DBG_LOG("房间-广播动作: %s", body.c_str());
return broadcast(json_resp);
}
// 消息处理
Json::Value handler_chat(Json::Value &req)
{
Json::Value json_resp = req;
std::string message = req["message"].asString();
size_t pos = message.find("垃圾");
if (pos != std::string::npos)
{
json_resp["result"] = false;
json_resp["reason"] = "消息中包含敏感词,不能发送!";
return json_resp;
}
json_resp["result"] = true;
return json_resp;
}
// 处理下棋
Json::Value handler_chess(Json::Value &req)
{
Json::Value json_resp;
// 判断房间中两个玩家是否都在线
if (_online_user->is_in_game_hall(_white_id) == false)
{
json_resp["result"] = true;
json_resp["reason"] = "运气真好!对方掉线,不战而胜!";
json_resp["winner"] = (Json::UInt64)_black_id;
return json_resp;
}
if (_online_user->is_in_game_room(_black_id) == false)
{
json_resp["result"] = true;
json_resp["reason"] = "运气真好!对方掉线,不战而胜!";
json_resp["winner"] = (Json::UInt64)_white_id;
return json_resp;
}
// 走棋位置:不合理 或者已经被占用
int chess_row = req["row"].asInt();
int chess_col = req["col"].asInt();
if (_board[chess_row][chess_col] != 0)
{
json_resp["result"] = true;
json_resp["reason"] = "当前位置已经有了其他棋子!";
return json_resp;
}
// 走棋
uint64_t cur_uid = req["uid"].asUInt64();
int cur_color = cur_uid == _white_id ? CHESS_WHITE : CHESS_BLACK;
_board[chess_row][chess_col] = cur_color;
// 判断是否有玩家胜利
uint64_t winner_id = 0;
if (_chess_count > 6)
{
// 如果胜利 返回winner_id handler_request 接收resp返回 检查是否有winner诞生
winner_id = check_win(chess_row, chess_col, cur_color);
if (winner_id != 0)
{
json_resp["reason"] = "五星连珠,战无敌!";
}
}
json_resp["result"] = true;
json_resp["winner"] = (Json::UInt64)winner_id;
return json_resp;
}
// 进行广播
void broadcast(Json::Value &resp)
{
// 1.序列化发送的消息
std::string body;
Util::json_util::serialize(resp, body);
// 2.获取房间中所有⽤⼾的通信连接
// 3.通过websocket发送消息
wsserver_t::connection_ptr wconn = _online_user->get_conn_from_game_room(_white_id);
if (wconn.get() != nullptr)
wconn->send(body);
else
DBG_LOG("房间-白棋玩家连接获取失败");
wsserver_t::connection_ptr bconn = _online_user->get_conn_from_game_room(_black_id);
if (bconn.get() != nullptr)
bconn->send(body);
else
DBG_LOG("房间-黑棋玩家连接获取失败");
return;
}
};
using room_ptr = std::shared_ptr;
class room_manager
{
private:
uint64_t _next_rid; // 用作传入 room的_room_id
user_table *_user_tb;
online_manager *_online_user;
// 房间和房间号
std::unordered_map _rooms;
// 房间号与用户id
std::unordered_map _users;
std::mutex _mtx;
public:
room_manager(user_table *user_tb, online_manager *online_user)
: _next_rid(1), _user_tb(user_tb), _online_user(online_user)
{
DBG_LOG("房间管理模块初始化完毕!");
}
~room_manager() { DBG_LOG("房间管理模块即将销毁!"); }
// 为两个用户创建房间,并返回房间的智能指针管理对象
room_ptr create_room(uint64_t uid1, uint64_t uid2)
{
// 校验两个用户是否都还在游戏大厅中,只有都在才需要创建房间
if (_online_user->is_in_game_hall(uid1) == false)
{
DBG_LOG("用户:%lu 不在大厅中,创建房间失败!", uid1);
return room_ptr();
}
if (_online_user->is_in_game_hall(uid2) == false)
{
DBG_LOG("用户:%lu 不在大厅中,创建房间失败!", uid2);
return room_ptr();
}
// 创建房间,将用户信息添加到房间中
room_ptr rp(new room(_next_rid, _user_tb, _online_user));
{
std::unique_lock lock(_mtx);
rp->add_white_user(uid1);
rp->add_black_user(uid2);
// 将房间信息管理起来
_rooms.insert({_next_rid, rp});
_users.insert(std::make_pair(uid1, _next_rid));
_users.insert(std::make_pair(uid2, _next_rid));
_next_rid++;
}
return rp;
}
/*通过房间ID获取房间信息*/
room_ptr get_room_by_rid(uint64_t rid)
{
std::unique_lock lock(_mtx);
auto it = _rooms.find(rid);
if (it == _rooms.end())
return room_ptr();
return it->second;
}
/*通过用户ID获取房间信息*/
room_ptr get_room_by_uid(uint64_t uid)
{
std::unique_lock lock(_mtx);
auto uit = _users.find(uid);
if (uit == _users.end())
return room_ptr();
auto it = _rooms.find(uit->second);
if (it == _rooms.end())
return room_ptr();
return it->second;
}
/*通过房间ID销毁房间*/
void remove_room(uint64_t rid)
{
// 房间信息,是通过shared_ptr在_rooms中进行管理
// 将shared_ptr从 rooms中移除 shared_ptr --
room_ptr rp = get_room_by_rid(rid);
if (rp.get() == nullptr)
{
return;
}
// 通过房间信息,获取房间中所有用户的ID
uint64_t uid1 = rp->get_white_id();
uint64_t uid2 = rp->get_black_id();
// 移除房间管理中的用户信息
{
std::unique_lock lock(_mtx);
_users.erase(uid1);
_users.erase(uid2);
_rooms.erase(rid);
}
return;
}
/*删除房间中指定用户,如果房间中没有用户了,则销毁房间,用户连接断开时被调用*/
void remove_room_user(uint64_t uid)
{
room_ptr rp = get_room_by_uid(uid);
if (rp.get() == nullptr)
return;
// 处理房间中玩家退出动作
rp->handler_exit(uid);
if (rp->player_count() == 0)
{
remove_room(rp->room_id());
}
return;
}
};
#endif
服务器处理:
#ifndef __MY_SERVER__
#define __MY_SERVER__
#include "db.hpp"
#include "matcher.hpp"
#include "online.hpp"
#include "room.hpp"
#include "session.hpp"
#include "Util/util.hpp"
#define WWWROOT "./wwwroot"
#define DEFAULT_DATABSE "gobang"
class gobang_server
{
private:
// 静态资源根目录 ./wwwroot/ /register.html -> ./wwwroot/register.html
std::string _web_root;
wsserver_t _wssrv;
user_table _ut;
online_manager _om;
room_manager _rm;
matcher _mm;
session_manager _sm;
http /
private:
// 静态资源处理
void file_handler(wsserver_t::connection_ptr &conn)
{
// 1. 获取到请求uri-资源路径,了解客户端请求的页面文件名称
websocketpp::http::parser::request req = conn->get_request();
std::string uri = req.get_uri();
// 2.组合出文件的实际路径 相对根目录 + uri
std::string realpath = _web_root + uri;
// 如果请求的是个目录,增加一个后缀 login.html, / -> /login.html
if (realpath.back() == '/')
{
realpath += "login.html";
}
// 3.文件读取
Json::Value resp_json;
std::string body;
bool ret = Util::file_util::read_file(realpath, body);
// 文件不存在
if (ret == false)
{
body += "";
body += "";
body += "";
body += "";
body += "";
body += " Not Found
";
body += "";
conn->set_status(websocketpp::http::status_code::not_found);
conn->set_body(body);
return;
}
// 5.设置响应正文
conn->set_body(body);
conn->set_status(websocketpp::http::status_code::ok);
}
// 构建http返回
void http_resp(wsserver_t::connection_ptr &conn, bool result,
websocketpp::http::status_code::value code, const std::string &reason)
{
Json::Value json_resp;
json_resp["result"] = result;
json_resp["reason"] = reason;
// 将发送的预发送的json 序列化
std::string resp_body;
Util::json_util::serialize(json_resp, resp_body);
conn->set_body(resp_body);
conn->set_status(code);
conn->append_header("Content-Type", "application/json");
return;
}
bool get_header_val(const std::string &cookie_str, const std::string &key, std::string &val)
{
// Cookie: SSID=XXX; path=/
std::string sep = ";";
std::vector cookie_arr;
Util::string_util::split(cookie_str, sep, cookie_arr);
for (auto &str : cookie_arr)
{
// 对单个cookie字符串,以 = 为间隔进行分割,得到key和val
std::vector tmp_arr;
Util::string_util::split(str, "=", tmp_arr);
if (tmp_arr.size() != 2)
{
continue;
}
if (tmp_arr[0] == key)
{
val = tmp_arr[1];
return true;
}
}
}
private:
// 注册请求
void reg(wsserver_t::connection_ptr &conn)
{
// 根据conn 获取用户信息
websocketpp::http::parser::request req = conn->get_request();
// 1. 获取到请求正文
std::string req_body = conn->get_request_body();
// 这个正文是被序列化过后的字符串
Json::Value info; // 反序列化 用户信息
bool ret = Util::json_util::unserialize(req_body, info);
if (ret == false)
{
DBG_LOG("反序列化注册信息失败");
return http_resp(conn, false, websocketpp::http::status_code::bad_request, "请求正文格式错误!");
}
// 3.进行数据库的用户新增操作
if (info["username"].isNull() || info["password"].isNull())
{
DBG_LOG("用户名或密码不完整");
return http_resp(conn, false, websocketpp::http::status_code::bad_request, "请输入用户名或密码");
}
// 更新数据表
ret = _ut.insert(info);
if (ret == false)
{
DBG_LOG("用户录入失败");
return http_resp(conn, false, websocketpp::http::status_code::bad_request, "用户名已经被占用!");
}
return http_resp(conn, true, websocketpp::http::status_code::ok, "注册成功!");
}
// 登录请求
void login(wsserver_t::connection_ptr &conn)
{
// 1. 获取请求正文,并进行json反序列化,得到用户名和密码
std::string req_body = conn->get_request_body();
Json::Value info;
bool ret = Util::json_util::unserialize(req_body, info);
if (ret == false)
{
DBG_LOG("反序列化登录信息失败");
return http_resp(conn, false, websocketpp::http::status_code::bad_request, "请求的正文格式错误");
}
// 2. 校验正文完整性,进行数据库的用户信息验证
if (info["username"].isNull() || info["password"].isNull())
{
DBG_LOG("用户名密码不完整");
return http_resp(conn, false, websocketpp::http::status_code::bad_request, "请输入用户名/密码");
}
ret = _ut.login(info);
if (ret == false)
{
DBG_LOG("用户名密码错误");
return http_resp(conn, false, websocketpp::http::status_code::bad_request, "用户名密码错误");
}
// 3.验证成功,创建session
uint64_t uid = info["id"].asInt64();
session_ptr ssp = _sm.create_session(uid, LOGIN);
if (ssp.get() == nullptr)
{
DBG_LOG("创建会话失败");
return http_resp(conn, false, websocketpp::http::status_code::internal_server_error, "创建会话失败");
}
// 为会话设置时长:默认登录时 设置30s
_sm.set_session_expire_time(ssp->ssid(), SESSION_TIMEOUT);
// 进行resp返回 需要返回一个ssid 供浏览器设置
// 新的请求 需要携带ssid
std::string cookie_ssid = "SSID=" + std::to_string(ssp->ssid());
conn->append_header("Set-Cookie", cookie_ssid);
return http_resp(conn, true, websocketpp::http::status_code::ok, "登录成功");
}
void info(wsserver_t::connection_ptr &conn)
{
Json::Value err_resp;
std::string cookie_str = conn->get_request_header("Cookie");
if (cookie_str.empty())
{
return http_resp(conn, true, websocketpp::http::status_code::bad_request, "找不到cookie信息,请重新登录");
}
std::string ssid_str;
bool ret = get_header_val(cookie_str, "SSID", ssid_str);
if (ret == false)
{
// cookie中没有ssid,返回错误:没有ssid信息,让客户端重新登录
return http_resp(conn, true, websocketpp::http::status_code::bad_request, "找不到ssid信息,请重新登录");
}
// 从cookie中取出ssid
session_ptr ssp = _sm.get_session_by_ssid(std::stol(ssid_str));
if (ssp.get() == nullptr)
{
return http_resp(conn, true, websocketpp::http::status_code::bad_request, "登录过期,请重新登录");
}
// 从数据库中取出用户信息,进行序列化发送给客户端
uint64_t uid = ssp->get_user();
Json::Value user_info;
ret = _ut.select_by_ID(uid, user_info);
if (ret == false)
{
// 获取用户信息失败,返回错误:找不到用户信息
return http_resp(conn, true, websocketpp::http::status_code::bad_request, "找不到用户信息,请重新登录");
}
// 返回用户数据
std::string body;
Util::json_util::serialize(user_info, body);
conn->set_body(body);
conn->append_header("Content-Type", "application/json");
conn->set_status(websocketpp::http::status_code::ok);
// 这里进行了一次 交互刷新 session
_sm.set_session_expire_time(ssp->ssid(), SESSION_TIMEOUT);
}
public:
void http_handler(websocketpp::connection_hdl hd1)
{
wsserver_t::connection_ptr conn = _wssrv.get_con_from_hdl(hd1);
websocketpp::http::parser::request req = conn->get_request();
std::string method = req.get_method();
std::string uri = req.get_uri();
if (method == "POST" && uri == "/ret")
{
return reg(conn);
}
else if (method == "POST" && uri == "/login")
{
return login(conn);
}
else if (method == "GET" && uri == "/info")
{
return info(conn);
}
else
{
return file_handler(conn);
}
}
websocket /
private:
void ws_resp(wsserver_t::connection_ptr conn, Json::Value &resp)
{
std::string body;
Util::json_util::serialize(resp, body);
conn->send(body);
}
session_ptr get_session_ptr_by_cookie(wsserver_t::connection_ptr conn)
{
Json::Value err_resp;
// 从获取信息中获取cookie
std::string cookie_str = conn->get_request_header("Cookie");
if (cookie_str.empty())
{
err_resp["optye"] = "hall_ready";
err_resp["reason"] = "没有找到cookie信息,需要重新登录";
err_resp["result"] = false;
ws_resp(conn, err_resp);
return session_ptr();
}
// 取出cookie_ssid
std::string ssid_str;
bool ret = get_header_val(cookie_str, "SSID", ssid_str);
if (ret == false)
{
err_resp["optye"] = "hall_ready";
err_resp["reason"] = "没有找到SSID信息,需要重新登录";
err_resp["result"] = false;
ws_resp(conn, err_resp);
return session_ptr();
}
// 找到session
session_ptr ssp = _sm.get_session_by_ssid(std::stol(ssid_str));
if (ssp.get() == nullptr)
{
err_resp["optye"] = "hall_ready";
err_resp["reason"] = "没有找到session信息,需要重新登录";
err_resp["result"] = false;
ws_resp(conn, err_resp);
return session_ptr();
}
return ssp;
}
public:
// 处理大厅、游戏房间 等操作
void wsopen_game_hall(wsserver_t::connection_ptr conn);
void wsclose_game_hall(wsserver_t::connection_ptr conn);
void wsopen_game_room(wsserver_t::connection_ptr conn);
void wsclose_game_room(wsserver_t::connection_ptr conn);
void wsmsg_game_hall(wsserver_t::connection_ptr conn, wsserver_t::message_ptr msg);
void wsmsg_game_room(wsserver_t::connection_ptr conn, wsserver_t::message_ptr msg);
// 分配handler函数
void wsopen_handler(websocketpp::connection_hdl hd1);
void wsclose_handler(websocketpp::connection_hdl hd1);
void wsmsg_handler(websocketpp::connection_hdl hd1, wsserver_t::message_ptr msg);
public:
gobang_server(const std::string &host,
const std::string user,
const std::string pass,
const std::string dbname = DEFAULT_DATABSE,
uint16_t port = 3306,
const std::string &wwwroot = WWWROOT)
: _web_root(wwwroot), _ut(host, user, pass, dbname, port),
_rm(&_ut, &_om), _sm(&_wssrv), _mm(&_rm, &_ut, &_om)
{
// 服务器基本设置
_wssrv.set_access_channels(websocketpp::log::alevel::none);
_wssrv.init_asio();
_wssrv.set_reuse_addr(true);
// 回调函数bind
_wssrv.set_http_handler(std::bind(&gobang_server::http_handler,this,std::placeholders::_1));
_wssrv.set_open_handler(std::bind(&gobang_server::wsopen_handler,this,std::placeholders::_1));
_wssrv.set_close_handler(std::bind(&gobang_server::wsclose_handler,this,std::placeholders::_1));
_wssrv.set_message_handler(std::bind(&gobang_server::wsmsg_handler,this,std::placeholders::_1,std::placeholders::_2));
}
};
void gobang_server::wsopen_game_hall(wsserver_t::connection_ptr conn)
{
// 游戏大厅建立
Json::Value resp_json;
// 1.判断当前用户 登录成功--> ssesion一定存在
session_ptr ssp = get_session_ptr_by_cookie(conn);
if (ssp.get() == nullptr)
{
return;
}
// 2.判断是否重复登录
if (_om.is_in_game_hall(ssp->get_user()) || _om.is_in_game_room(ssp->get_user()))
{
resp_json["optype"] = "hall_ready";
resp_json["reason"] = "玩家重复登录!";
resp_json["result"] = false;
return ws_resp(conn, resp_json);
}
// 加入进 在线连接管理
_om.enter_game_hall(ssp->get_user(), conn);
// 返回给客户进入大厅成功
resp_json["optype"] = "hall_ready";
resp_json["result"] = true;
ws_resp(conn, resp_json);
// 进入大厅 设置session为永久存在
_sm.set_session_expire_time(ssp->get_user(), SESSION_FOREVER);
}
void gobang_server::wsclose_game_hall(wsserver_t::connection_ptr conn)
{
// 游戏大厅进行断开连接
// 检查session是否存在
session_ptr ssp = get_session_ptr_by_cookie(conn);
if (ssp.get() == nullptr)
return;
// 将玩家移除 并且设置session 到期自动销毁
_om.exit_game_hall(ssp->get_user());
_sm.set_session_expire_time(ssp->ssid(), SESSION_TIMEOUT);
}
void gobang_server::wsopen_game_room(wsserver_t::connection_ptr conn)
{
// 建立房间
Json::Value resp_json;
// 通过session获取当前客户端
session_ptr ssp = get_session_ptr_by_cookie(conn);
if (ssp.get() == nullptr)
return;
// 是否重复进入
if (_om.is_in_game_hall(ssp->get_user()) || _om.is_in_game_room(ssp->get_user()))
{
resp_json["optype"] = "room_ready";
resp_json["reason"] = "玩家重复登录!";
resp_json["result"] = false;
return ws_resp(conn, resp_json);
}
// 判断当前用户是否已经创建好了房间 --- 房间管理
room_ptr rp = _rm.get_room_by_uid(ssp->get_user());
if (rp.get() == nullptr)
{
resp_json["optype"] = "room_ready";
resp_json["reason"] = "没有找到玩家的房间信息";
resp_json["result"] = false;
return ws_resp(conn, resp_json);
}
// 将当前用户添加到在线用户管理的游戏房间中
_om.enter_game_room(ssp->get_user(), conn);
// 5. 将session重新设置为永久存在
_sm.set_session_expire_time(ssp->ssid(), SESSION_FOREVER);
resp_json["optype"] = "room_ready";
resp_json["result"] = true;
resp_json["room_id"] = (Json::UInt64)rp->room_id();
resp_json["uid"] = (Json::UInt64)ssp->get_user();
resp_json["white_id"] = (Json::UInt64)rp->get_white_id();
resp_json["black_id"] = (Json::UInt64)rp->get_black_id();
return ws_resp(conn, resp_json);
}
void gobang_server::wsclose_game_room(wsserver_t::connection_ptr conn)
{
// 获取会话信息,识别客户端
session_ptr ssp = get_session_ptr_by_cookie(conn);
if (ssp.get() == nullptr)
{
return;
}
// 1. 将玩家从在线用户管理中移除
_om.exit_game_room(ssp->get_user());
// 2. 将session回复生命周期的管理,设置定时销毁
_sm.set_session_expire_time(ssp->ssid(), SESSION_TIMEOUT);
// 3. 将玩家从游戏房间中移除,房间中所有用户退出了就会销毁房间
_rm.remove_room_user(ssp->get_user());
}
void gobang_server::wsmsg_game_hall(wsserver_t::connection_ptr conn, wsserver_t::message_ptr msg)
{
Json::Value resp_json;
// 身份验证 哪个玩家推送的消息
session_ptr ssp = get_session_ptr_by_cookie(conn);
if (ssp.get() == nullptr)
return;
// 读取msg
std::string req_body = msg->get_payload();
Json::Value req_json;
bool ret = Util::json_util::unserialize(req_body, req_json);
if (ret == false)
{
resp_json["result"] = false;
resp_json["reason"] = "请求信息解析失败";
return ws_resp(conn, resp_json);
}
// 对请求进行处理
if (!req_json["optype"].isNull() && req_json["optype"].asString() == "match_start")
{
// 开始匹配:将用户添加进匹配队列
_mm.add(ssp->get_user());
// 返回匹配成功字样
resp_json["optype"] = "match_start";
resp_json["result"] = true;
return ws_resp(conn, resp_json);
}
else if (!req_json["optype"].isNull() && req_json["optype"].asString() == "match_stop")
{
// 停止匹配:将用户从匹配队列中移除
_mm.del(ssp->get_user());
resp_json["optype"] = "match_stop";
resp_json["result"] = true;
return ws_resp(conn, resp_json);
}
resp_json["optype"] = "unkown";
resp_json["reason"] = "未知类型";
resp_json["result"] = false;
return ws_resp(conn, resp_json);
}
void gobang_server::wsmsg_game_room(wsserver_t::connection_ptr conn, wsserver_t::message_ptr msg)
{
Json::Value resp_json;
session_ptr ssp = get_session_ptr_by_cookie(conn);
if (ssp.get() == nullptr)
return;
// 获取房间信息
room_ptr rp = _rm.get_room_by_uid(ssp->get_user());
if (rp.get() == nullptr)
{
resp_json["optype"] = "unkown";
resp_json["reason"] = "没有找到玩家的房间信息";
resp_json["result"] = false;
DBG_LOG("房间-没有找到玩家房间信息");
return ws_resp(conn, resp_json);
}
// 序列化消息
Json::Value req_json;
std::string req_body = msg->get_payload();
bool ret = Util::json_util::unserialize(req_body, req_json);
if (ret == false)
{
resp_json["optype"] = "unknow";
resp_json["reason"] = "请求解析失败";
resp_json["result"] = false;
DBG_LOG("房间-反序列化请求失败");
return ws_resp(conn, resp_json);
}
// 收到消息 进行消息处理
DBG_LOG("房间:收到房间请求,开始处理....");
return rp->handler_request(req_json);
}
void gobang_server::wsopen_handler(websocketpp::connection_hdl hd1)
{
// websocket长连接建立成功之后的处理函数
// 消息可能是建立大厅 也可能是 建立房间的请求
wsserver_t::connection_ptr conn = _wssrv.get_con_from_hdl(hd1);
websocketpp::http::parser::request req = conn->get_request();
std::string uri = req.get_uri();
if (uri == "/hall")
{
return wsopen_game_hall(conn);
}
else if (uri == "/romm")
{
return wsopen_game_room(conn);
}
return;
}
void gobang_server::wsclose_handler(websocketpp::connection_hdl hd1)
{
wsserver_t::connection_ptr conn = _wssrv.get_con_from_hdl(hd1);
websocketpp::http::parser::request req = conn->get_request();
std::string uri = req.get_uri();
if (uri == "/hall")
{
// 建立了游戏大厅的长连接
return wsclose_game_hall(conn);
}
else if (uri == "/room")
{
// 建立了游戏房间的长连接
return wsclose_game_room(conn);
}
}
void gobang_server::wsmsg_handler(websocketpp::connection_hdl hd1, wsserver_t::message_ptr msg)
{
wsserver_t::connection_ptr conn = _wssrv.get_con_from_hdl(hd1);
websocketpp::http::parser::request req = conn->get_request();
std::string uri = req.get_uri();
if (uri == "/hall")
{
// 建立了游戏大厅的长连接
return wsmsg_game_hall(conn, msg);
}
else if (uri == "/room")
{
// 建立了游戏房间的长连接
return wsmsg_game_room(conn, msg);
}
}
#endif
session块:
#ifndef __MY_SESSION__
#define __MY_SESSION__
#include "Util/util.hpp"
#include
#include
#include
#include
typedef enum
{
UNLOGIN,
LOGIN
} ss_status;
typedef websocketpp::server wsserver_t;
class session
{
private:
// 每个用户id 对应一个 session_id
uint64_t _ssid;
uint64_t _uid;
ss_status _statu; // 用户状态:未登录,已登录
// session关联的定时器
wsserver_t::timer_ptr _tp;
public:
session(uint64_t ssid) : _ssid(ssid) { DBG_LOG("SESSION:%p 被创建", this); }
~session() { DBG_LOG("SESSION %p 被释放!!", this); }
uint64_t ssid() { return _ssid; }
void set_statu(ss_status statu) { _statu = statu; }
void set_user(uint64_t uid) { _uid = uid; }
uint64_t get_user() { return _uid; }
bool is_login() { return (_statu == LOGIN); }
void set_timer(const wsserver_t::timer_ptr &tp) { _tp = tp; }
wsserver_t::timer_ptr &get_timer() { return _tp; }
};
#define SESSION_TIMEOUT 30000
#define SESSION_FOREVER -1
// 管理session信息
// 1.创建 2.插入 3.删除 4.查找
// 5.设置每个session块的 到期时间
using session_ptr = std::shared_ptr;
class session_manager
{
private:
uint64_t _next_ssid; // 自增的session_id
std::mutex _mtx;
// {ssid,session_ptr}
std::unordered_map _sessions;
wsserver_t *_server;
public:
session_manager(wsserver_t *srv) : _next_ssid(1), _server(srv) { DBG_LOG("session管理器初始化完毕!"); }
~session_manager() { DBG_LOG("session管理器即将销毁!"); }
public:
session_ptr create_session(uint64_t uid, ss_status statu)
{
std::unique_lock lock_gaurd(_mtx);
session_ptr ssp(new session(_next_ssid));
ssp->set_user(uid);
ssp->set_statu(statu);
_sessions.insert({_next_ssid, ssp});
_next_ssid++;
return ssp;
}
void append_session(const session_ptr &ssp)
{
std::unique_lock lock(_mtx);
_sessions.insert(std::make_pair(ssp->ssid(), ssp));
}
session_ptr get_session_by_ssid(uint64_t ssid)
{
std::unique_lock lock(_mtx);
auto it = _sessions.find(ssid);
if (it == _sessions.end())
{
return session_ptr();
}
return it->second;
}
void remove_session(uint64_t ssid)
{
std::unique_lock lock(_mtx);
_sessions.erase(ssid);
}
void set_session_expire_time(uint64_t ssid, int ms)
{
// 依赖于websocketpp的定时器来完成session生命周期的管理。
// 1.登录之后,创建session,session需要在指定时间无通信后删除
// 2.当进入游戏大厅,或者游戏房间,这个session就应该永久存在
// 3.等到退出游戏大厅,或者游戏房间,这个session应该被重新设置为临时,在长时间无通信后被删除
session_ptr ssp = get_session_by_ssid(ssid);
if (ssp.get() == nullptr)
return;
// 获取websocket提供的定时器
wsserver_t::timer_ptr tp = ssp->get_timer();
if(tp.get() == nullptr && ms == SESSION_FOREVER)
{
// session永久存储
return;
}
else if(tp.get() == nullptr && ms != SESSION_FOREVER)
{
// 在session永久存在的情况下,进行删除
// 重新为这个session 设置定时器
// 并且在设置之前 需要将 这个session删除
wsserver_t::timer_ptr tmp_tp = _server->set_timer(ms,
std::bind(&session_manager::remove_session,this,ssid));
ssp->set_timer(tmp_tp);
}
else if(tp.get() != nullptr && ms == SESSION_FOREVER)
{
// 重新设置为永久
}
else if(tp.get() != nullptr && ms != SESSION_FOREVER)
{
// 重置session块时间
}
}
};
#endif
本篇到此结束,感谢你的阅读。
祝你好运,向阳而生~