5 - C++ Development of "Fight the Landlord" - Backend Network Services & Business Distribution

后台程序内部除了定时器其它的各个服务工作都是由网络请求被动触发的,这篇我们来分析网络服务和数据包解析与分发调用自定义服务的关键流程

server.cpp:TCP网络服务

start函数里面创建TCP服务并设置回调与启动,主要说下on_recv回调,接收到完整的数据后获取 HandleCenter(处理控制器)单例指针调用push推送

#include "server.h"
#include "core/handlecenter.h"
#include "game/playermanager.h"
#include "HPSocket/HPSocket.h"
#include "yutil/print.h"
// 取玩家ID
uint64 toPlayerID(ylib::network::tcp::server* server, uint64 connid)
{
    PVOID extra = 0;
    server->getHP()->GetConnectionExtra(connid, &extra);
    return (uint64)extra;
}
bool Server::start()
{
    stop();
    m_server = new network::tcp::server();

    m_server->on_accept([&](ylib::network::tcp::server* server, uint64 connid) {
    });
    m_server->on_close([&](ylib::network::tcp::server* server, uint64 connid) {
        
        uint64 player_id = toPlayerID(server,connid);
        LOG_INFO("[server] on_close, connid:"+std::to_string(connid)+"\tplayer_id:"+std::to_string(player_id));
         if (player_id != 0)
        {
            // 加入处理队列
            ylib::json queueData;
            queueData["player_id"] = player_id;
            HandleCenter::getInstance()->pushInternal(c2s::InternalAction::REMOVE_PLAYER, queueData);
        }
    });
    m_server->on_recv([&](ylib::network::tcp::server* server, uint64 connid, const char* data, uint32 len) {
        uint64 player_id = toPlayerID(server, connid);

        ylib::println("[recv]:" + std::to_string(connid) + "\t" + std::string(data, len),(ylib::ConsoleTextColor)3);

        HandleCenter::getInstance()->push(connid,player_id,data, len);
    });
    if (!m_server->start({ "0.0.0.0",15888 }, ylib::receive_model::PACK))
    {
        m_lastErrorDesc = m_server->last_error();
        return false;
    }
    return true;
}

void Server::stop()
{
    if (m_server != nullptr)
    {
        m_server->close();
        delete m_server;
        m_server = nullptr;
    }
}

ITcpServer* Server::hp()
{
    return m_server->getHP();
}


void Server::send(uint64 fd, const ylib::json& data)
{
    ylib::println("[send]:" + std::to_string(fd) + "\t" + data.to_string(),ylib::ConsoleTextColor::GREEN);


    std::string str_data = data.to_string();
    m_server->send(fd, str_data);
}

HandleCenter.cpp:处理控制器

1、结合上面说的我们首先查看push函数,push函数判断data指针是否为json格式并解析,随后将参数打包到package结构体加入到队列当中

2、start函数启动线程,该线程会调用run函数,

3、run函数内部循环处理队列:①数据包队列 ②定时器队列

4、数据包队列 handlePackageQueue() 通过初步解析json,解析出type、action和data等并在Request初始化构造时传入,随后调用ServiceManager:exec执行

#include "handlecenter.h"
#include "core/servicemanager.h"
#include "yutil/system.h"
#include "yutil/time.h"
#include "network/request.h"

void HandleCenter::start()
{
    ::ithread::start();
}

void HandleCenter::stop()
{
    ::ithread::stop();
    ::ithread::wait();
}

void HandleCenter::push(uint64 fd, uint64 player_id, const char* data, uint32 length)
{
    // 判断是否为JSON并加入数据包处理队列
    if (data[0] != '{' || data[length - 1] != '}')
        return;
    Package package;
    package.fd = fd;
    package.player_id = player_id;
    package.data = ylib::json::from(data);
    m_queue.push(package);
}
void HandleCenter::pushInternal(c2s::InternalAction action, const ylib::json& data)
{
    ylib::json jsonData;
    jsonData["data"] = data;
    jsonData["type"] = c2s::INTERNAL;
    jsonData["action"] = action;

    Package package;
    package.data = jsonData;
    m_queue.push(package);
}

uint32 HandleCenter::push_timer(bool once, uint32 interval_msec, std::function<void()> callback)
{
    Timer timer;
    timer.once = once;
    timer.interval_msec = interval_msec;
    timer.callback = callback;
    timer.call_msec = time::now_msec() + interval_msec;
    timer.id = m_timer_idx.make();
    m_timer_lock.lock();
    m_timer.push_back(timer);
    m_timer_lock.unlock();
    return timer.id;
}

