使用Sqlite构建服务端简易DBProxy数据库代理

使用Sqlite构建服务端简易DBProxy数据库代理

  目前博主构建的建议数据库代理(DBProxy)是依附于主线程单独开一个线程处理的(注意,这里只开了一个线程,这个线程依附于主线程)。单独开一个线程管理DBProxy,主要目的是不想让存盘(文件操作)的耗时影响到主线程,即我们将存盘操作从同步策略变换为异步策略。

关于Sqlite的封装可参考:https://ufgnix0802.blog.csdn.net/article/details/128192088

关于C++11的线程库封装可参考:https://ufgnix0802.blog.csdn.net/article/details/128069481

关于单例类模块的实现可参考:https://ufgnix0802.blog.csdn.net/article/details/128121759

  以下代码仅供思路,不做演示。

DBProxy.h

#pragma once

class IDBModule {
public:
    IDBModule()          {}
    virtual ~IDBModule() {}

    bool virtual DBExec(U::DB::DBSqlite* pSqlite) = 0;

    bool virtual OnDBExec() = 0;

    bool virtual Release()  = 0;
};


class DBProxy {
    SINGLETON_CLASS_FUNC_DECL(DBProxy)
public:
    int  Init(const char* dbPath);
    bool UnInit();

    bool Start();
    bool Stop();

    //同步初始化场景和商城
    bool GamaServiceSubmoduleConfig(class Scene* scene, class Shop* shop);

    bool PostHandlerEvent(IDBModule* iDBModule);
    bool OnPostHandlerEvent(IDBModule* iDBModule);
private:
    U::DB::DBSqlite   m_sqlite;
    U::THREAD::Thread m_thread;

    //任务队列
    std::mutex              m_mutexWork;
    std::vector<IDBModule*> m_workArr;

    int m_version;  //数据库版本号
};

DBProxy.cpp

#include "DBProxy.h"

SINGLETON_CLASS_IMPL(DBProxy)

