在线五子棋对战

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


项目介绍:

(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

③ 安装其他工具

# 安装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

        

⑤ 安装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       

        关于什么是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<Json::StreamWriter> 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<Json::CharReader> 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<std::string> &arry)
        {
            arry.clear();
            // idx:表示切分字符串的位置
            size_t idx = 0;
            while (idx < in.size())
            {
                size_t pos = in.find(sep, idx);
                if (pos == std::string::npos)
                {
                    // 虽然原字符串没有sep 但 pos-in != 0
                    arry.push_back(in.substr(idx));
                    break;
                }
                
                // "出现多个相连sep: 33,,,,"
                if (pos == idx) {
                    idx += sep.size();
                    continue;
                }
                res.push_back(src.substr(idx, pos - idx));
                idx = pos + sep.size();
            }
            return arry.size();
        }
    };

    class file_util
    {
    public:
        static bool read_file(const std::string filename, std::string &body)
        {
            std::fstream fs(filename, std::ios::in | std::ios::binary);
            if (!fs.is_open())
            {
                ERR_LOG("file open faild:%s", filename.c_str());
                return false;
            }

            // 使用偏移量的方法 读取整个文件
            // seekg:设置读取偏移指针的位置
            // tellg:返回当前position
            fs.seekg(0, std::ios::end);
            body.resize(fs.tellg());
            fs.seekg(std::ios::beg); // 回到文件开头
            fs.read(&body[0], body.size());
            // good:Check whether state of stream is good
            if (fs.good() == false)
            {
                ERR_LOG("read file content faild:%s", filename.c_str());
                fs.close();
                return false;
            }

            fs.close();
            return true;
        }
    };

功能模块:

用户管理模块: 

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

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

        

在线⽤⼾管理模块:        

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

                

游戏房间管理:        

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

Session管理:

什么是session?

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

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

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

匹配队列实现:

        五⼦棋对战的玩家匹配是根据⾃⼰的天梯分数进⾏匹配的,实现玩家匹配的思想⾮常简单,为不同的档次设计各⾃的匹配队列,当⼀个队列中的玩家数量⼤于 等于2的时候,则意味着同⼀档次中,有2个及以上的⼈要进⾏实战匹配,则出队队列中的前两个⽤⼾,并为之创建游戏房间准备对战。

        剩余的模块属于前端知识,诸如登录页面、注册页面、游戏大厅等等,也就不过多赘述了。


项目实现:

日志打印:

#ifndef __MY_LOG__
#define __MY_LOG__
#include <iostream>
#include <stdio.h>
#include <time.h>
#include <pthread.h>

// 定义日志类等级
#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 <mutex>
#include <cassert>
#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<std::mutex> 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<std::mutex> 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<std::mutex> 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 <mutex>
#include <condition_variable>
#include <list>

template <class T>
class match_queue
{
private:
    // 用链表而不直接使用queue是因为我们有中间删除数据的需要
    std::list<T> _list;
    std::mutex _mtx;
    std::condition_variable _cond;
public:
    int size()
    {
        std::unique_lock<std::mutex> lock_guarud(_mtx);
        return _list.size();
    }

    bool empty()
    {
        std::unique_lock<std::mutex> lock_guarud(_mtx);
        return _list.empty();
    }

    // 阻塞线程
    void wait()
    {
        std::unique_lock<std::mutex> lock_guarud(_mtx);
        _cond.wait(lock_guarud);
    }

    void push(const T &data)
    {
        std::unique_lock<std::mutex> lock_guarud(_mtx);
        _list.push_back(data);
        _cond.notify_all();
    }

    bool pop(T &data)
    {
        std::unique_lock<std::mutex> lock_guarud(_mtx);
        if (_list.empty() == nullptr)
        {
            return false;
        }
        // 输出型参数
        data = _list.front();
        _list.pop_front();
        return true;
    }

    // 移除指定数据
    void remove(T &data)
    {
        std::unique_lock<std::mutex> lock_guarud(_mtx);
        _list.remove(data);
    }
};