void HandleCenter::remove_timer(uint32 id)
{
    std::lock_guard<std::mutex> guard(m_timer_lock);
    for_iter(iter, m_timer)
    {
        if (iter->id == id)
        {
            m_timer.erase(iter);
            break;
        }
    }
}
HandleCenter::HandleCenter()
{

}

void HandleCenter::handleTimerList()
{
    std::lock_guard<std::mutex> guard(m_timer_lock);

    auto now_msec = time::now_msec();
    for (auto iter = m_timer.begin(); iter != m_timer.end();)
    {
        if (iter->call_msec <= now_msec)
        {
            iter->callback();
            if (iter->once)
            {
                iter = m_timer.erase(iter);
                continue;
            }
            else
            {
                iter->call_msec = now_msec + iter->interval_msec;
            }
        }
        iter++;
    }
}

bool HandleCenter::run()
{
    // 处理数据包
    while(m_queue.size() != 0)
    {
        handlePackageQueue();
    }
    // 处理定时器
    handleTimerList();
    system::sleep_msec(1);
    return true;
}

void HandleCenter::handlePackageQueue()
{
    
    Package package;
    while (m_queue.pop(package)) {
        // 解析数据包
        int type = package.data["type"].to<int>();
        int action = package.data["action"].to<int>();
        int index = package.data["index"].to<int>();
        // 构造请求
        Request request(package.player_id,package.fd,index,package.data["data"]);
        // 执行
        ServiceManager::getInstance()->exec(type, action, request);
    }
}

ServiceManager.cpp:自定义服务控制器

1、exec函数中判断是否有符合的type和action并判断回调是否可用,如果service在构造时声明需要权限则会调用request.player(),该方法通过PlayerManager获取当前连接的玩家缓存

2、鉴权失败则会返回默认数据提示失败

3、自定义service服务在处理时若抛出异常,也会由servicemanager返回默认数据,DEBUG模式下会返回错误描述信息

<pre class="prism-highlight prism-language-cpp">#include "servicemanager.h"
#include "yutil/print.h"
std::shared_ptr<IService> ServiceManager::getService(const std::string& name)
{
    return m_services[name];
}
void ServiceManager::removeService(const std::string& name)
{
    m_services.erase(name);
}
void ServiceManager::exec(int type, int action, Request& request)
{
    if (m_cmds[type][action].callback)
    {
        // 是否需要session鉴权
        if (m_services[m_cmds[type][action].m_name]->haveLogined())
        {
            if (request.player() == nullptr) {
                request.repFailed("权限过期,请重新登录.");
                return;
            }
        }

        try
        {
            // 执行service
            m_cmds[type][action].callback(request);
        }
        catch (const std::exception& e)
        {
#ifdef _DEBUG
            request.repFailed(e.what());
#else
            request.repFailed("server processing exception, please check logs");
#endif
            LOG_ERROR(e.what());
        }
        
    }
    else
    {
        std::string error_msg = "No service found for handling the request with type=" + std::to_string(type) + ", action=" + std::to_string(action) + ".";
        LOG_ERROR(error_msg);
        request.repFailed(error_msg);
    }
}
bool ServiceManager::registService(IService* service)
{
    auto iter = m_services.find(service->name());
    if (iter == m_services.end())
    {
        m_services.emplace(service->name(), service);
        return true;
    }
    m_lastErrorDesc = "A service with the name = " + service->name() + " already exists.";
    return false;
}
void ServiceManager::registAction(int action, REQUEST_CALLBACK callback,IService* service)
{
    auto as = m_cmds.find(service->type<int>());
    if (as == m_cmds.end())
    {
        std::map<int, ActionCallback> cmd;
        m_cmds[service->type<int>()] = cmd;
    }
    ActionCallback acb;
    acb.m_name = service->name();
    acb.callback = callback;
    m_cmds[service->type<int>()][action] = acb;
}

此时运作流程这里除定时器其它地方都有说明了,定时器将在Room游戏房间类中使用请参阅后面的文章

Title of this article:<5 - C++ Development of "Fight the Landlord" - Backend Network Services & Business Distribution>Author:minimini
Original link:https://www.xxmjw.com/post/17.html
Unless otherwise specified, all content is original. Please indicate when reprinting.

Related

minimini

minimini