项目篇:模块设计与实现

1.数据管理模块

数据管理模块只要负责对数据库中的数据进行统一的增删查改管理,任何其他模块需要修改数据都需要通过数据管理模块来完成。

1.1数据库设计

创建user表,用来表示用户信息以及积分信息。用户信息主要用来实现登录、注册、游戏对战数据管理等功能,积分信息用来实现匹配功能(初始积分默认为1000)。

1.1.1user表的创建

复制代码
CREATE TABLE `user` (
  `id` int NOT NULL AUTO_INCREMENT,
  `username` varchar(32) NOT NULL,
  `password` varchar(64) NOT NULL,
  `score` int DEFAULT NULL,
  `total_count` int DEFAULT NULL,
  `win_count` int DEFAULT NULL,
  PRIMARY KEY (`id`),
  UNIQUE KEY `username` (`username`),
  UNIQUE KEY `username_2` (`username`),
  UNIQUE KEY `username_3` (`username`)
) ENGINE=InnoDB AUTO_INCREMENT=10 DEFAULT CHARSET=utf8mb3

1.2user_table类

对应数据库中的表都需要进行管理,不同的表的结构不同所以需要很多的类来进行管理。当我们想要修改表时就可以通过实例化类来对表的内容进行操作。

1.2.1user_table类的实现

cpp 复制代码
#ifndef __M_DB_H__
#define __M_DB_H__
#include "util.hpp"
#include <mutex>
#include <assert.h>