class matcher
{
private:
    /*普通选手匹配队列*/
    match_queue<uint64_t> _q_normal;
    /*高手匹配队列*/
    match_queue<uint64_t> _q_high;
    /*大神匹配队列*/
    match_queue<uint64_t> _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<uint64_t> &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 <mutex>
#include <unordered_map>

// asio_no_tls:未加密
#include <websocketpp/server.hpp>
#include <websocketpp/config/asio_no_tls.hpp>

// 服务器类型
typedef websocketpp::server<websocketpp::config::asio> wsserver_t;

// 判断用户是否在线 --->  是否连接到了大厅
// 游戏房间连接

class online_manager
{
private:
    // 取出wsserver_t::connection_ptr 这个是 玩家通信的链接
    // 游戏大厅 and 对战房间
    std::unordered_map<uint64_t, wsserver_t::connection_ptr> _game_hall;
    std::unordered_map<uint64_t, wsserver_t::connection_ptr> _game_room;
    std::mutex _mtx;

public:
    // 进入游戏大厅:将玩家设置进 大厅在线容器中
    void enter_game_hall(uint64_t uid, const wsserver_t::connection_ptr &conn)
    {
        std::unique_lock<std::mutex> lockguard(_mtx);
        _game_hall.insert({uid, conn});
    }

    void exit_game_hall(uint64_t uid)
    {
        std::unique_lock<std::mutex> lockguard(_mtx);
        _game_hall.erase(uid);
    }

    // 进入游戏房间
    void enter_game_room(uint64_t uid, const wsserver_t::connection_ptr &conn)
    {
        std::unique_lock<std::mutex> lockguard(_mtx);
        _game_room.insert({uid, conn});
    }

    void exit_game_room(uint64_t uid)
    {
        std::unique_lock<std::mutex> lockguard(_mtx);
        _game_room.erase(uid);
    }

    // 判断是否在大厅中
    bool is_in_game_hall(uint64_t uid)
    {
        std::unique_lock<std::mutex> 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<std::mutex> 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<std::mutex> 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<std::mutex> 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 <vector>

// 一些必要的宏观定义
#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<std::vector<int>> _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<int>(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<room>;
class room_manager
{
private:
    uint64_t _next_rid; // 用作传入 room的_room_id
    user_table *_user_tb;
    online_manager *_online_user;
    // 房间和房间号
    std::unordered_map<uint64_t, room_ptr> _rooms;
    // 房间号与用户id
    std::unordered_map<uint64_t, uint64_t> _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<std::mutex> 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<std::mutex> 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<std::mutex> 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<std::mutex> 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 += "<html>";
            body += "<head>";
            body += "<meta charset='UTF-8'/>";
            body += "</head>";
            body += "<body>";
            body += "<h1> Not Found </h1>";
            body += "</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<std::string> cookie_arr;
        Util::string_util::split(cookie_str, sep, cookie_arr);
        for (auto &str : cookie_arr)
        {
            // 对单个cookie字符串,以 = 为间隔进行分割,得到key和val
            std::vector<std::string> 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 <unordered_map>
#include <memory>
#include <websocketpp/server.hpp>
#include <websocketpp/config/asio_no_tls.hpp>

typedef enum
{
    UNLOGIN,
    LOGIN
} ss_status;
typedef websocketpp::server<websocketpp::config::asio> 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<session>;
class session_manager
{
private:
    uint64_t _next_ssid; // 自增的session_id
    std::mutex _mtx;
    // {ssid,session_ptr}
    std::unordered_map<uint64_t, session_ptr> _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<std::mutex> 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<std::mutex> lock(_mtx);
        _sessions.insert(std::make_pair(ssp->ssid(), ssp));
    }

    session_ptr get_session_by_ssid(uint64_t ssid)
    {
        std::unique_lock<std::mutex> 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<std::mutex> 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

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

祝你好运,向阳而生~ 

  • 19
    点赞
  • 20
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值