在线五子棋对战

在线五子棋对战_第1张图片

"你经过我每个灿烂时刻,我才真正学会如你般自由~" 


项目介绍:

(1) 项目简介

        本项目是一款网页版的五子棋对战游戏,其主要支持以下几种功能:

● ⽤⼾管理: 实现⽤⼾注册, ⽤⼾登录、获取⽤⼾信息、⽤⼾天梯分数记录、⽤⼾比赛场次记录等.

● 匹配对战: 实现两个玩家在⽹⻚端根据天梯分数匹配游戏对⼿,并进⾏五⼦棋游戏对战的功能.

● 聊天功能: 实现两个玩家在下棋的同时可以进⾏实时聊天的功能.

(2) 核心技术

        ● HTTP/WebSocket/Websocket++  ● JsonCpp  ● Mysql  ● C++11 ● HTML/CSS/JS/AJAX

(3) 环境搭建(Centos-7.6)

① 安装wget⼯具

# 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

在线五子棋对战_第2张图片

③ 安装其他工具

# 安装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数据库服务及开发包

# 获取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
过期Key:
        如果因为GPG KEY的过期导致安装失败:
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
配置Mysql文件:

● 配置 '/etc/my.cnf' 字符集        

[client]
default-character-set=utf8
[mysql]
default-character-set=utf8
[mysqld]
character-set-server=utf8

在线五子棋对战_第3张图片

        

⑤ 安装Websocketpp库

# 远方拉取库
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       

在线五子棋对战_第4张图片

        关于什么是WebSocket可以看看这篇文章:

我们来谈谈websocket_websocket 线程_RNGWGzZs的博客-CSDN博客                


前置工具包:

        该项目牵涉到数据库的常用操作,数据序列化反序列化,文件读写等操作,所以我们先对这些操作进行一个封装,方便之后接口的调用。

mysql:

        其中包含了创建初始化 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);
        }
    };

Json工具:

        包含传输数据的序列化和反序列化过程。

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;
        }
    };

功能模块:

用户管理模块: 在线五子棋对战_第5张图片

        用户管理模块中,我们需要为玩家存储他们的各项身份信息,比如:id,玩家名,天梯分数,总场数,胜场数……这些数据都是存储在磁盘上的,由强大的Mysql进行管理。

        另外,为了取得这些数据,我们实现一个类来完成,user_table类就是为了获取数据库中的数据的。

        

在线⽤⼾管理模块:        

在线五子棋对战_第6张图片

        在线⽤⼾管理,是对于当前游戏⼤厅和游戏房间中的⽤⼾进⾏管理。它需要承担对在线用户以及正在进行五子棋对战的玩家进行管理。并通过一定的策略对玩家进行 "保活" 查询。

                

游戏房间管理:        在线五子棋对战_第7张图片

         这是该项目功能的主逻辑。游戏房间模块是在两个玩家成功匹配后生成的。该房间需要支持游戏玩法和实时聊天的功能。

Session管理:

在线五子棋对战_第8张图片

什么是session?

        http是一种无状态的短连接协议。所谓无状态,就是不知道这条信息或者这份数据是谁发送的。我们可以试想一下,两个正在进行游戏的玩家,会在一张棋盘上落子,客户端点击发送黑白棋子,但在计算机看无非就是一串01序列,压根不知道这条消息是属于谁的。

        由此,服务器为每个⽤⼾浏览器创建⼀个会话对象(session),在需要保存⽤⼾数据时,服务器程序可以把⽤⼾数据写到⽤⼾浏览器独占的session中,当⽤⼾使⽤浏览器访问该程序时,就会携带这个sessio信息,该程序可以从⽤⼾的session中读取该⽤⼾的历史数据,识别该连接对应的⽤⼾,并为⽤⼾提供服务。        在线五子棋对战_第9张图片

         在这里我们需要设计一个session类,并且这个session类不能一直存在,当用户结束对局或者用户下线等,该session对象就需要被清理掉。

匹配队列实现:

在线五子棋对战_第10张图片

        五⼦棋对战的玩家匹配是根据⾃⼰的天梯分数进⾏匹配的,实现玩家匹配的思想⾮常简单,为不同的档次设计各⾃的匹配队列,当⼀个队列中的玩家数量⼤于 等于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

本篇到此结束,感谢你的阅读。

祝你好运,向阳而生~ 

在线五子棋对战_第11张图片

你可能感兴趣的:(项目,c++,数据库,json)