class user_table
{
public:
    user_table(const std::string &host,
               const std::string &user,
               const std::string &password,
               const std::string &db, unsigned int port = 3306)
    {
        _mysql = mysql_util::mysql_create(host, user, password, db, port);
        assert(_mysql != NULL);
    }
    // 注册用户
    bool insert(Json::Value &root)
    {
        if(root["username"].isNull()||root["password"].isNull())
        {
            DLOG("Please enter username or password");
            return false;
        }
#define INSERT "insert into user values(null,'%s',MD5('%s'),1000,0,0)"

        char sql[4096] = {0};
        sprintf(sql, INSERT, root["username"].asCString(), root["password"].asCString());

        bool re = mysql_util::mysql_exec(_mysql, sql);
        if (re == false)
        {
            DLOG("New user created error!!");
            return false;
        }
        return true;
    }
    // 登陆验证,并返回用户信息
    bool login(Json::Value &root)
    {
#define LOGIN "select id,score,total_count,win_count from user where username='%s' and password=MD5('%s');"
        char sql[4096] = {0};
        sprintf(sql, LOGIN, root["username"].asCString(), root["password"].asCString());
        MYSQL_RES *res = NULL;
        {
            std::unique_lock<std::mutex> lock(_mutex);
            bool ret = mysql_util::mysql_exec(_mysql, sql);
            if (ret == false)
            {
                DLOG("User login failed!!");
                return false;
            }
            res = mysql_store_result(_mysql);
            if (res == NULL)
            {
                DLOG("have no login user info!!");
                return false;
            }
        }
        int row_num = mysql_num_rows(res);
        if (row_num != 1)
        {
            DLOG("user information is not unique!");
            return false;
        }
        MYSQL_ROW row = mysql_fetch_row(res);
        root["id"] = (Json::UInt64)std::stol(row[0]);
        root["score"] = (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 selete_by_name(std::string name, Json::Value &root)
    {
#define USER_BY_NAME "select id,score,total_count,win_count from user where username='%s';"
        char sql[4096] = {0};
        sprintf(sql, USER_BY_NAME, name.c_str());
        MYSQL_RES *res = NULL;
        {
            std::unique_lock<std::mutex> lock(_mutex);
            bool ret = mysql_util::mysql_exec(_mysql, sql);
            if (ret == false)
            {
                DLOG("User login failed!!");
                return false;
            }
            res = mysql_store_result(_mysql);
            if (res == NULL)
            {
                DLOG("have no login user info!!");
                return false;
            }
        }
        int row_num = mysql_num_rows(res);
        if (row_num != 1)
        {
            DLOG("user information is not unique!");
            return false;
        }
        MYSQL_ROW row = mysql_fetch_row(res);
        root["id"] = (Json::UInt64)std::stol(row[0]);
        root["score"] = (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;
    }
    // 通过id查询用户信息
    bool selete_by_id(uint64_t id, Json::Value &root)
    {
#define USER_BY_ID "select username,score,total_count,win_count from user where id=%d;"
        char sql[4096] = {0};
        sprintf(sql, USER_BY_ID, id);
        MYSQL_RES *res = NULL;
        {
            std::unique_lock<std::mutex> lock(_mutex);
            bool ret = mysql_util::mysql_exec(_mysql, sql);
            if (ret == false)
            {
                DLOG("User login failed!!");
                return false;
            }
            res = mysql_store_result(_mysql);
            if (res == NULL)
            {
                DLOG("have no login user info!!");
                return false;
            }
        }
        int row_num = mysql_num_rows(res);
        if (row_num != 1)
        {
            DLOG("user information is not unique!");
            return false;
        }
        MYSQL_ROW row = mysql_fetch_row(res);
        root["id"] = (Json::UInt64)id;
        root["username"] =row[0];
        root["score"] = (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 win(uint64_t id)
    {
#define USER_WIN "update user set score=score+30,total_count=total_count+1,win_count=win_count+1 where id=%d;"
        char sql[4096] = {0};
        sprintf(sql, USER_WIN, id);
        bool ret = mysql_util::mysql_exec(_mysql, sql);
        if (ret == false)
        {
            DLOG("update win user info failed!!");
            return false;
        }
        return true;
    }
    // 失败
    bool lose(uint64_t id)
    {
        #define USER_LOSE "update user set score=score-30,total_count=total_count+1  where id=%d;"
        char sql[4096] = {0};
        sprintf(sql, USER_LOSE, id);
        bool ret = mysql_util::mysql_exec(_mysql, sql);
        if (ret == false)
        {
            DLOG("update lose user info failed!!");
            return false;
        }
        return true;
    }
    ~user_table()
    {
        mysql_util::mysql_destroy(_mysql);
        _mysql = NULL;
    }

private:
    MYSQL *_mysql;
    std::mutex _mutex;
};

#endif

2.在线用户管理模块

对于在线的用户我要能够根据用户的信息找到客户端进行通信的Socket链接,还要能够判断用户是否在线,或者来判断其掉线,进而决定是否能执行某些操作。用户即可能在游戏大厅又可能在游戏房间中,所以就需要两个链表来管理它们。

2.1在线用户管理模块实现

cpp 复制代码
#ifndef __M_ONLINE_H__
#define __M_ONLINE_H__
#include "util.hpp"
#include <mutex>
#include <unordered_map>
#include <websocketpp/server.hpp>
#include <websocketpp/config/asio_no_tls.hpp>

typedef websocketpp::server<websocketpp::config::asio> websocket_server;

class online_manage
{
public:
    // websocket链接建立成功之后才会进入游戏大厅&游戏房间在线管理
    void enter_game_hall(uint64_t uid, websocket_server::connection_ptr &conn) 
    {
        std::unique_lock<std::mutex> lock(_mutex);
        _hall_user.insert(std::make_pair(uid,conn));
    }
    void enter_game_room(uint64_t uid, websocket_server::connection_ptr &conn)
     {
        std::unique_lock<std::mutex> lock(_mutex);
        _room_user.insert(std::make_pair(uid,conn));
     }
    // 当websocket链接断开后才会移除游戏大厅&游戏房间的在线管理
    void exit_game_hall(uint64_t uid)
     {
        std::unique_lock<std::mutex> lock(_mutex);
        _hall_user.erase(uid);
     }
    void exit_game_room(uint64_t uid)
     {
        std::unique_lock<std::mutex> lock(_mutex);
        _room_user.erase(uid);
     }
    // 判断当前用户是否在游戏大厅/游戏房间
    bool is_in_game_hall(uint64_t uid) 
    {
        std::unique_lock<std::mutex> lock(_mutex);
        auto tl = _hall_user.find(uid);
        if(tl==_hall_user.end())
        {
            return false;
        }
        return true;
    }
    bool is_in_game_room(uint64_t uid) 
    {
        std::unique_lock<std::mutex> lock(_mutex);
        auto tl = _room_user.find(uid);
        if(tl==_room_user.end())
        {
            return false;
        }
        return true;
    }
    // 通过用户ID获取对应链接信息
    websocket_server::connection_ptr get_conn_from_hall(uint64_t uid) 
    {
        std::unique_lock<std::mutex> lock(_mutex);
        auto tl = _hall_user.find(uid);
        if(tl==_hall_user.end())
        {
            return websocket_server::connection_ptr();
        }
        return tl->second;
    }
    websocket_server::connection_ptr get_conn_from_room(uint64_t uid) 
    {
        std::unique_lock<std::mutex> lock(_mutex);
        auto tl = _room_user.find(uid);
        if(tl==_room_user.end())
        {
            return websocket_server::connection_ptr();
        }
        return tl->second;
    }

private:
    std::mutex _mutex;
    std::unordered_map<uint64_t, websocket_server::connection_ptr> _hall_user;
    std::unordered_map<uint64_t, websocket_server::connection_ptr> _room_user;
};

#endif

3.游戏房间管理模块

首先,设计一个房间类,需要有能够表示自己的元素,也就是房间号。房间中要能处理下棋,还要处理聊天请求,其中为了保证文明聊天还需要保证能够检测敏感词。

3.1房间类

房间类中至少要包含:下棋、聊天、检测敏感词、返回房间号、返回房间状态、返回房间人数、添加黑白棋玩家、获取黑白棋id、退出房间、广播操作、判断是否胜利。

3.1.1房间类的实现

cpp 复制代码
#include <iostream>
#include "db.hpp"
#include "online.hpp"

#define BOARD_ROW 15
#define BOARD_COL 15
#define CHESS_WHITE 1
#define CHESS_BLACK 2

typedef enum
{
    GAME_START,
    GAME_OVER
} room_statu;

class room
{
private:
    uint64_t _room_id;
    room_statu _statu;
    uint64_t _white_id;
    uint64_t _black_id;
    int _player_count;
    user_table *_tb_user;
    online_manage *_online_user;
    std::vector<std::vector<int>> _board;

private:
    // 检测敏感词
    bool checkSensitiveWord(const std::string &content)
    {
        // 自定义敏感词列表,按需增删
        const std::vector<std::string> sensitiveList = {
            "垃圾",
            "菜逼",
            "废物"};

        // 遍历所有敏感词逐个匹配
        for (const auto &word : sensitiveList)
        {
            // 字符串find找到返回下标,找不到返回std::string::npos
            if (content.find(word) != std::string::npos)
            {
                return true;
            }
        }
        return false;
    }

    bool five(int row, int col, int row_off, int col_off, int color)
    {
        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);
    }
    uint64_t check_win(int row, int col, int color)
    {
        if (five(row, col, 0, 1, color) ||
            five(row, col, 1, 0, color) ||
            five(row, col, -1, 1, color) ||
            five(row, col, -1, -1, color))
        {
            return color == CHESS_WHITE ? _white_id : _black_id;
        }
        return 0;
    }

public:
    room(uint64_t room_id, user_table *tb_user, online_manage *online_user)
        : _room_id(room_id), _statu(GAME_START), _player_count(0), _tb_user(tb_user), _online_user(online_user), _board(BOARD_ROW, std::vector<int>(BOARD_COL, 0))
    {
        DLOG("%lu 房间创建成功", _room_id);
    }
    // 获取房间id/状态
    uint64_t get_room_id() { return _room_id; }
    room_statu statu() { return _statu; }
    uint64_t get_player_count() { return _player_count; }
    // 添加黑白棋id
    void add_white_id(uint64_t uid)
    {
        _white_id = uid;
        _player_count++;
    }
    void add_black_id(uint64_t uid)
    {
        _black_id = uid;
        _player_count++;
    }
    // 获取黑白棋id
    uint64_t get_white_id() { return _white_id; }
    uint64_t get_black_id() { return _black_id; }
    // 下棋动作
    Json::Value handle_chess(Json::Value &req)
    {
        Json::Value resp = req;
        // 判断双方是否都在线
        int chess_row = req["row"].asInt();
        int chess_col = req["col"].asInt();
        uint64_t cur_id = req["uid"].asInt64();
        if (_online_user->is_in_game_room(_white_id) == false)
        {
            resp["result"] = true;
            resp["reason"] = "运气真好,对方掉线了!";
            resp["winner"] = (Json::UInt64)_black_id;
            return resp;
        }
        if (_online_user->is_in_game_room(_black_id) == false)
        {
            resp["result"] = true;
            resp["reason"] = "运气真好,对方掉线了!";
            resp["winner"] = (Json::UInt64)_white_id;
            return resp;
        }
        // 判断下棋位置是否合理
        if (_board[chess_row][chess_col] != 0)
        {
            resp["result"] = false;
            resp["reason"] = "当前位置已经有其他棋子了,请重新选择!";
            return resp;
        }
        int cur_color = cur_id == _white_id ? CHESS_WHITE : CHESS_BLACK;
        _board[chess_row][chess_col] = cur_color;
        // 判断是否有玩家胜利
        uint64_t win_id = check_win(chess_row, chess_col, cur_color);
        if (win_id != 0)
        {
            resp["reason"] = "五星连珠,大获全胜!";
        }
        resp["result"] = true;
        resp["winner"] = (Json::UInt64)win_id;
        return resp;
    }

    // 聊天动作
    Json::Value handle_chat(Json::Value &req)
    {
        Json::Value resp = req;
        std::string msg = req["message"].asString();
        if (checkSensitiveWord(msg))
        {
            resp["result"] = false;
            resp["reason"] = "发送信息中包含敏感词!";
            return resp;
        }
        resp["result"] = true;
        return resp;
    }

    // 用户退出
    // 用户退出
    void handle_exit(uint64_t uid)
    {
        Json::Value resp;
        if (_statu == GAME_START)
        {
            _statu = GAME_OVER; // 标记游戏结束

            // 计算胜者和败者
            uint64_t winner_id = (uid == _white_id) ? _black_id : _white_id;
            uint64_t loser_id = uid;

            // 更新数据库积分
            _tb_user->win(winner_id);
            _tb_user->lose(loser_id);

            DLOG("掉线处理: 赢家=%lu, 输家=%lu", winner_id, loser_id);

            resp["optype"] = "put_chess";
            resp["result"] = true;
            resp["reason"] = "对方掉线,不战而胜!";
            resp["room_id"] = (Json::UInt64)_room_id;
            resp["uid"] = (Json::UInt64)uid;
            resp["row"] = -1;
            resp["col"] = -1;
            resp["winner"] = (Json::UInt64)winner_id;
            broadcast(resp);
        }
        _player_count--;
        return;
    }

    // 总的处理函数,根据不同请求调用不同函数,将处理的数据广播给其他用户
    void handle_request(Json::Value &req)
    {
        Json::Value resp;
        // 判断请求的房间号跟当前房间是否匹配
        uint64_t room_id = req["room_id"].asUInt64();
        if (room_id != _room_id)
        {
            resp["optype"] = req["optype"].asString();
            resp["result"] = false;
            resp["reason"] = "房间号不匹配!";
            broadcast(resp);
            return;
        }
        // 根据不同请求调用不同函数
        if (req["optype"].asString() == "put_chess")
        {
            resp = handle_chess(req);
            // 判断游戏结束再更新数据库数据
            if (resp["winner"].asInt64() != 0)
            {
                uint64_t winner_id = resp["winner"].asInt64();
                uint64_t lose_id = winner_id == _white_id ? _black_id : _white_id;
                _tb_user->win(winner_id);
                _tb_user->lose(lose_id);
                _statu = GAME_OVER;
            }
        }
        else if (req["optype"].asString() == "chat")
        {
            resp = handle_chat(req);
        }
        else
        {
            resp["optype"] = req["optype"].asString();
            resp["result"] = false;
            resp["reason"] = "未知请求类型!";
        }
        broadcast(resp);
        return;
    }

    // 广播
    void broadcast(Json::Value &rsp)
    {
        // 把应答序列化
        std::string body;
        json_util::Serialization(rsp, body);
        // 发送消息给所有用户
        websocket_server::connection_ptr wconn = _online_user->get_conn_from_room(_white_id);
        if (wconn.get() != nullptr)
        {
            wconn->send(body);
        }
        websocket_server::connection_ptr bconn = _online_user->get_conn_from_room(_black_id);
        if (bconn.get() != nullptr)
        {
            bconn->send(body);
        }
        return;
    }

    ~room()
    {
        DLOG("%lu 房间销毁成功", _room_id);
    }
};

#endif

3.2房间管理类

服务器中会存在很多的房间,就需要实现对房间的管理。主要包含:创建房间、通过房间号查询房间信息、通过用户id查询房间信息、通过房间号删除房间、删除房间的用户(如果房间空了就删除房间)。

3.2.1房间管理类的实现

cpp 复制代码
using room_ptr = std::shared_ptr<room>;

class room_manage
{
private:
    user_table *_tb_user;
    online_manage *_online_user;
    uint64_t _next_rid;
    std::mutex _mutex;
    std::unordered_map<uint64_t, room_ptr> _rooms;
    std::unordered_map<uint64_t, uint64_t> _users;

public:
    room_manage(user_table *ut, online_manage *om)
        : _next_rid(1), _tb_user(ut), _online_user(om)
    {
        DLOG("房间管理模块初始化成功");
    }
    ~room_manage()
    {
        DLOG("房间管理模块即将销毁");
    }
    // 创建房间
    room_ptr create_room(uint64_t uid1, uint64_t uid2)
    {
        // 判断两个用户是否在游戏大厅
        if (_online_user->is_in_game_hall(uid1) == false)
        {
            DLOG("%lu 用户不在游戏大厅", uid1);
            return room_ptr();
        }
        if (_online_user->is_in_game_hall(uid2) == false)
        {
            DLOG("%lu 用户不在游戏大厅", uid2);
            return room_ptr();
        }
        // 创建房间,将用户信息添加到房间中
        std::unique_lock<std::mutex> lock(_mutex);
        room_ptr rp(new room(_next_rid, _tb_user, _online_user));
        rp->add_white_id(uid1);
        rp->add_black_id(uid2);
        // 将房间信息管理起来
        _rooms.insert(std::make_pair(_next_rid, rp));
        _users.insert(std::make_pair(uid1, _next_rid));
        _users.insert(std::make_pair(uid2, _next_rid));
        // 返回房间信息
        _next_rid++; // 创建新房间后自动增加下一个房间的房间号
        return rp;
    }
    // 通过房间号查询房间信息
    room_ptr get_room_by_rid(uint64_t rid)
    {
        std::unique_lock<std::mutex> lock(_mutex);
        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(_mutex);
        auto rit = _users.find(uid);
        if (rit == _users.end())
        {
            return room_ptr();
        }
        uint64_t rid = rit->second;
        auto it = _rooms.find(rid);
        if (it == _rooms.end())
        {
            return room_ptr();
        }
        return it->second;
    }
    // 通过房间号删除房间
    void remove_room(uint64_t rid)
    {
        std::unique_lock<std::mutex> lock(_mutex); // 添加锁

        // 通过房间号获取房间信息
        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();
        // 删除用户管理信息
        _users.erase(uid1);
        _users.erase(uid2);
        // 删除房间管理信息
        _rooms.erase(rid);
    }

    // 删除房间的用户,如果房间空了就删除房间
    void remove_room_user(uint64_t uid)
    {
        // 通过用户id获取房间信息
        room_ptr rp = get_room_by_uid(uid);
        if (rp.get() == nullptr)
        {
            return;
        }

        uint64_t room_id = rp->get_room_id();

        // 删除用户信息(不加锁,因为内部可能广播)
        rp->handle_exit(uid);

        // 加锁检查并删除房间
        std::unique_lock<std::mutex> lock(_mutex);
        if (rp->get_player_count() == 0)
        {
            // 删除用户映射
            uint64_t uid1 = rp->get_white_id();
            uint64_t uid2 = rp->get_black_id();
            _users.erase(uid1);
            _users.erase(uid2);
            // 删除房间
            _rooms.erase(room_id);
        }
    }
};

4.session模块

我们实现的在线五子棋对战项目还是在浏览器上来进行对战的,在进入游戏大厅之前都是使用http协议进行通信的。合同谈判又是无状态无连接的协议,使用为了保证维持用户的登录状态就需要session来保持登录状态。

4.1session类

session类需要包含:标识符(ssid)、用户id、登录状态、超时时间。包含的功能有:设置用户id、登录状态、超时时间,获取用户id、标识符、超时时间,还要能判断是否掉线。

4.1.1session类的实现

cpp 复制代码
#include "room.hpp"

typedef enum
{
    UNLOGIN,
    ONLOGIN
} ss_statu;

class session
{
private:
    uint64_t _ssid;                  // 标识符
    uint64_t _uid;                   // 用户id
    ss_statu _statu;                 // 登录状态
    websocket_server::timer_ptr _tm; // 超时时间
public:
    session(uint64_t sid) : _ssid(sid), _statu(UNLOGIN) { DLOG("session %p 被创建", this); }
    ~session() { DLOG("session %p 被销毁", this); }
    void set_statu(ss_statu statu) { _statu = statu; }
    void set_uid(uint64_t uid) { _uid = uid; }
    uint64_t get_uid() { return _uid; }
    uint64_t get_ssid() { return _ssid; }
    bool is_login() { return _statu == ONLOGIN; }
    void set_tm(const websocket_server::timer_ptr &tm) { _tm = tm; }
    websocket_server::timer_ptr get_tm() { return _tm; }
};

4.2session管理类

房间管理类要包含:session分配器、互斥锁、session表、socket链接、WebSocket 服务端。要实现:创建session、添加session、通过ssid查询session信息、设置过期时间。

4.2.1session管理类的实现

cpp 复制代码
#define SESSION_TIME 60000
#define SESSION_PERMANENT -1
using session_ptr = std::shared_ptr<session>;

class session_manage
{
private:
    uint64_t _next_ssid;
    std::mutex _mutex;
    std::unordered_map<uint64_t, session_ptr> _session;
    websocket_server *_server;

public:
    session_manage(websocket_server *server) : _server(server), _next_ssid(1)
    {
        DLOG("session管理器创建成功");
    }
    ~session_manage() { DLOG("session管理器即将销毁"); }
    session_ptr create_session(uint64_t uid, ss_statu statu) 
    {
        std::unique_lock<std::mutex> lock(_mutex);
        session_ptr spr(new session(_next_ssid));
        spr->set_statu(statu);
        spr->set_uid(uid);
        _session.insert(std::make_pair(_next_ssid,spr));
        _next_ssid++;
        return spr;
    }
    void append_session(session_ptr spr)
    {
        std::unique_lock<std::mutex> lock(_mutex);
        _session.insert(std::make_pair(spr->get_ssid(),spr));
    }
    session_ptr get_session_by_ssid(uint64_t ssid)
    {
        std::unique_lock<std::mutex> lock(_mutex);
        auto it = _session.find(ssid);
        if (it == _session.end())
        {
            DLOG("%lu 会话不存在", ssid);
            return session_ptr();
        }
        return it->second;
    }
    void set_session_time(uint64_t ssid, int ms) 
    {
        session_ptr spr = get_session_by_ssid(ssid);
        if(spr.get() == nullptr)
        {
            return;
        }
        websocket_server::timer_ptr tp = spr->get_tm();
        if(tp.get()==nullptr && ms == SESSION_PERMANENT )
        {
            //在session永久存在的情况下,使其永久存在
            return;
        }
        else if(tp.get()==nullptr && ms != SESSION_PERMANENT)
        {
            //在session永久存在的情况下,设置过期时间
            websocket_server::timer_ptr tmp_tp = _server->set_timer(ms,
                std::bind(&session_manage::remove_session,this,ssid));
            spr->set_tm(tmp_tp);
        }
        else if(tp.get()!=nullptr && ms == SESSION_PERMANENT)
        {
            //在session已经设置过期时间的情况下,设置其永久存在
            tp->cancel();
            spr->set_tm(websocket_server::timer_ptr());
            _server->set_timer(0,std::bind(&session_manage::append_session,this,spr));
        }
        else if(tp.get()!=nullptr && ms != SESSION_PERMANENT)
        {
            //在session已经设置过期时间的情况下,重新设置其过期时间
            tp->cancel();
            spr->set_tm(websocket_server::timer_ptr());
            _server->set_timer(0,std::bind(&session_manage::append_session,this,spr));
            websocket_server::timer_ptr tmp_tp = _server->set_timer(ms,
                std::bind(&session_manage::remove_session,this,ssid));
            spr->set_tm(tmp_tp);
        }
    }
    void remove_session(uint64_t ssid)
    {
        std::unique_lock<std::mutex> lock(_mutex);
        _session.erase(ssid);
    }
};

5.匹配模块

首先,五子棋对战的玩家匹配是根据自己的天梯分数进行匹配的,根据不同分段匹配到不同的玩家。所以需要很多的匹配队列,在这里我们的分段划分为:1.青铜、2.白银、3.黄金。

5.1匹配队列类

同一分段的匹配队列中同时又2个及其以上的人进行匹配时,出队这两个玩家,为他们创建房间,并将他们的信息放到房间中。

match_queue类需要包含:链表(_list)、互斥锁(_mutex)、条件变量(_cond)。包含的功能有:获取队列大小(size)、判断是否为空(empty)、等待条件变量(wait)、压入数据(push)、弹出数据(pop)、移除指定数据(remove)。

5.1.1匹配队列类的实现

cpp 复制代码
#include "room.hpp"
#include <list>

template <class T>
class match_queue
{
private:
    // 使用链表是因为匹配可以退出
    std::list<T> _list;
    // 加锁保证线程安全
    std::mutex _mutex;
    // 条件变量是队列的用户数量<2就阻塞
    std::condition_variable _cond;

public:
    match_queue() {}
    ~match_queue() {}
    int size()
    {
        std::unique_lock<std::mutex> lock(_mutex);
        return _list.size();
    }
    bool empty()
    {
        std::unique_lock<std::mutex> lock(_mutex);
        return _list.empty();
    }
    void wait()
    {
        std::unique_lock<std::mutex> lock(_mutex);
        _cond.wait(lock);
    }
    void push(const T &data)
    {
        std::unique_lock<std::mutex> lock(_mutex);
        _list.push_back(data);
        _cond.notify_all();
    }
    bool pop(T &data)
    {
        std::unique_lock<std::mutex> lock(_mutex);
        if (_list.empty() == true)
        {
            return false;
        }
        data = _list.front();
        _list.pop_front();
        return true;
    }
    void remove(T &data)
    {
        std::unique_lock<std::mutex> lock(_mutex);
        _list.remove(data);
    }
};

5.2匹配队列管理类

match类需要包含:青铜匹配队列(_q_bronze)、白银匹配队列(_q_silver)、黄金匹配队列(_q_gold)、青铜队列线程(_th_bronze)、白银队列线程(_th_silver)、黄金队列线程(_th_gold)、用户表指针(_ut)、在线管理指针(_om)、房间管理指针(_rm)。

包含的功能有:处理匹配(handle_match)、线程入口(th_bronze_entry/th_silver_entry/th_gold_entry)、添加用户到匹配队列(add)、从匹配队列删除用户(del)。

5.2.1匹配队列管理类实现

cpp 复制代码
class match
{
private:
    // 青铜到黄金的匹配队列
    match_queue<uint64_t> _q_bronze;
    match_queue<uint64_t> _q_silver;
    match_queue<uint64_t> _q_gold;
    // 青铜到黄金的匹配队列对应的线程
    std::thread _th_bronze;
    std::thread _th_silver;
    std::thread _th_gold;
    user_table *_ut;
    online_manage *_om;
    room_manage *_rm;

private:
    void handle_match(match_queue<uint64_t> &mq)
    {
        while (1)
        {
            // 等两个用户
            while (mq.size() < 2)
            {
                mq.wait();
            }
            // 出队两个用户
            uint64_t uid1, uid2;
            bool ret = mq.pop(uid1);
            if (ret == false)
            {
                continue;
            }
            ret = mq.pop(uid2);
            if (ret == false)
            {
                this->add(uid1);
                continue;
            }
            // 查看两个用户是否在线
            websocket_server::connection_ptr conn1 = _om->get_conn_from_hall(uid1);
            if (conn1.get() == nullptr)
            {
                this->add(uid2);
                continue;
            }
            websocket_server::connection_ptr conn2 = _om->get_conn_from_hall(uid2);
            if (conn2.get() == nullptr)
            {
                this->add(uid1);
                continue;
            }
            // 创建房间
            room_ptr rp = _rm->create_room(uid1, uid2);
            if (rp.get() == nullptr)
            {
                DLOG("创建房间失败: %lu, %lu", uid1, uid2);
                this->add(uid1);
                this->add(uid2);
                continue;
            }
            DLOG("匹配成功,创建房间: room_id=%lu, white=%lu, black=%lu",
                 rp->get_room_id(), uid1, uid2);
            // 构建应答
            Json::Value root;
            root["optype"] = "match_success";
            root["result"] = true;
            std::string body;
            json_util::Serialization(root, body);
            conn1->send(body);
            conn2->send(body);
        }
    }
    // 线程入口
    void th_bronze_entry() { handle_match(_q_bronze); }
    void th_silver_entry() { handle_match(_q_silver); }
    void th_gold_entry() { handle_match(_q_gold); }

public:
    match(user_table *ut, online_manage *om, room_manage *rm)
        : _ut(ut), _om(om), _rm(rm), _th_bronze(std::thread(&match::th_bronze_entry, this)), _th_silver(std::thread(&match::th_silver_entry, this)), _th_gold(std::thread(&match::th_gold_entry, this))
    {
        DLOG("游戏模块初始化完毕");
    }
    ~match() {}
    bool add(uint64_t uid)
    {
        Json::Value root;
        bool ret = _ut->selete_by_id(uid, root);
        if (ret == false)
        {
            DLOG("获取玩家%d失败", uid);
            return false;
        }
        int score = root["score"].asInt();
        if (score < 2000)
        {
            _q_bronze.push(uid);
        }
        else if (score >= 2000 && score < 3000)
        {
            _q_silver.push(uid);
        }
        else
        {
            _q_gold.push(uid);
        }
        return true;
    }
    bool del(uint64_t uid)
    {
        Json::Value root;
        bool ret = _ut->selete_by_id(uid, root);
        if (ret == false)
        {
            DLOG("获取玩家%d失败", uid);
            return false;
        }
        int score = root["score"].asInt();
        if (score < 2000)
        {
            _q_bronze.pop(uid);
        }
        else if (score >= 2000 && score < 3000)
        {
            _q_silver.pop(uid);
        }
        else
        {
            _q_gold.pop(uid);
        }
        return true;
    }
};
相关推荐
牛油果子哥q1 小时前
【C++ STL vector】C++ STL vector 终极精讲:动态数组底层原理、两倍扩容机制、迭代器失效、增删查改、性能剖析与工程避坑指南
开发语言·c++
流星白龙3 小时前
【MySQL高阶】26.事务(1)
数据库·mysql
三十..3 小时前
Redis 核心原理与高可用架构实践
运维·数据库·redis
为何创造硅基生物3 小时前
独占指针的创建std::make_unique 本身自带堆出现
c++
kyle~3 小时前
ROS 2 与 Isaac Sim 联合仿真(一)体系架构、环境选型与基础通信闭环
c++·机器人·nvidia·仿真·ros2
这个DBA有点耶3 小时前
索引优化深潜(下):索引合并、ICP 与索引设计的实战法则
数据库·mysql·架构
努力努力再努力wz4 小时前
【内存管理与高并发内存池系列】从 mmap 到 malloc:文件映射、匿名映射与 glibc 内存分配机制详解
linux·c语言·数据结构·数据库·c++·qt·链表
八解毒剂4 小时前
数据结构-平衡二叉树——对二叉搜索树的优化
数据结构·c++·算法
JdSnE27zv4 小时前
Qt 操作SQLite数据库
数据库·qt·sqlite