//0, 表示存在数据库且数据库代理初始化成功
//1, 表示不存在数据库且数据库代理初始化成功
//-1,表示初始化失败
int DBProxy::Init(const char* dbPath) {
    assert(nullptr != dbPath);
    int   result = -1;
    char* pErr   = nullptr;
    m_version    = 0;

    LOG_TRACE << "初始化数据库";
    if (!m_sqlite.Create(dbPath)) {
        LOG_ERROR << "init m_sqlite err...";
        goto Exit;
    }

    m_sqlite.Exec("select versio from dbver;", &pErr, 
        [&](int   column_size,
            char* column_value[], 
            char* column_name[]) {
        m_version = atoi(column_value[0]);
    });

    //如果数据库不存在,那么创建数据库,读取配置文件进行模块初始化
    //如果数据库存在,  那么同步读取数据库,初始化模块

    //如果不存在数据库,则创建数据库
    if (0 == m_version) {
        LOG_TRACE << "没有找到基础数据库,创建数据库,版本号:100";
        const char *sql = "        \
            CREATE TABLE `dbver` ( \
            `versio` int NULL,     \
            PRIMARY KEY (`versio`) \
            )";
        pErr = NULL;

        m_sqlite.Begin();
        //创建数据库
        m_sqlite.Exec(sql, &pErr);
        if (NULL != pErr) {
            LOG_ERROR << pErr;
            pErr = NULL;
            goto Exit;
        }
        //插入版本号
        m_sqlite.Exec("insert into dbver values ('100');", &pErr);
        if (NULL != pErr) {
            LOG_ERROR << pErr;
            pErr = NULL;
            goto Exit;
        }

        //启动外键
        m_sqlite.Exec("PRAGMA foreign_keys = ON;", &pErr);
        if (NULL != pErr) {
            LOG_ERROR << pErr;
            pErr = NULL;
            goto Exit;
        }

        sql = "                             \
			CREATE TABLE \"Players\" (      \
			    \"PlayerID\" TEXT NOT NULL, \
			    \"Password\" TEXT NOT NULL, \
			    \"CreateTime\" TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP, \
				PRIMARY KEY(\"PlayerID\")   \
				); \
			CREATE INDEX \"PlayerIndex\" ON \"Players\" (\"PlayerID\"); \
			";

        //创建一张玩家管理表
        m_sqlite.Exec(sql, &pErr);
        if (NULL != pErr) {
            LOG_ERROR << pErr;
            pErr = NULL;
            goto Exit;
        }

        sql = "                                   \
			CREATE TABLE \"Scenes\" (             \
			    \"SceneID\" INTEGER NOT NULL,     \
			    \"Width\"  INTEGER NOT NULL,      \
			    \"Length\" INTEGER NOT NULL,      \
			    \"MarkRoleID\" INTEGER NOT NULL,  \
			    \"CreateTime\" TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP, \
				PRIMARY KEY(\"SceneID\")          \
				); \
			CREATE INDEX \"SceneIndex\" ON \"Scenes\" (\"SceneID\");         \
			";

        //创建一张场景管理表
        m_sqlite.Exec(sql, &pErr);
        if (NULL != pErr) {
            LOG_ERROR << pErr;
            pErr = NULL;
            goto Exit;
        }

        sql = "                              \
			CREATE TABLE \"Roles\" (         \
			    \"RoleID\" INTEGER NOT NULL, \
			    \"SceneID\" INTEGER NOT NULL,\
			    \"PlayerID\" TEXT NOT NULL,  \
			    \"RotX\" REAL NOT NULL,      \
			    \"RotY\" REAL NOT NULL,      \
			    \"Speed\" INTEGER NOT NULL,  \
                \"PosX\" REAL NOT NULL,      \
			    \"PosY\" REAL NOT NULL,      \
			    \"CreateTime\" TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,   \
				PRIMARY KEY(\"RoleID\"),     \
                FOREIGN KEY \(\"PlayerID\"\) REFERENCES Players\(\"PlayerID\"\) \
                FOREIGN KEY \(\"SceneID\"\) REFERENCES Scenes\(\"SceneID\"\) \
				); \
			CREATE INDEX \"RoleIndex\" ON \"Roles\" (\"RoleID\"); \
			";

        //创建一张角色管理表
        m_sqlite.Exec(sql, &pErr);
        if (NULL != pErr) {
            LOG_ERROR << pErr;
            pErr = NULL;
            goto Exit;
        }

        sql = "                              \
			CREATE TABLE \"Bags\" (          \
			    \"RoleID\" INTEGER NOT NULL, \
			    \"Bag\" BLOB NOT NULL,       \
			    \"CreateTime\" TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP, \
				PRIMARY KEY(\"RoleID\"),     \
                FOREIGN KEY \(\"RoleID\"\) REFERENCES Roles\(\"RoleID\"\)    \
				); \
			CREATE INDEX \"BagIndex\" ON \"Bags\" (\"RoleID\"); \
			";
        //创建一张背包管理表
        m_sqlite.Exec(sql, &pErr);
        if (NULL != pErr) {
            LOG_ERROR << pErr;
            pErr = NULL;
            goto Exit;
        }

        sql = "                               \
			CREATE TABLE \"RoleMoneys\" (     \
			    \"RoleID\" INTEGER NOT NULL,  \
			    \"Money\"  INTEGER NOT NULL,  \
			    \"CreateTime\" TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP, \
				PRIMARY KEY(\"RoleID\"),      \
                FOREIGN KEY \(\"RoleID\"\) REFERENCES Roles\(\"RoleID\"\)    \
				); \
			CREATE INDEX \"MoneyIndex\" ON \"RoleMoneys\" (\"RoleID\");      \
			";
        //创建一张角色金钱管理表
        m_sqlite.Exec(sql, &pErr);
        if (NULL != pErr) {
            LOG_ERROR << pErr;
            pErr = NULL;
            goto Exit;
        }

        sql = "                                \
			CREATE TABLE \"Shops\" (           \
			    \"ShopID\" INTEGER NOT NULL,   \
			    \"Shop\" BLOB NOT NULL,        \
			    \"CreateTime\" TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP, \
				PRIMARY KEY(\"ShopID\")        \
				); \
			CREATE INDEX \"ShopIndex\" ON \"Shops\" (\"ShopID\");            \
			";
        //创建一张商城管理表
        m_sqlite.Exec(sql, &pErr);
        if (NULL != pErr) {
            LOG_ERROR << pErr;
            pErr = NULL;
            goto Exit;
        }
        m_sqlite.Commit();

        //设置版本号
        m_version = 100;
        result    = 1;
    }
    else {
        result    = 0;
    }

    LOG_TRACE << "当前数据库版本号:" << m_version;

    if (!m_thread.Init()) {
        goto Exit;
    }

    m_workArr.clear();
    LOG_TRACE << "DB sqlite init success...";
Exit:
    return result;
}

