在线五子棋
项目展示
开发环境
- Linux(Ubuntu22.04)
- Vscode/vim
- g++/gdb
- Makefile
项目介绍
本项⽬主要实现⼀个⽹⻚版的五⼦棋对战游戏, 其主要⽀持以下核⼼功能:
- ⽤⼾管理: 实现⽤⼾注册, ⽤⼾登录、获取⽤⼾信息、⽤⼾天梯分数记录、⽤⼾⽐赛场次记录等
- 匹配对战: 实现两个玩家在⽹⻚端根据天梯分数匹配游戏对⼿,并进⾏五⼦棋游戏对战的功能
- 聊天功能: 实现两个玩家在下棋的同时可以进⾏实时聊天的功能
项目实现
日志实现
代码实现
#ifndef __M_LOGGER_H__
#define __M_LOGGER_H__
#include <stdio.h>
#include <cstdio>
#include <time.h>
#include <string>
#include <pthread.h>
#include <sys/types.h>
#include <unistd.h>
/*
改日志支持格式化打印 或者直接字符串打印
*/
// 其中format是我们消息的格式 __VA_ARGS__是传进来的参数
/*
[可读性很好的时间] [⽇志等级] [进程pid] [打印对应⽇志的⽂件名][⾏号] - 消息内容,⽀持可变参数
[2024-08-04 12:27:03] [DEBUG] [202938] [main.cc] [16] - hello world
[2024-08-04 12:27:03] [DEBUG] [202938] [main.cc] [17] - hello world
[2024-08-04 12:27:03] [DEBUG] [202938] [main.cc] [18] - hello world
[2024-08-04 12:27:03] [DEBUG] [202938] [main.cc] [20] - hello world
[2024-08-04 12:27:03] [DEBUG] [202938] [main.cc] [21] - hello world
[2024-08-04 12:27:03] [WARNING] [202938] [main.cc] [23] - hello world
##__VA_ARGS__ 可以没有处理没有多个参数的情况 在这里如果我们传入的没有格式化字符串 而是一个文本字符串 此时我们消息内容就是format
va_args_替换则啥都没有了
*/
#define INF 0
#define DBG 1
#define ERR 2
#define LOG_LEVEL INF
std::string getlevle(int le)
{
if (le == 0)
return "INF";
else if (le == 1)
return "DBG";
else if (le == 2)
return "LOG";
else
return "none";
}
#define LOG(level, format, ...) \
do \
{ \
if (level < LOG_LEVEL) \
break; \
char *strlevel = (char *)getlevle(level).c_str(); \
time_t t = time(nullptr); \
struct tm *ltm = localtime(&t); \
char tmp[64] = {0}; \
strftime(tmp, 63, "%Y-%m-%d %H:%M:%S", ltm); /*按照指定格式把strurct tm的时间信息格式化到缓冲区*/ \
fprintf(stdout, "[%s] [%s] [%d] [%s] [%d] - " format "\n", tmp, strlevel, (int)getpid(), __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
相关API
获取时间戳
工具模块
该模块封装了我们后续常用的函数如:MySQL操作函数、Json串的序列化和反序列化、字符串分割
代码实现
//util.hpp
#ifndef __M_UTIL_H__
#define __M_UTIL_H__
#include "logerr.hpp"
#include <mysql/mysql.h>
#include <memory>
#include <jsoncpp/json/json.h>
#include <sstream>
#include <fstream>
#include <iostream>
#include <vector>
#include <iostream>
#include <websocketpp/server.hpp>
#include <websocketpp/config/asio_no_tls.hpp>
#include <unordered_map>
typedef websocketpp::server<websocketpp::config::asio> webserve_t;
class mysql_util
{
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句柄
MYSQL *mysql = mysql_init(NULL);
if (mysql == NULL)
{
ERR_LOG("mysql init failed!");
return nullptr;
}
// 建立连接
if (mysql_real_connect(mysql, host.c_str(), username.c_str(), password.c_str(), dbname.c_str(), port, NULL, 0) == NULL)
{
ERR_LOG("connect mysql server failed : %s", mysql_error(mysql));
return nullptr;
}
// 设置字符集
if (mysql_set_character_set(mysql, "utf8") != 0)
{
ERR_LOG("set client character failed : %s", mysql_error(mysql));
return nullptr;
}
return mysql;
}
static bool mysql_exec(MYSQL *mysql, const std::string &sql)
{
int ret = mysql_query(mysql, sql.c_str());
if (ret != 0)
{
ERR_LOG("Executed SQL: %s", sql.c_str());
ERR_LOG("mysql query failed : %s", mysql_error(mysql));
return false;
}
return true;
}
static void mysql_destroy(MYSQL *mysql)
{
if (mysql != NULL)
{
mysql_close(mysql);
}
return;
}
};
class json_util
{
public:
static bool serialize(const Json::Value &root, std::string &str)
{
Json::StreamWriterBuilder swb;
std::unique_ptr<Json::StreamWriter> writer(swb.newStreamWriter());
std::stringstream ss;
int n = writer->write(root, &ss);
if (n != 0)
{
ERR_LOG("serialize error");
return false;
}
str = ss.str();
return true;
}
static bool unserialize(const std::string &str, Json::Value &root)
{
Json::CharReaderBuilder crb;
std::unique_ptr<Json::CharReader> reader(crb.newCharReader());
std::string err;
bool ret = reader->parse(str.c_str(), str.c_str() + str.size(), &root, &err);
if (ret == false)
{
ERR_LOG("parse err");
return false;
}
return true;
}
};
class string_util
{
public:
static int split(const std::string &src, const std::string &sep, std::vector<std::string> &res)
{
// 处理字符串给分割
int sindex = 0;
while (sindex < src.size())
{
// 从sindex的位置开始查找
int pos = src.find(sep, sindex);
if (pos == std::string::npos)
{
res.push_back(src.substr(sindex));
break;
}
// 找到了
// 如果是空串不加
if (sindex == pos)
{
sindex += sep.size();
continue;
}
res.push_back(src.substr(sindex, pos - sindex));
sindex = pos + sep.size();
}
return res.size();
}
};
class file_util
{
public:
static bool read(const std::string &filename, std::string &body)
{
std::ifstream in(filename.c_str(), std::ios::binary);
if (!in.is_open())
{
ERR_LOG("open %s error", filename.c_str());
return false;
}
int fsize = 0;
in.seekg(0, std::ios::end);
fsize = in.tellg();
in.seekg(0, std::ios::beg);
body.resize(fsize);
in.read(&body[0], fsize);
if (!in.good())
{
ERR_LOG("read %s error", filename.c_str());
in.close();
return false;
}
in.close();
return true;
}
};
#endif
其中用到的API介绍
MYSQL API
// Mysql操作句柄初始化
// 参数说明:
// mysql为空则动态申请句柄空间进⾏初始化
// 返回值: 成功返回句柄指针, 失败返回NULL
MYSQL *mysql_init(MYSQL *mysql);
// 连接mysql服务器
// 参数说明:
// mysql--初始化完成的句柄
// host---连接的mysql服务器的地址
// user---连接的服务器的⽤⼾名
// passwd-连接的服务器的密码
// db ----默认选择的数据库名称
// port---连接的服务器的端⼝: 默认0是3306端⼝
// unix_socket---通信管道⽂件或者socket⽂件,通常置NULL
// client_flag---客⼾端标志位,通常置0
// 返回值:成功返回句柄指针,失败返回NULL
MYSQL *mysql_real_connect(MYSQL *mysql, const char *host, const char *user, const char *passwd,const char *db, unsigned int port, const char *unix_socket, unsigned long client_flag);
// 设置当前客⼾端的字符集
// 参数说明:
// mysql--初始化完成的句柄
// csname--字符集名称,通常:"utf8"
// 返回值:成功返回0, 失败返回⾮0
int mysql_set_character_set(MYSQL *mysql, const char *csname)
// 选择操作的数据库
// 参数说明:
// mysql--初始化完成的句柄
// db-----要切换选择的数据库名称
// 返回值:成功返回0, 失败返回⾮0
int mysql_select_db(MYSQL *mysql, const char *db)
// 执⾏sql语句
// 参数说明:
// mysql--初始化完成的句柄
// stmt_str--要执⾏的sql语句
// 返回值:成功返回0, 失败返回⾮0
int mysql_query(MYSQL *mysql, const char *stmt_str)
// 保存查询结果到本地
// 参数说明:
// mysql--初始化完成的句柄
// 返回值:成功返回结果集的指针, 失败返回NULL
MYSQL_RES *mysql_store_result(MYSQL *mysql)
//获取结果集中的⾏数
// 参数说明:
// result--保存到本地的结果集地址
// 返回值:结果集中数据的条数
uint64_t mysql_num_rows(MYSQL_RES *result);
// 获取结果集中的列数
// 参数说明:
// result--保存到本地的结果集地址
// 返回值:结果集中每⼀条数据的列数
unsigned int mysql_num_fields(MYSQL_RES *result)
// 遍历结果集, 并且这个接⼝会保存当前读取结果位置,每次获取的都是下⼀条数据
// 参数说明:
// result--保存到本地的结果集地址
// 返回值:实际上是⼀个char **的指针,将每⼀条数据做成了字符串指针数组
// row[0]-第0列 row[1]-第1列 ...
MYSQL_ROW mysql_fetch_row(MYSQL_RES *result)
// 释放结果集
// 参数说明:
// result--保存到本地的结果集地址
void mysql_free_result(MYSQL_RES *result)
// 关闭数据库客⼾端连接,销毁句柄
// 参数说明:
// mysql--初始化完成的句柄
void mysql_close(MYSQL *mysql)
// 获取mysql接⼝执⾏错误原因
// 参数说明:
// mysql--初始化完成的句柄
const char *mysql_error(MYSQL *mysql)
JsonCPP
//Json::value
class Json::Value{
Value &operator=(const Value &other); //Value重载了[]和=,因此所有的赋值和获取数据都可以通过
Value& operator[](const std::string& key);//简单的⽅式完成 val["name"] = "xx";
Value& operator[](const char* key);
Value removeMember(const char* key);//移除元素
const Value& operator[](ArrayIndex index) const; //val["score"][0]
Value& append(const Value& value);//添加数组元素val["score"].append(88);
ArrayIndex size() const;//获取数组元素个数 val["score"].size();
bool isNull(); //⽤于判断是否存在某个字段
std::string asString() const;//转string string name = val["name"].asString();
const char* asCString() const;//转char* char *name = val["name"].asCString();
Int asInt() const;//转int int age = val["age"].asInt();
float asFloat() const;//转float float weight = val["weight"].asFloat();
bool asBool() const;//转 bool bool ok = val["ok"].asBool();
};
//序列化接口
class JSON_API StreamWriter {
virtual int write(Value const& root, std::ostream* sout) = 0;
}
class JSON_API StreamWriterBuilder : public StreamWriter::Factory {
virtual StreamWriter* newStreamWriter() const;
}
//反序列化
class JSON_API CharReader {
virtual bool parse(char const* beginDoc, char const* endDoc,
Value* root, std::string* errs) = 0;
}
class JSON_API CharReaderBuilder : public CharReader::Factory {
virtual CharReader* newCharReader() const;
}
用户管理模块
该模块就是实现了我们后端的登录、注册、积分跟新、获取用户信息的功能。
代码实现
drop database if exists gobang;
create database if not exists gobang;
use gobang;
create table if not exists user(
id int primary key auto_increment,
username varchar(128) unique key not null,
password varchar(128) not null,
score int,
total_count int,
win_count int
)
#ifndef __M_DB_H__
#define __M_DB_H__
#include "util.hpp"
#include "logerr.hpp"
#include <mutex>
#include <cassert>
#include <string>
class user_table
{
public:
user_table(const std::string &host, const std::string &username, const std::string &password,
const std::string &dbname, uint16_t port)
: _mysql(mysql_util::mysql_create(host, username, password, dbname, port))
{
assert(_mysql != nullptr);
}
~user_table()
{
mysql_util::mysql_destroy(_mysql);
}
bool insert(Json::Value &user)
{
if (user["username"].isNull() || user["password"].isNull())
{
DBG_LOG("insert error:%s", "username or password is null");
return false;
}
std::lock_guard<std::mutex> lock(_mutex);
// 转义用户名
char sql[256] = {0};
std::string username = user["username"].asString();
std::string password = user["password"].asString();
if(username==""||password=="") return false;
snprintf(sql, sizeof(sql) - 1, "insert user values(null,'%s',password('%s'),1000,0,0);", username.c_str(), password.c_str());
DBG_LOG("sql :%s", sql);
bool n = mysql_util::mysql_exec(_mysql, std::string(sql));
if (n == false)
{
DBG_LOG("insert error");
return false;
}
return true;
}
bool login(Json::Value &user)
{
if (user["username"].isNull() || user["password"].isNull())
{
DBG_LOG("login error:%s", "username or password is null");
return false;
}
MYSQL_RES *res = nullptr;
{
std::lock_guard<std::mutex> lock(_mutex);
// 转义输入
char sql[256] = {0};
std::string username = user["username"].asString();
std::string password = user["password"].asString();
if(username==""||password=="") return false;
snprintf(sql, sizeof(sql) - 1, "select id,score,total_count,win_count from user where username= '%s' and password=password('%s');",
username.c_str(), password.c_str());
bool n = mysql_util::mysql_exec(_mysql, sql);
if (n == false)
{
DBG_LOG("login error");
return false;
}
res = mysql_store_result(_mysql);
if (res == nullptr)
{
DBG_LOG("获取结果集错误");
return false;
}
int row = mysql_num_rows(res);
if (row == 0)
{
DBG_LOG("没有查询到相关数据");
return false;
}
else if (row != 1)
{
DBG_LOG("查询结果不唯一");
return false;
}
}
MYSQL_ROW row = mysql_fetch_row(res);
user["id"] = (Json::UInt64)std::stol(row[0]);
user["score"] = (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;
}
// 通过user带出去信息
bool select_by_name(const std::string &name, Json::Value &user)
{
MYSQL_RES *res = nullptr;
{
std::lock_guard<std::mutex> lock(_mutex);
char sql[256] = {0};
snprintf(sql, sizeof(sql) - 1, "select id,score,total_count,win_count from user where username= '%s';", name.c_str());
bool n = mysql_util::mysql_exec(_mysql, sql);
if (n == false)
{
DBG_LOG("select_by_name error");
return false;
}
res = mysql_store_result(_mysql);
if (res == nullptr)
{
DBG_LOG("获取结果集错误");
return false;
}
int row = mysql_num_rows(res);
if (row == 0)
{
DBG_LOG("没有查询到相关数据");
return false;
}
else if (row != 1)
{
DBG_LOG("查询结果不唯一");
return false;
}
}
MYSQL_ROW row = mysql_fetch_row(res);
user["id"] = (Json::UInt64)std::stol(row[0]);
user["score"] = (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;
}
bool select_by_id(uint64_t id, Json::Value &user)
{
MYSQL_RES *res = nullptr;
{
std::lock_guard<std::mutex> lock(_mutex);
char sql[256] = {0};
snprintf(sql, sizeof(sql) - 1, "select username,score,total_count,win_count from user where id=%s;", std::to_string(id).c_str());
bool n = mysql_util::mysql_exec(_mysql, sql);
if (n == false)
{
DBG_LOG("select_by_id error");
return false;
}
res = mysql_store_result(_mysql);
if (res == nullptr)
{
DBG_LOG("获取结果集错误");
return false;
}
int row = mysql_num_rows(res);
if (row == 0)
{
DBG_LOG("没有查询到相关数据");
return false;
}
else if (row != 1)
{
DBG_LOG("查询结果不唯一");
return false;
}
}
MYSQL_ROW row = mysql_fetch_row(res);
user["id"] = (Json::UInt64)id;
user["username"] = row[0];
user["score"] = (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;
}
bool win(uint64_t id)
{
Json::Value vv;
if (select_by_id(id, vv) == false)
{
DBG_LOG("改id %ld不存在", id);
return false;
}
std::lock_guard<std::mutex> lock(_mutex);
char sql[256] = {0};
snprintf(sql, sizeof(sql) - 1, "update user set score=score+30,total_count=total_count+1, win_count=win_count+1 where id=%s;", std::to_string(id).c_str());
bool n = mysql_util::mysql_exec(_mysql, sql);
if (n == false)
{
DBG_LOG("win score error");
return false;
}
return true;
}
bool lose(uint64_t id)
{
Json::Value vv;
if (select_by_id(id, vv) == false)
{
DBG_LOG("改id %ld不存在", id);
return false;
}
std::lock_guard<std::mutex> lock(_mutex);
char sql[256] = {0};
snprintf(sql, sizeof(sql) - 1, "update user set score=score-30,total_count=total_count+1 where id=%s;", std::to_string(id).c_str());
DBG_LOG("%s", sql);
bool n = mysql_util::mysql_exec(_mysql, sql);
if (n == false)
{
DBG_LOG("lose socre error");
return false;
}
return true;
}
private:
MYSQL *_mysql;
std::mutex _mutex;
};
#endif
长连接管理模块
该模块负责我们后端对游戏大厅和游戏房间的长连接管理。可以通过用户id获取用户的长连接。从而通过连接和客户端进行交互。
代码实现
#ifndef __M_ONLINE_H__
#define __M_ONLINE_H__
#include "util.hpp"
#include "logerr.hpp"
class online
{
private:
// 在线分为两者 一种是在大厅 一种在游戏房间内
std::unordered_map<uint32_t, webserve_t::connection_ptr> _hall_user;
std::unordered_map<uint32_t, webserve_t::connection_ptr> _room_user;
std::mutex _mutex;
public:
// 进入大厅 进入房间接口 在websocket连接建立时
void enter_game_hall(uint64_t uid, webserve_t::connection_ptr &conn)
{
std::unique_lock<std::mutex> lock(_mutex);
_hall_user.insert(std::make_pair(uid, conn));
}
void enter_game_room(uint64_t uid, webserve_t::connection_ptr &conn)
{
std::unique_lock<std::mutex> lock(_mutex);
_room_user.insert(std::make_pair(uid, conn));
}
// 离开游戏大厅 和游戏房间 在websocket连接断开时
void exit_game_hall(uint64_t uid)
{
std::unique_lock<std::mutex> lock(_mutex);
//DBG_LOG("uid=%s离开游戏大厅", std::to_string(uid).c_str());
_hall_user.erase(uid);
}
void exit_game_room(uint64_t uid)
{
std::unique_lock<std::mutex> lock(_mutex);
DBG_LOG("uid=%s离开房间大厅", std::to_string(uid).c_str());
_room_user.erase(uid);
}
// 查询是否在游戏大厅中 或者是否在游戏房间中
bool is_in_game_hall(uint64_t uid)
{
std::unique_lock<std::mutex> lock(_mutex);
auto it = _hall_user.find(uid);
if (it == _hall_user.end())
{
return false;
}
return true;
}
bool is_in_game_room(uint64_t uid)
{
std::unique_lock<std::mutex> lock(_mutex);
auto it = _room_user.find(uid);
if (it == _room_user.end())
{
return false;
}
return true;
}
void show_hall()
{
std::unique_lock<std::mutex> lock(_mutex);
DBG_LOG("hall_online_num=%d",(int)_hall_user.size());
for (auto &ch : _hall_user)
{
DBG_LOG("hall-- uid=%s",std::to_string(ch.first).c_str());
}
}
void show_room()
{
std::unique_lock<std::mutex> lock(_mutex);
for (auto &ch : _room_user)
{
DBG_LOG("room-- uid=%s",std::to_string(ch.first).c_str());
}
}
// 如果在线我们可以通过id 获取连接
webserve_t::connection_ptr get_conn_from_hall(uint64_t uid)
{
std::unique_lock<std::mutex> lock(_mutex);
auto it = _hall_user.find(uid);
if (it == _hall_user.end())
{
return webserve_t::connection_ptr(); // 就是一个void 指针默认为空指针
}
return it->second;
}
webserve_t::connection_ptr get_conn_from_room(uint64_t uid)
{
std::unique_lock<std::mutex> lock(_mutex);
auto it = _room_user.find(uid);
if (it == _room_user.end())
{
return webserve_t::connection_ptr();
}
return it->second;
}
};
#endif
房间模块
代码实现
该模块负责房间内所有可能收到的浏览器的请求的处理函数如:下棋、聊天、广播功能、移除用户、销毁房间、获取房间信息、创建房间
#ifndef __M_ROOM_H__
#define __M_ROOM_H__
#include "util.hpp"
#include "onlineuser.hpp"
#include "db.hpp"
#define CHESS_ROW 15
#define CHESS_COL 15
#define CHESS_WHITE 1
#define CHESS_BLACK 2
typedef enum
{
GAMESTART,
GAMEOVER
} room_statu;
/*
room类 将来就是 创建一个房间后有我们的服务器给我们一个房间号 和此时下棋的双方uid
以及管理的用户信息
接着处理请求 返回应答
判断应答中如果返回true 并且此时的Winner_id不为0 去调用我们的退出房间接口
*/
class room
{
private:
uint64_t _room_id;
room_statu _room_statu;
int _player_num;
uint64_t _black_id;
uint64_t _white_id;
std::vector<std::vector<int>> _board;
user_table *_user; // 一个用户的属性
online *_onlineuser; // 一个维护用户连接的句柄 里面管理了所有的在线用户
// 在同一条线上相反的两个方向上 棋子的坐标的偏移量互为相反数
bool help(int row, int col, int row_off, int col_off, int color)
{
int count = 1;
int currow = row + row_off;
int curcol = col + col_off;
while (currow >= 0 && currow < CHESS_ROW && curcol >= 0 && curcol < CHESS_COL && _board[currow][curcol] == color)
{
count++;
currow += row_off;
curcol += col_off;
}
currow = row - row_off;
curcol = col - col_off;
while (currow >= 0 && currow < CHESS_ROW && curcol >= 0 && curcol < CHESS_COL && _board[currow][curcol] == color)
{
count++;
currow -= row_off;
curcol -= col_off;
}
return count >= 5;
}
uint64_t check_iswin(int row, int col, int color)
{
// 有获胜的返回获胜的id 没有人获胜返回0
if (help(row, col, 0, 1, color) || help(row, col, -1, 0, color) || help(row, col, -1, 1, color) || help(row, col, -1, -1, color))
{
// 这里也可以加上积分更新逻辑
return color == CHESS_WHITE ? _white_id : _black_id;
}
return 0;
}
public:
room(uint64_t room_id, user_table *user, online *onlinemannger) : _room_id(room_id), _room_statu(GAMESTART), _player_num(0),
_board(CHESS_ROW, std::vector<int>(CHESS_COL, 0)), _user(user),
_onlineuser(onlinemannger)
{
DBG_LOG("%lu room create success", _room_id);
}
~room()
{
// if(_player_num==0)
DBG_LOG("%lu room destory success", _room_id);
}
room_statu statu() { return _room_statu; }
int player_count() { return _player_num; }
void set_whiteuser_id(uint64_t id)
{
_white_id = id;
_player_num++;
};
void set_blackuser_id(uint64_t id)
{
_black_id = id;
_player_num++;
};
void set_room_id(uint64_t id) { _room_id = id; };
uint64_t get_whiteuser_id() { return _white_id; };
uint64_t get_blackuser_id() { return _black_id; };
uint64_t get_room_id() { return _room_id; };
// 动作1:处理下棋
Json::Value handle_putchess(Json::Value req)
{
// 判断当前的客户端用户是否断开了连接
Json::Value json_resp = req; /*如果失败的话直接设置失败理由即可*/
uint64_t req_uid = req["uid"].asUInt64();
if (_onlineuser->is_in_game_room(_white_id) == false)
{
json_resp["result"] = true;
json_resp["reason"] = "运气真好!白棋掉线,不战而胜!";
json_resp["winner"] = (Json::UInt64)_black_id;
return json_resp;
}
if (_onlineuser->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 put_row = req["row"].asInt();
int put_col = req["col"].asInt();
if (_board[put_row][put_col] != 0)
{
json_resp["result"] = false;
json_resp["reason"] = "当前位置已经有了其他棋子!";
return json_resp;
}
int cur_chess_color = req_uid == _white_id ? CHESS_WHITE : CHESS_BLACK;
_board[put_row][put_col] = cur_chess_color;
// 判断谁获胜 没有人获胜 返回false 接着下棋
uint64_t winner_uid = check_iswin(put_row, put_col, cur_chess_color);
if (winner_uid != 0)
{
// 此时可以跟新我们的积分了啊
json_resp["reason"] = "五星连珠,战无敌!";
_user->win(winner_uid);
_user->lose(_black_id==winner_uid?_white_id:_black_id);
_room_statu=GAMEOVER;
}
json_resp["result"] = true;
json_resp["winner"] = (Json::UInt64)winner_uid; // 浏览器收到应答后判断获胜的是不是为0如果不是客户端继续下棋逻辑
return json_resp;
}
// 动作2:处理我们的聊天
Json::Value handle_chat(Json::Value req)
{
Json::Value json_resp = req;
// 检测消息中是否包含敏感词
std::string msg = req["message"].asString();
size_t pos = msg.find("垃圾");
if (pos != std::string::npos)
{
json_resp["result"] = false;
json_resp["reason"] = "消息中包含敏感词,不能发送!";
return json_resp;
}
// 广播消息---返回消息
json_resp["result"] = true;
return json_resp;
}
// 该函数房间中有人退出
void handle_exit(uint64_t uid)
{
// 如果是下棋中退出,则对方胜利,否则下棋结束了退出,则是正常退出
Json::Value json_resp;
if (_room_statu == GAMESTART)
{
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 ? _black_id : _white_id;
_user->win(winner_id);
_user->lose(loser_id);
_room_statu = GAMEOVER;
handle_broad(json_resp);
}
// 房间中玩家数量--
_player_num--;
return;
}
// 总的请求 不论 下棋还是聊天 包括请求类型字段 以及请求房间id 以及当前操作用户的uid
void handle_request(Json::Value req)
{
std::string req_style = req["optype"].asString();
uint64_t reps_user_id = req["uid"].asUInt64();
uint64_t reps_room_id = req["room_id"].asUInt64();
// 当前请求房间id 和我们的实例化对象id不匹配 直接构建应答 进行广播
Json::Value reps;
if (reps_room_id != _room_id)
{
reps["optype"] = req_style;
reps["result"] = false;
reps["reason"] = "请求房间id 和我们的当前房间id不匹配";
handle_broad(reps);
return;
}
if (req_style == "put_chess")
{
reps = handle_putchess(req);
}
else if (req_style == "chat")
{
reps = handle_chat(req);
}
else
{
reps["optype"] = req_style;
reps["result"] = false;
reps["reason"] = "未知请求";
handle_broad(reps);
return;
}
// 处理完请求
handle_broad(reps);
// 调试打印
std::string body;
json_util::serialize(reps, body);
DBG_LOG("房间-广播动作: %s", body.c_str());
return;
}
// 广播 我们服务器收到请求 发送给房间内的所有的客户端
void handle_broad(Json::Value reps)
{
std::string body;
json_util::serialize(reps, body);
// 获取我们的连接
webserve_t::connection_ptr white_conn = _onlineuser->get_conn_from_room(_white_id);
if (white_conn != nullptr)
{
white_conn->send(body);
}
else
{
DBG_LOG("房间-白棋玩家连接获取失败");
}
webserve_t::connection_ptr black_conn = _onlineuser->get_conn_from_room(_black_id);
if (black_conn != nullptr)
{
black_conn->send(body);
}
else
{
DBG_LOG("房间-黑棋玩家连接获取失败");
}
}
};
/*
改类到时候我们会创建一个对象 其中管理我们的所有的房间信息
*/
using room_ptr = std::shared_ptr<room>;
class room_manager
{
private:
user_table *_user; // 一个用户的属性
online *_onlineuser; // 一个维护用户连接的句柄 里面管理了所有的在线用户
std::mutex _mutex;
uint64_t _room_id;
std::unordered_map<uint64_t, room_ptr> _rid_roomptr;
std::unordered_map<uint64_t, uint64_t> _uid_rid;
public:
room_manager(user_table *utb, online *our)
: _user(utb), _onlineuser(our), _room_id(1)
{
DBG_LOG("房间管理模块初始化完毕!");
}
~room_manager()
{
DBG_LOG("房间管理模块即将销毁!");
}
// 1.匹配成功创建我我们的房间
room_ptr create_room(uint64_t uid1, uint64_t uid2)
{
// 首先确定两者在线
if (_onlineuser->is_in_game_hall(uid1) == false)
{
DBG_LOG("用户:%lu 不在大厅中,创建房间失败!", uid1);
return room_ptr();
}
if (_onlineuser->is_in_game_hall(uid2) == false)
{
DBG_LOG("用户:%lu 不在大厅中,创建房间失败!", uid2);
return room_ptr(); // 空
}
std::unique_lock<std::mutex> lock(_mutex);
{
room_ptr room_info(new room(_room_id, _user, _onlineuser));
room_info->set_whiteuser_id(uid1);
room_info->set_blackuser_id(uid2);
room_info->set_room_id(_room_id);
_uid_rid.insert(std::make_pair(uid1, _room_id));
_uid_rid.insert(std::make_pair(uid2, _room_id));
_rid_roomptr.insert(std::make_pair(_room_id, room_info));
_room_id++;
return room_info;
}
}
/*通过房间号获得房间指针*/
room_ptr get_room_by_rid(uint64_t rid)
{
std::unique_lock<std::mutex> lock(_mutex);
auto pos = _rid_roomptr.find(rid);
if (pos == _rid_roomptr.end())
{
return room_ptr();
}
return pos->second;
}
/*通过用户ID获取房间信息*/
room_ptr get_room_by_uid(uint64_t uid)
{
std::unique_lock<std::mutex> lock(_mutex);
auto tem = _uid_rid.find(uid);
if (tem == _uid_rid.end())
{
return room_ptr();
}
auto pos = _rid_roomptr.find(tem->second);
if (pos == _rid_roomptr.end())
{
return room_ptr();
}
return pos->second;
}
uint64_t get_room_id()
{
return _room_id;
}
/*通过房间ID销毁房间*/
void remove_room(uint64_t rid)
{
// 删除我们的房间之前要先释放房间内的用户
room_ptr roominfor = get_room_by_rid(rid); /*该函数加锁了因此线程安全*/
if (roominfor.get() == nullptr)
{
return;
}
std::unique_lock<std::mutex> lock(_mutex);
{
uint64_t uid1 = roominfor->get_whiteuser_id();
uint64_t uid2 = roominfor->get_blackuser_id();
roominfor->handle_exit(uid1);
roominfor->handle_exit(uid2);
_uid_rid.erase(uid1);
_uid_rid.erase(uid2);
// 当我们的room_ptr的引用计数为0就会自动释放 此时存room信息的就是_rid_roomptr容器 当删除后
// 引用计数为0就会自动调用room类的析构函数
_rid_roomptr.erase(rid);
}
return;
}
/*删除房间中指定用户,如果房间中没有用户了,则销毁房间,用户连接断开时被调用*/
void remove_room_user(uint64_t uid)
{
room_ptr rp = get_room_by_uid(uid);
if (rp.get() == nullptr)
{
return;
}
// 处理房间中玩家退出动作
rp->handle_exit(uid);
// 房间中没有玩家了,则销毁房间
if (rp->player_count() == 0)
{
remove_room(rp->get_room_id());
}
return;
}
};
#endif
session模块
该模块提供通过session获得我们的用户uid,以及设置session的过期时间,以及我们的定时任务的设置
#ifndef __M_SS_H__
#define __M_SS_H__
#include "util.hpp"
#include "logerr.hpp"
typedef enum
{
UNLOGIN,
LOGIN
} ss_statu;
class session
{
private:
uint64_t _uid;
uint64_t _ssid;
webserve_t::timer_ptr _time; // 有没有定时任务 有的话就是短链接 没有的话时长连接
ss_statu _status;
public:
session(uint64_t ssid) : _ssid(ssid) { DBG_LOG("SESSION %p 被创建!!", this); }
~session() { DBG_LOG("SESSION %p 被释放!!", this); }
uint64_t get_uid() { return _uid; };
void set_uid(uint64_t uid) { _uid = uid; };
uint64_t get_ssid() { return _ssid; };
void set_ssid(uint64_t ssid) { _ssid = ssid; };
void set_status(ss_statu status) { _status = status; };
ss_statu get_status() { return _status; };
bool check_time_is_empty() { return _time.get() == nullptr; };
webserve_t::timer_ptr get_timer() { return _time; };
void set_time(webserve_t::timer_ptr time) { _time = time; };
};
#define SESSION_TIMEOUT 30000
#define SESSION_FOREVER -1
using session_ptr = std::shared_ptr<session>;
class session_manager
{
private:
session_ptr _sessionptr;
std::mutex _mutex;
uint64_t _sessionid; // 分配id的计数器
std::unordered_map<uint64_t, session_ptr> _sessioncontain;
webserve_t *_wbr; // 用来设置我们的定时任务
public:
session_manager(webserve_t *wbr)
: _wbr(wbr), _sessionid(1)
{
DBG_LOG("session manager create success");
}
~session_manager()
{
DBG_LOG("session manager delete success");
}
session_ptr create_session(uint64_t uid, ss_statu statu)
{
std::unique_lock<std::mutex> lock(_mutex);
{
session_ptr temsession(new session(_sessionid));
temsession->set_ssid(_sessionid);
temsession->set_status(statu);
temsession->set_uid(uid);
_sessioncontain.insert(std::make_pair(_sessionid,temsession));
_sessionid++;
return temsession;
}
}
session_ptr get_session_by_ssid(uint64_t ssid)
{
std::unique_lock<std::mutex> lock(_mutex);
auto pos = _sessioncontain.find(ssid);
if (pos == _sessioncontain.end())
{
return session_ptr();
}
return pos->second;
}
void remove_session(uint64_t ssid)
{
std::unique_lock<std::mutex> lock(_mutex);
_sessioncontain.erase(ssid);
}
void append_session(const session_ptr &ssp)
{
std::unique_lock<std::mutex> lock(_mutex);
_sessioncontain.insert(std::make_pair(ssp->get_ssid(), ssp));
}
void set_session_expire_time(uint64_t ssid, int ms)
{
// DBG_LOG("SSID = %s",std::to_string(ssid).c_str());
//DBG_LOG("ms = %d",ms);
// 此时有几种情况 如果时登录 注册此时我们采用http
// 我们此时设置我们的过期时间
// 如果我们进入游戏大厅 和 游戏房间此时我们时wessocket连接 我们的session此时时永久的我们不做处理
session_ptr cursessionptr = get_session_by_ssid(ssid);
if (cursessionptr.get() == nullptr)
{
DBG_LOG("没有改会话");
return;
}
webserve_t::timer_ptr tp = cursessionptr->get_timer();
if (tp.get() == nullptr && ms == SESSION_FOREVER)
{
// 1. 在session永久存在的情况下,设置永久存在
return;
}
else if (tp.get() == nullptr && ms != SESSION_FOREVER)
{
// 2. 在session永久存在的情况下,设置指定时间之后被删除的定时任务
webserve_t::timer_ptr tmp_tp = _wbr->set_timer(ms,
std::bind(&session_manager::remove_session, this, ssid));
cursessionptr->set_time(tmp_tp);
}
else if (tp.get() != nullptr && ms == SESSION_FOREVER)
{
// http协议情况下 想把会话时间设置为永久的
tp->cancel();
// 置空代表没有定时任务 就是永久的session了
cursessionptr->set_time(webserve_t::timer_ptr());
// 再次进行设置
_wbr->set_timer(0, std::bind(&session_manager::append_session, this, cursessionptr));
}
else if (tp.get() != nullptr && ms != SESSION_FOREVER)
{
// http协议情况下 想把会话时间设置为永久的
tp->cancel();
// 置空代表没有定时任务 就是永久的session了
cursessionptr->set_time(webserve_t::timer_ptr());
// 再次进行设置
_wbr->set_timer(0, std::bind(&session_manager::append_session, this, cursessionptr));
// 此时我们直接重置我们的定时任务 就是删除我们的session
webserve_t::timer_ptr tem1ptr = _wbr->set_timer(ms,
std::bind(&session_manager::remove_session, this, ssid));
cursessionptr->set_time(tem1ptr);
}
return;
}
};
#endif
服务器模块
该模块负责对以往模块的整合 通过设置我们的四个回调函数根据不同的请求进行我们后台业务的处理
代码实现
#ifndef _h__serve__h_
#define _h__serve__h_
#include "db.hpp"
#include "logerr.hpp"
#include "util.hpp"
#include "session.hpp"
#include "room.hpp"
#include "onlineuser.hpp"
#include "match.hpp"
#endif
#define WWWROOT "./wwwroot"
class gobang_serve
{
private:
std::string _web_root;
webserve_t _serve;
user_table _user;
online _conn_manage;
session_manager _session_manager;
room_manager _room_manager;
matcher _match_manage;
private:
void file_handler(webserve_t::connection_ptr &conn)
{
// 处理HTTP静态请求
websocketpp::http::parser::request req = conn->get_request();
std::string uri = req.get_uri();
// DBG_LOG("uri=%s", uri.c_str());
// 访问我们的路径如果是根目录 返回首页
std::string path;
std::string root = WWWROOT;
std::string body;
if (uri == "/")
{
path = root + "/index.html";
}
else
{
path = root + uri;
}
// 下面去访问静态资源
// DBG_LOG("path=%s", path.c_str());
bool ret = file_util::read(path, body);
Json::Value resp;
if (ret == false)
{
// 此时跳转到404页面
DBG_LOG("读取错误");
body.clear();
file_util::read("./wwwroot/404.html", body);
conn->set_body(body);
conn->set_status(websocketpp::http::status_code::not_found);
// conn->append_header("Content-Type", "text/html");
return;
}
// 走到这里访问成功
conn->set_body(body);
// conn->append_header("Content-Type", "text/html");
conn->set_status(websocketpp::http::status_code::ok);
}
void http_resp(webserve_t::connection_ptr &conn, bool result,
websocketpp::http::status_code::value code, const std::string &reason)
{
Json::Value resp_json;
resp_json["result"] = result;
resp_json["reason"] = reason;
std::string resp_body;
json_util::serialize(resp_json, resp_body);
conn->set_status(code);
conn->set_body(resp_body);
conn->append_header("Content-Type", "application/json");
return;
}
// 注册
void reg(webserve_t::connection_ptr &conn)
{
websocketpp::http::parser::request req = conn->get_request();
std::string reqbody = req.get_body();
DBG_LOG("reqbody:%s", reqbody.c_str());
Json::Value req_json;
bool ret = json_util::unserialize(reqbody, req_json);
if (ret == false)
{
DBG_LOG("反序列化注册失败");
// 下面构建应答
http_resp(conn, false, websocketpp::http::status_code::bad_request, "反序列化失败");
return;
}
if (req_json["username"].isNull() || req_json["password"].isNull())
{
DBG_LOG("登录用户名或密码为空");
http_resp(conn, false, websocketpp::http::status_code::bad_request, "用户名或密码为空");
return;
}
// 下面就是插入数据库
ret = _user.insert(req_json);
if (ret == false)
{
DBG_LOG("插入数据库失败");
http_resp(conn, false, websocketpp::http::status_code::bad_request, "用户名已经被注册");
return;
}
return http_resp(conn, true, websocketpp::http::status_code::ok, "用户注册成功");
}
void login(webserve_t::connection_ptr &conn)
{
websocketpp::http::parser::request req = conn->get_request();
std::string reqbody = req.get_body();
// DBG_LOG("reqbody:%s", reqbody.c_str());
Json::Value req_json;
bool ret = json_util::unserialize(reqbody, req_json);
if (ret == false)
{
DBG_LOG("反序列化登录失败");
// 下面构建应答
http_resp(conn, false, websocketpp::http::status_code::bad_request, "反序列化失败");
return;
}
if (req_json["username"].isNull() || req_json["password"].isNull())
{
DBG_LOG("登录用户名或密码为空");
http_resp(conn, false, websocketpp::http::status_code::bad_request, "用户名或密码为空");
return;
}
// 下面查看在数据库中对比
ret = _user.login(req_json);
if (ret == false)
{
DBG_LOG("登录失败账户或用户名错误");
http_resp(conn, false, websocketpp::http::status_code::bad_request, "登录失败账户或用户名错误");
return;
}
// 下面创建我们的session
uint64_t id = req_json["id"].asInt64();
// DBG_LOG("%s", std::to_string(id).c_str());
session_ptr session = _session_manager.create_session(id, LOGIN);
if (session.get() == nullptr)
{
DBG_LOG("创建会话失败");
return http_resp(conn, false, websocketpp::http::status_code::internal_server_error, "创建会话失败");
}
_session_manager.set_session_expire_time(session->get_ssid(), SESSION_TIMEOUT);
std::string cookie = "SSID=" + std::to_string(session->get_ssid());
conn->append_header("Set-Cookie", cookie);
http_resp(conn, true, websocketpp::http::status_code::ok, "登录成功");
return;
}
bool get_cookie_val(const std::string cookie_str, const std::string &key, std::string &val)
{
std::vector<std::string> strarr;
std::string sep = ";";
string_util::split(cookie_str, sep, strarr);
for (auto &ch : strarr)
{
std::vector<std::string> temarr;
string_util::split(ch, "=", temarr);
if (temarr.size() != 2)
continue;
if (key == temarr[0])
{
val = temarr[1];
return true;
}
}
return false;
}
session_ptr get_session_by_conn(webserve_t::connection_ptr &conn)
{
// 获得我们的cookie
std::string cookiestr = conn->get_request_header("Cookie");
if (cookiestr.empty())
{
return session_ptr();
}
// 通过cookie获得我们的ssid
std::string ssid;
bool ret = get_cookie_val(cookiestr, "SSID", ssid);
if (ret == false)
{
return session_ptr();
}
// 拿到我们的ssid去进行去获取session的句柄
session_ptr ses = _session_manager.get_session_by_ssid(std::stol(ssid));
return ses;
}
void userinfo(webserve_t::connection_ptr &conn)
{
session_ptr ses = get_session_by_conn(conn);
if (ses.get() == nullptr)
{
return http_resp(conn, false, websocketpp::http::status_code::bad_request, "登录过期,请重新登录");
}
// 拿到session去进行获取用户信息通过uid
Json::Value user;
bool ret = _user.select_by_id(ses->get_uid(), user);
if (ret == false)
{
return http_resp(conn, false, websocketpp::http::status_code::bad_request, "找不到用户信息,请重新登录");
}
std::string body;
json_util::serialize(user, body);
conn->set_body(body);
conn->append_header("Content-Type", "application/json");
conn->set_status(websocketpp::http::status_code::ok);
// 4. 刷新session的过期时间
_session_manager.set_session_expire_time(ses->get_ssid(), SESSION_TIMEOUT);
}
// http请求的回调函数 用于处理我们的静态资源 和动态请求如注册 登录 获取用户信息
void http_callback(websocketpp::connection_hdl hdl)
{
webserve_t::connection_ptr conn = _serve.get_con_from_hdl(hdl);
// 拿到请求
websocketpp::http::parser::request req = conn->get_request();
std::string reqmethod = req.get_method();
std::string requri = req.get_uri();
// DBG_LOG("reqmethod=%s", reqmethod.c_str());
// DBG_LOG("requri=%s", requri.c_str());
if (reqmethod == "POST" && requri == "/reg")
{
// DBG_LOG("进入注册处理");
return reg(conn);
}
else if (reqmethod == "POST" && requri == "/login")
{
DBG_LOG("进入登录处理");
return login(conn);
}
else if (reqmethod == "GET" && requri == "/userinfo")
{
// DBG_LOG("进入获取信息处理");
return userinfo(conn);
}
else // 全是静态页面的请求
{
// DBG_LOG("进入静态资源处理");
return file_handler(conn);
}
}
void wb_reps(webserve_t::connection_ptr &conn, Json::Value &reps)
{
std::string reps_str;
json_util::serialize(reps, reps_str);
conn->send(reps_str);
}
void wsopen_game_hall(webserve_t::connection_ptr &conn)
{
// DBG_LOG("进入了游戏大厅的长连接");
//
// 我们游戏大厅websocket建立好大厅的调用函数
// 判断不可以两个用户同时登录
// 我们要通过session拿到uid
// 接着加入连接管理模块同时设置我们的session永久
session_ptr sesptr = get_session_by_conn(conn);
if (sesptr.get() == nullptr)
{
return;
}
// 判断是否异地登陆
Json::Value reps;
uint64_t uid = sesptr->get_uid();
// DBG_LOG("uid=%s", std::to_string(uid).c_str());
if (_conn_manage.is_in_game_hall(uid) == true || _conn_manage.is_in_game_room(uid) == true)
{
reps["optype"] = "hall_ready";
reps["reason"] = "玩家重复登录!";
reps["result"] = false;
return wb_reps(conn, reps);
}
_conn_manage.enter_game_hall(uid, conn);
reps["optype"] = "hall_ready";
reps["result"] = true;
// 设置我们的session过期时间为永久
_session_manager.set_session_expire_time(sesptr->get_ssid(), SESSION_FOREVER);
return wb_reps(conn, reps);
}
// 注意如果是同一个浏览器同时发送匹配请求此时的sessionid是一样的谁先进入房间就会导致另一个重复登录
void wsopen_game_room(webserve_t::connection_ptr &conn)
{
// DBG_LOG("进入了房间大厅的长连接");
Json::Value reps_json;
// 1.获得客户端信息
session_ptr ses = get_session_by_conn(conn);
if (ses == nullptr)
{
return;
}
// 2.判断该用户是否异地登录
// DBG_LOG("在房间建立判断uid=%s是不是在大厅或游戏中",std::to_string(ses->get_uid()).c_str());
if (_conn_manage.is_in_game_hall(ses->get_uid()) || _conn_manage.is_in_game_room(ses->get_uid()))
{
reps_json["optype"] = "room_ready";
reps_json["reason"] = "玩家重复登录!";
reps_json["result"] = false;
return wb_reps(conn, reps_json);
}
// 3.看一下当前用户是否被分配了房间
room_ptr room_infor = _room_manager.get_room_by_uid(ses->get_uid());
if (room_infor.get() == nullptr)
{
reps_json["optype"] = "room_ready";
reps_json["reason"] = "当前用户没有所在的房间!";
reps_json["result"] = false;
return wb_reps(conn, reps_json);
}
// 4.把用户加入房间管理模块
_conn_manage.enter_game_room(ses->get_uid(), conn);
// 5.重新设置我们的session过期时间
_session_manager.set_session_expire_time(ses->get_ssid(), SESSION_FOREVER);
reps_json["optype"] = "room_ready";
reps_json["reason"] = "房间信息获得成功!";
reps_json["result"] = true;
reps_json["uid"] = (Json::UInt64)ses->get_uid();
reps_json["room_id"] = (Json::UInt64)room_infor->get_room_id();
reps_json["white_id"] = (Json::UInt64)room_infor->get_whiteuser_id();
reps_json["black_id"] = (Json::UInt64)room_infor->get_blackuser_id();
return wb_reps(conn, reps_json);
}
// websocket建立成功的回调函数
void wsopen_callback(websocketpp::connection_hdl hdl)
{
// 获取我们的大厅界面后开始建立我们的websocket长连接
// 我们的长连接分为两种
webserve_t::connection_ptr conn = _serve.get_con_from_hdl(hdl);
// 拿到请求
websocketpp::http::parser::request req = conn->get_request();
std::string requri = req.get_uri();
if (requri == "/hall")
{
// 建立了游戏大厅的长连接
return wsopen_game_hall(conn);
}
else if (requri == "/room")
{
// 建立了游戏房间的长连接
return wsopen_game_room(conn);
}
}
void wsclose_game_hall(webserve_t::connection_ptr &conn)
{
// DBG_LOG("进入我们的websocket的断开连接的模块");
session_ptr ssp = get_session_by_conn(conn);
if (ssp.get() == nullptr)
{
return;
}
_conn_manage.exit_game_hall(ssp->get_uid());
// 设置session的过期的时间
_session_manager.set_session_expire_time(ssp->get_ssid(), SESSION_TIMEOUT);
}
void wsclose_game_room(webserve_t::connection_ptr &conn)
{
// 1.获取此时客户端的信息
session_ptr ssp = get_session_by_conn(conn);
if (ssp.get() == nullptr)
{
return;
}
// 2.移出在线用户管理
_conn_manage.exit_game_room(ssp->get_uid());
// 3.设置我们的session过期时间
_session_manager.set_session_expire_time(ssp->get_ssid(), SESSION_TIMEOUT);
// 4.把当用户移除我们的房间
_room_manager.remove_room_user(ssp->get_uid()); /*里面会判断如果房间内用户为0房间就会销毁*/
}
void wsclose_callback(websocketpp::connection_hdl hdl)
{
// 这里也是如果判断关闭的连接是大厅还是room
webserve_t::connection_ptr conn = _serve.get_con_from_hdl(hdl);
// 拿到请求
websocketpp::http::parser::request req = conn->get_request();
std::string requri = req.get_uri();
session_ptr ssp = get_session_by_conn(conn);
if (ssp.get() == nullptr)
{
return;
}
if (requri == "/hall")
{
// 关闭了游戏大厅的长连接
//
DBG_LOG("此时是uid=%s离开我们的游戏大厅", std::to_string(ssp->get_uid()).c_str());
_conn_manage.show_hall();
wsclose_game_hall(conn);
DBG_LOG("-------------------------");
_conn_manage.show_hall();
return;
}
else if (requri == "/room")
{
// 关闭了游戏房间的长连接
wsclose_game_room(conn);
return;
}
}
void wsmsg_game_hall(webserve_t::connection_ptr &conn, webserve_t::message_ptr &msg)
{
// 获取身份信息
session_ptr ses = get_session_by_conn(conn);
if (ses.get() == nullptr)
{
// DBG_LOG(" wsmsg_game_hall 获取session信息错误");
return;
}
Json::Value req;
Json::Value reps;
std::string mes = msg->get_payload();
DBG_LOG("%s", mes.c_str());
bool ret = json_util::unserialize(mes, req);
if (ret == false)
{
reps["result"] = false;
reps["reason"] = "请求信息解析失败";
wb_reps(conn, reps);
return;
}
if (!req["optype"].isNull() && req["optype"].asString() == "match_start")
{
// 我们开始进行匹配
// DBG_LOG("uid=%s进入了匹配队列",std::to_string(ses->get_uid()).c_str());
ret = _match_manage.add(ses->get_uid());
if (ret == false)
{
reps["optype"] = "match_start";
reps["result"] = false;
reps["reason"] = "加入匹配队列失败";
wb_reps(conn, reps);
return;
}
reps["optype"] = "match_start";
reps["result"] = true;
reps["reason"] = "加入匹配队列成功";
wb_reps(conn, reps);
return;
}
else if (!req["optype"].isNull() && req["optype"].asString() == "match_stop")
{
ret = _match_manage.del(ses->get_uid());
if (ret == false)
{
reps["optype"] = "match_stop";
reps["result"] = false;
reps["reason"] = "移除匹配队列失败";
wb_reps(conn, reps);
return;
}
reps["optype"] = "match_stop";
reps["result"] = true;
reps["reason"] = "移除匹配队列成功";
wb_reps(conn, reps);
return;
}
reps["optype"] = "unknow";
reps["reason"] = "请求类型未知";
reps["result"] = false;
return wb_reps(conn, reps);
}
void wsmsg_game_room(webserve_t::connection_ptr &conn, webserve_t::message_ptr &msg)
{
// 先获取客户端信息
session_ptr ses = get_session_by_conn(conn);
if (ses.get() == nullptr)
{
return;
}
// 拿到房间信息
room_ptr roominfor = _room_manager.get_room_by_uid(ses->get_uid());
Json::Value reps_json;
if (roominfor.get() == nullptr)
{
reps_json["optype"] = "unknow";
reps_json["reason"] = "没有找到玩家的房间信息";
reps_json["result"] = false;
DBG_LOG("房间-没有找到玩家房间信息");
return wb_reps(conn, reps_json);
}
//.接着我们进行获取信息内容
std::string body = msg->get_payload();
//.根据信息反序列化
Json::Value req_json;
bool ret = json_util::unserialize(body, req_json);
if (ret == false)
{
reps_json["optype"] = "unknow";
reps_json["reason"] = "room_mesage:反序列化失败";
reps_json["result"] = false;
DBG_LOG("房间-没有找到玩家房间信息");
return wb_reps(conn, reps_json);
}
//把请求json串传给我们的room模块
roominfor->handle_request(req_json);
}
void wsmsg_callback(websocketpp::connection_hdl hdl, webserve_t::message_ptr msg)
{
// 这里处理来自websocket建立后服务端发的信息
webserve_t::connection_ptr conn = _serve.get_con_from_hdl(hdl);
// 拿到请求
websocketpp::http::parser::request req = conn->get_request();
std::string requri = req.get_uri();
// 注意我们收到的uri是hall是我们http时的连接
// 成为websocket通信好我们的请求和应答都变为了json串
if (requri == "/hall")
{
return wsmsg_game_hall(conn, msg);
}
else if (requri == "/room")
{
return wsmsg_game_room(conn, msg);
}
}
public:
// 构造函数完成我们的服务器的初始化
gobang_serve(const std::string &host, const std::string &username, const std::string &password,
const std::string &dbname, uint16_t port)
: _user(host, username, password, dbname, port),
_session_manager(&_serve),
_room_manager(&_user, &_conn_manage),
_match_manage(&_user, &_conn_manage, &_room_manager),
//_match_manage(&_room_manager, &_user, &_conn_manage),
_web_root(WWWROOT)
{
// 设置websocket的日志
_serve.set_access_channels(websocketpp::log::alevel::none);
// 初始化asio中的调度器
_serve.init_asio();
_serve.set_reuse_addr(true);
// 4.设置回调函数
/*websocket握⼿成功回调处理函数*/
_serve.set_open_handler(std::bind(&gobang_serve::wsopen_callback, this, std::placeholders::_1));
/*websocket连接关闭回调处理函数*/
_serve.set_close_handler(std::bind(&gobang_serve::wsclose_callback, this, std::placeholders::_1));
/*websocket消息回调处理函数*/
_serve.set_message_handler(std::bind(&gobang_serve::wsmsg_callback, this, std::placeholders::_1, std::placeholders::_2));
/*http请求回调处理函数*/
_serve.set_http_handler(std::bind(&gobang_serve::http_callback, this, std::placeholders::_1));
}
void start(int port)
{
// 5.设置服务器为监听的端口号
_serve.listen(port);
// 获取和客户端的连接
_serve.start_accept();
// 启动服务器
_serve.run();
}
};
WebSocket相关API
namespace websocketpp {
typedef lib::weak_ptr<void> connection_hdl;
template <typename config>
class endpoint : public config::socket_type {
typedef lib::shared_ptr<lib::asio::steady_timer> timer_ptr;
typedef typename connection_type::ptr connection_ptr;
typedef typename connection_type::message_ptr message_ptr;
typedef lib::function<void(connection_hdl)> open_handler;
typedef lib::function<void(connection_hdl)> close_handler;
typedef lib::function<void(connection_hdl)> http_handler;
typedef lib::function<void(connection_hdl,message_ptr)>
message_handler;
/* websocketpp::log::alevel::none 禁⽌打印所有⽇志*/
void set_access_channels(log::level channels);/*设置⽇志打印等级*/
void clear_access_channels(log::level channels);/*清除指定等级的⽇志*/
/*设置指定事件的回调函数*/
void set_open_handler(open_handler h);/*websocket握⼿成功回调处理函数*/
void set_close_handler(close_handler h);/*websocket连接关闭回调处理函数*/
void set_message_handler(message_handler h);/*websocket消息回调处理函数*/
void set_http_handler(http_handler h);/*http请求回调处理函数*/
/*发送数据接⼝*/
void send(connection_hdl hdl, std::string& payload,
frame::opcode::value op);
void send(connection_hdl hdl, void* payload, size_t len,
frame::opcode::value op);
/*关闭连接接⼝*/
void close(connection_hdl hdl, close::status::value code, std::string&
reason);
/*获取connection_hdl 对应连接的connection_ptr*/
connection_ptr get_con_from_hdl(connection_hdl hdl);
/*websocketpp基于asio框架实现,init_asio⽤于初始化asio框架中的io_service调度
器*/
void init_asio();
/*设置是否启⽤地址重⽤*/
void set_reuse_addr(bool value);
/*设置endpoint的绑定监听端⼝*/
void listen(uint16_t port);
/*对io_service对象的run接⼝封装,⽤于启动服务器*/
std::size_t run();
/*websocketpp提供的定时器,以毫秒为单位*/
timer_ptr set_timer(long duration, timer_handler callback);
};
template <typename config>
class server : public endpoint<connection<config>,config> {
/*初始化并启动服务端监听连接的accept事件处理*/
void start_accept();
};
template <typename config>
class connection
: public config::transport_type::transport_con_type
, public config::connection_base
{
/*发送数据接⼝*/
error_code send(std::string&payload, frame::opcode::value
op=frame::opcode::text);
/*获取http请求头部*/
std::string const & get_request_header(std::string const & key)
/*获取请求正⽂*/
std::string const & get_request_body();
/*设置响应状态码*/
void set_status(http::status_code::value code);
/*设置http响应正⽂*/
void set_body(std::string const & value);
/*添加http响应头部字段*/
void append_header(std::string const & key, std::string const & val);
/*获取http请求对象*/
request_type const & get_request();
/*获取connection_ptr 对应的 connection_hdl */
connection_hdl get_handle();
};
namespace http {
namespace parser {
class parser {
std::string const & get_header(std::string const & key)
}
class request : public parser {
/*获取请求⽅法*/
std::string const & get_method()
/*获取请求uri接⼝*/
std::string const & get_uri()
};
}};
namespace message_buffer {
/*获取websocket请求中的payload数据类型*/
frame::opcode::value get_opcode();
/*获取websocket中payload数据*/
std::string const & get_payload();
};
namespace log {
struct alevel {
static level const none = 0x0;
static level const connect = 0x1;
static level const disconnect = 0x2;
static level const control = 0x4;
static level const frame_header = 0x8;
static level const frame_payload = 0x10;
static level const message_header = 0x20;
static level const message_payload = 0x40;
static level const endpoint = 0x80;
static level const debug_handshake = 0x100;
static level const debug_close = 0x200;
tatic level const devel = 0x400;
static level const app = 0x800;
static level const http = 0x1000;
static level const fail = 0x2000;
static level const access_core = 0x00003003;
static level const all = 0xffffffff;
};
}
namespace http {
namespace status_code {
enum value {
uninitialized = 0,
continue_code = 100,
switching_protocols = 101,
ok = 200,
created = 201,
accepted = 202,
non_authoritative_information = 203,
no_content = 204,
reset_content = 205,
partial_content = 206,
multiple_choices = 300,
moved_permanently = 301,
found = 302,
see_other = 303,
not_modified = 304,
use_proxy = 305,
temporary_redirect = 307,
bad_request = 400,
unauthorized = 401,
payment_required = 402,
forbidden = 403,
not_found = 404,
method_not_allowed = 405,
not_acceptable = 406,
proxy_authentication_required = 407,
request_timeout = 408,
conflict = 409,
gone = 410,
length_required = 411,
precondition_failed = 412,
request_entity_too_large = 413,
request_uri_too_long = 414,
unsupported_media_type = 415,
request_range_not_satisfiable = 416,
expectation_failed = 417,
im_a_teapot = 418,
upgrade_required = 426,
precondition_required = 428,
too_many_requests = 429,
request_header_fields_too_large = 431,
internal_server_error = 500,
not_implemented = 501,
bad_gateway = 502,
service_unavailable = 503,
gateway_timeout = 504,
http_version_not_supported = 505,
not_extended = 510,
network_authentication_required = 511
};}}
namespace frame {
namespace opcode {
enum value {
continuation = 0x0,
text = 0x1,
binary = 0x2,
rsv3 = 0x3,
rsv4 = 0x4,
rsv5 = 0x5,
rsv6 = 0x6,
rsv7 = 0x7,
close = 0x8,
ping = 0x9,
pong = 0xA,
control_rsvb = 0xB,
control_rsvc = 0xC,
control_rsvd = 0xD,
control_rsve = 0xE,
control_rsvf = 0xF,
};}}
}
完整源码
前端的处理我主要负责了js代码的编写,处理了点击事件,与后端业务相结合项目完整源码在我的git仓库项目链接