bool DBProxy::UnInit() {
    if (!m_sqlite.UnInit()) {
        return false;
    }

    m_thread.UnInit();
    m_workArr.clear();
    return true;
}

bool DBProxy::Start() {
    LOG_TRACE << "启动数据库线程";
    m_thread.Start(nullptr, [this](U::THREAD::Thread* pThread) {
        while (pThread->IsRun()) {
            //处理任务队列
            if (m_workArr.size() > 0) {
                std::vector<IDBModule*> workArr;
                do {
                    std::lock_guard<std::mutex> __(m_mutexWork);
                    m_workArr.swap(workArr);
                    m_workArr.clear();
                } while (false);

                for (auto& work : workArr) {
                    OnPostHandlerEvent(work);
                }
            }
            //else {
            //    //休息1ms
            //    pThread->Wait();
            //}
        }
    });
    return false;
}

bool DBProxy::Stop() {
    m_sqlite.Destroy();
    m_thread.Close();
    return true;
}

bool DBProxy::GamaServiceSubmoduleConfig(class Scene* scene,
    class Shop* shop) {
    assert(nullptr != scene);
    assert(nullptr != shop);

    char  sql[1024] = { 0 };
    char* pError    = NULL;
    bool  result    = false;

    //数据库同步读,初始化场景及商城
    sprintf(sql, "SELECT * FROM Scenes WHERE SceneID = %d;", 1);
    m_sqlite.Begin();
    //查询场景表
    m_sqlite.Exec(sql, &pError, [&scene](int column_size, char* column_value[],
        char* column_name[]) {
        scene->Init(
            atoi(column_value[1]),
            atoi(column_value[2]),
            atoi(column_value[3]));
    });

    if (NULL != pError) {
        NET_UV_LOG_ERROR("%s\n", pError);
        pError = NULL;
        goto Exit;
    }

    sprintf(sql, "SELECT * FROM Shops WHERE ShopID = %d;", 1);
    m_sqlite.Exec(sql, &pError, [&shop](int column_size, char* column_value[],
        char* column_name[]) {
        TCCamp::ShopSaveData saveData;
        TCCamp::PBShopItem   pbShopItem;

        ShopItem* shopItem = nullptr;

        saveData.ParseFromArray(column_value[1], strlen(column_value[1]));
        for (int index = 0; index < saveData.items().size(); index++) {
            pbShopItem = saveData.items(index);
            shopItem = (ShopItem*)MALLOC(sizeof(ShopItem));
            new(shopItem) ShopItem();
            shopItem->Init(
                pbShopItem.id(),
                pbShopItem.price(),
                pbShopItem.personallimitmaxcount(),
                pbShopItem.globallimitcount(),
                pbShopItem.globallimitmaxcount(),
                pbShopItem.starttime(),
                pbShopItem.endtime());
            shop->AddShopItem(shopItem);
        }
    });

    if (NULL != pError) {
        NET_UV_LOG_ERROR("%s\n", pError);
        pError = NULL;
        goto Exit;
    }
    m_sqlite.Commit();

    result = true;
Exit:
    return result;
}

bool DBProxy::PostHandlerEvent(IDBModule* iDBModule) {
    std::lock_guard<std::mutex> __(m_mutexWork);
    m_workArr.push_back(iDBModule);
    return true;
}

bool DBProxy::OnPostHandlerEvent(IDBModule* iDBModule) {
    iDBModule->DBExec(&m_sqlite);
    //iDBModule->Release();
    return true;
}

使用实例(这里构建一个DBLogin,即登录数据库操作模块)

//登录DB
class DBLogin : public IDBModule {
public:
    bool Init(ReqEvent* req, GameService* gameService, PlayerMgr* playerMgr,
        std::string playerID, std::string pwd) {
        m_reqEvent    = req;
        m_gameService = gameService;
        m_playerMgr   = playerMgr;

        m_playerID = playerID;
        m_pwd      = pwd;

        m_count = 2;
        m_code  = TCCamp::PB_RESULT_CODE::SUCCESS;
        return true;
    }

    bool UnInit() {
        m_reqEvent    = nullptr;
        m_gameService = nullptr;
        m_playerMgr   = nullptr;
        return true;
    }

    // 通过 IDBModule 继承
    // 子线程
    virtual bool DBExec(U::DB::DBSqlite* pSqlite) override {
        bool result = false;
        int  index  = 0;

        std::string selectPwd;
        bool bFind = false;

        U::DB::SQLITE3_RESULT_CODE nRet;

        char  sql[1024] = { 0 };
        char* pError    = NULL;

        if (nullptr == m_reqEvent->TCP_SESSION) {
            goto Exit;
        }

        m_reqEvent->CUR_STATUS = ReqEvent::TRACE_STATUS::QUERYING;
        //查询玩家表
        sprintf(sql
            , "SELECT PlayerID, Password FROM Players WHERE PlayerID = '%s';"
            , m_playerID.c_str());
        
        nRet = pSqlite->Exec(sql, &pError
            , [this, &selectPwd, &bFind](int column_size, char* column_value[],
                char* column_name[]) {
            bFind = true;
            selectPwd = column_value[1];
        });

        if (NULL != pError) {
            NET_UV_LOG_ERROR("%s\n", pError);
            pError = NULL;
            m_code = TCCamp::PB_RESULT_CODE::SERVER_ERROR;
            goto Exit;
        }

        if (!bFind) {
            m_code = TCCamp::PB_RESULT_CODE::LOGIN_ACCOUNT_NOT_EXIST;
            goto Exit;
        }

        //判断密码是否正确
        if (m_pwd != selectPwd) {
            m_code = TCCamp::PB_RESULT_CODE::LOGIN_PASSWORD_ERROR;
            goto Exit;
        }

        //正确则初始化角色列表
        //查询角色表
        sprintf(sql
            , "SELECT RoleID FROM Roles WHERE PlayerID = '%s';"
            , m_playerID.c_str());

        nRet = pSqlite->Exec(sql, &pError
            , [this](int column_size, char* column_value[],
                char* column_name[]) {
            m_roleArrs.push_back(atoi(column_value[0]));
        });

        if (NULL != pError) {
            NET_UV_LOG_ERROR("%s\n", pError);
            pError = NULL;
            m_code = TCCamp::PB_RESULT_CODE::SERVER_ERROR;
            goto Exit;
        }
        result = true;
    Exit:
        m_reqEvent->CUR_STATUS = ReqEvent::TRACE_STATUS::FINISH;
        //投递至GameService主线程
        m_gameService->PostMsg(POST_COM_KEY::PCK_DB_OPERATION, this);
        return result;
    }

    // 主线程
    virtual bool OnDBExec() override {
        //将player添加至playerMgr中
        //清理cache缓冲队列
        //回应客户端
        if (m_reqEvent->TCP_SESSION) {
            TCCamp::AccountLoginRsp rsp;
            rsp.set_result(m_code);
            if (m_code == TCCamp::PB_RESULT_CODE::SUCCESS) {
                Player* player = (Player*)MALLOC(sizeof(Player));
                new(player) Player();
                assert(nullptr != player);
                player->Init(m_playerID, m_pwd, m_reqEvent->TCP_SESSION);

                for (auto& roleID : m_roleArrs) {
                    player->AddRole(roleID);
                }

                m_playerMgr->AddPlayer(player);
            }

            LOG_INFO << "playerID:" << m_playerID << " login...";
            m_reqEvent->TCP_SESSION->Send(TCCamp::SERVER_CMD::SERVER_LOGIN_RSP,
                &rsp);
        }
        m_playerMgr->RemoveReqEvent(m_reqEvent);
        return true;
    }

    virtual bool Release()  override {
        /*--m_count;
        if (m_count == 0) {
            UnInit();
            delete this;
        }*/

        UnInit();
        delete this;
        return true;
    }

private:
    ReqEvent*    m_reqEvent;
    GameService* m_gameService;
    PlayerMgr*   m_playerMgr;

    std::string  m_playerID;
    std::string  m_pwd;

    std::vector<int64_t> m_roleArrs;

    int  m_count;
    TCCamp::PB_RESULT_CODE m_code;
};

int main() {
    DBLogin*  dbLogin = nullptr;
    
    dbLogin = new DBLogin();
    dbLogin->Init(req, m_gameService, this, 
        loginReq->accountid(), 
        loginReq->password());

    //抛至数据库代理
    DBProxy::Ins().PostHandlerEvent(dbLogin);
    return 0;
}