开源网络库ZLToolkit源码剖析

ZLToolKit开源网络库源码剖析

说真的,我真受不了稀土掘金这么久了还没有支持字体颜色,居然连下划线的功能都没有,后续不在这个上面更新文章,太难用了。 后续考虑去CSDN去更新文章了,大家可以去我的主页看最新文章。名字和头像和当前这个一样

CSDN我的个人主页

大家觉得掘金排版不好看,可以去我的语雀去看。 语雀链接,默认半年到期看不了评论联系我

EventPoller [事件循环] -> src/NetWork/EventPoller.h

这个项目最主要的就是这个,搞懂这个原理,其他都好说了。后续画个关系图

EventPollerPool 事件循环池

描述

Why? 创建多个线程(默认是CPU数量),每个线程又创建出EventPoller,每个EventPoller底层又是用epoll处理事件的。猜测作者的意思将一个socket的事件注册多个epoll上,然后每个epoll又是不同的线程再监听,有任何一个线程监听就可以进行处理了,提高处理效率。

但是,数据包到达本机,底层是内核线程来处理调用协议栈,根据目标端口和目标ip地址找到对应的socket结构体,根据已打开的文件描述符列表找到对应的epoll红黑树节点,唤醒之前调用epoll_wait等待的线程。所以这一套流程,创建多个线程监听完全没必要,还浪费内存,还会造成一些竞争问题。

这是自己的见解,我觉得这是个问题

源码剖析

1. 默认构造

cpp 复制代码
static size_t s_pool_size = 0;    
static bool s_enable_cpu_affinity = true;

void EventPoller::runLoop(bool blocked, bool ref_self)
{
    if (blocked)
        // 代码省略...
    else 
    {
        // 执行到这里,一般是主线程创建EventPoller

        // 下面的addPoller会执行到这里
        _loop_thread = new thread(&EventPoller::runLoop, this, true, ref_self);
        _sem_run_started.wait();    // 等待被创建处理来的线程唤醒
    }
}


size_t TaskExecutorGetterImpl::addPoller(const string& name, size_t size, int priority,
    bool register_thread, bool enable_cpu_affinity)
{
    // 获取CPU核心数
    auto cpus = thread::hardware_concurrency();
    size = size > 0 ? size : cpus;    // 如果要创建的EventPoller数量大于0,则设置为参数size,否则就是cpu线程个数

    // 创建具体数量的线程
    for (size_t i = 0; i < size; i++)
    {
        auto full_name = name + " " + to_string(i);    // 设置线程的名称
        auto cpu_index = i % cpus;    

        // 创建EventPoller对象
        EventPoller::Ptr poller(new EventPoller(full_name));
        // 调用runLoop,这里会创建线程,看上面runLoop函数代码,参数传递的是  { false, register_thread }
        poller->runLoop(false, register_thread);

        // 异步由EventPoller执行该任务
        poller->async([cpu_index, full_name, priority, enable_cpu_affinity]() {
            // 设置该线程优先级
            ThreadPool::setPriority((ThreadPool::Priority)priority);
            setThreadName(full_name.data());    // 设置线程名称
            if (enable_cpu_affinity) 
                setThreadAffinity(cpu_index);    // 设置线程亲和性
        });
        // 将该线程保存起来
        _threads.emplace_back(std::move(poller));
    }
}

// 构造函数
EventPollerPool::EventPollerPool()
{
    // 创建多个的EventPoller
    auto size = addPoller("event poller", s_pool_size, ThreadPool::PRIORITY_HIGHEST,
        true, s_enable_cpu_affinity);
}

2. 从池中获取一个EventPoller对象

cpp 复制代码
static thread_local std::weak_ptr<EventPoller> s_current_poller;    // thread_local修饰的变量为每个线程独有的

// static
EventPoller::Ptr EventPoller::getCurrentPoller() 
{
    return s_current_poller.lock();
}


// prefer_current_thread: 是否优选选择当前线程
EventPoller::Ptr EventPollerPool::getPoller(bool prefer_current_thread) 
{
    auto poller = EventPoller::getCurrentPoller(); // 获取属于当前线程的EventPoller对象

    // 判断是否优先选择当前线程,如果是则返回
    if (prefer_current_thread && _prefer_current_thread && poller) 
        return poller;
    // 如果不是,则选择一个负载最小的线程
    return static_pointer_cast<EventPoller>(getExecutor());
}
cpp 复制代码
EventPoller::Ptr EventPollerPool::getFirstPoller() 
{
    // 从创建的线程里面选择第一个并返回
    return static_pointer_cast<EventPoller>(_threads.front()); 
}

事件循环

描述

Why? 底层epoll 监听事件。每个EventPoller有自己的线程。核心函数是 runLoop() ,该函数调用epoll_wait然后执行该事件所绑定的回调函数。

作者使用EventPoller的想法是: 创建出多个线程,每个线程又是独立的EventPoller。每个线程去监听事件,调用该事件所对应的回调函数。

源码剖析

1. 默认构造

cpp 复制代码
EventPoller::EventPoller(std::string name)
{
    _event_fd = create_event();    // 创建一个epoll
    if (_event_fd == -1)
        throw runtime_error(StrPrinter << "Create event fd failed: " << get_uv_errmsg());
    SockUtil::setCloExec(_event_fd);
    _name = std::move(name);
    _logger = Logger::Instance().shared_from_this();
    addEventPipe();    // epoll去监听当前管道的读、写事件
}

上面代码所做的事情:

创建epoll

调用addEventPipe(),用epoll去监听管道的读、写事件。主要目的是为了事件通知,比如有其他线程提交了任务,则会唤醒主线程去执行这个任务

2. addEventPipe() 监听管道的读、写事件

cpp 复制代码
void EventPoller::addEventPipe()
{
    SockUtil::setNoBlocked(_pipe.readFD());
    SockUtil::setNoBlocked(_pipe.writeFD());

    // 监听管道的读事件
    if (addEvent(_pipe.readFD(), EventPoller::Event_Read, [this](int event) { onPipeEvent(); }) == -1 )
        throw std::runtime_error("Add pipe fd to poller failed");
}

3. addEvent添加监听事件

cpp 复制代码
void EventPoller::addEventPipe()
{
    SockUtil::setNoBlocked(_pipe.readFD());
    SockUtil::setNoBlocked(_pipe.writeFD());

    // 监听管道的读事件
    if (addEvent(_pipe.readFD(), EventPoller::Event_Read, [this](int event) { onPipeEvent(); }) == -1 )
        throw std::runtime_error("Add pipe fd to poller failed");
}

4. delEvent 删除监听事件

cpp 复制代码
int EventPoller::delEvent(int fd, PollCompleteCB cb)
{
    TimeTicker();
    if (!cb) 
        cb = [](bool success) {};

    // 如果是当前线程
    if (isCurrentThread())
    {
        int ret = -1;
        // 首先从map中删除
        if (_event_map.erase(fd)) 
        {
            // 保存到缓存里
            _event_cache_expired.emplace(fd);
            // 从epoll中删除
            ret = epoll_ctl(_event_fd, EPOLL_CTL_DEL, fd, nullptr);
        }
        cb(ret != -1);    // 调用回调函数
        return ret;
    }

    // 如果是非当前线程,则异步交给删除事件
    async([this, fd, cb]() mutable {
        delEvent(fd, std::move(cb));
    });
    return 0;
}

5. modifyEvent 修改事件

cpp 复制代码
int EventPoller::modify(int fd, int event, PollCompleteCB cb)
{
    TimeTicker();
    if (!cb)
        cb = [](bool success) {};

    // 如果是当前线程
    if (isCurrentThread())
    {
        struct epoll_event ev = {0};
        ev.events = toEpoll(event);
        ev.data.fd = fd;

        // 修改epoll的事件
        auto ret = epoll_ctl(_event_fd, EPOLL_CTL_MOD, fd, &ev);
        cb(ret != -1);    // 调用回调函数
        return ret;
    }

    // 如果是非当前线程,则异步添加,然后由主线程去执行
    async([this, fd, event, cb]() mutable {
        modifyEvent(fd, event, std::move(cb));
    });
    return 0;
}

6. async异步提交任务

cpp 复制代码
Task::Ptr EventPoller::async(TaskIn task, bool may_sync)
{
    return async_l(std::move(task), may_sync, false);
}

Task::Ptr EventPoller::async_first(TaskIn task, bool may_sync)
{
    return async_l(std::move(task), may_sync, true);
}

Task::Ptr EventPoller::async_l(TaskIn task, bool may_sync, bool first)
{
    TimeTicker();
    // 如果需要同步并且也是当前线程
    if (may_sync && isCurrentThread())
    {
        task();    // 则直接执行任务
        return nullptr;
    }

    auto ret = std::make_shared<Task>(std::move(task));
    {
        lock_guard<mutex> lock(_mtx_task);
        if (first)    // 如果选择插入到队列头
            _list_task.emplce_front(ret);
        else
            _list_task.emplace_back(ret);
    }

    // 生成一个事件,唤醒主线程来执行这个任务
    _pipe.wrte("", 1);
    return ret;
}

7. isCurrentThread 判断是否是当前线程

cpp 复制代码
class EventPoller
{
private:
    // 执行事件循环的线程
    std::thread* _loop_thread = nullptr;
};

bool EventPoller::isCurrentThread()
{
    // 每个EventPoller有独立的线程
    return !_loop_thread || _loop_thread->get_id() 
        == this_thread::get_id();
}

8. doDelayTask 延时执行某个任务

cpp 复制代码
/*
    delay_ms: 延时的时间
    task: 要延时执行的任务
*/
EventPoller::DelayTask::Ptr 
    EventPoller::doDelayTask(uint64_t delay_ms, function<uint64_t()> task)
{
    DelayTask::Ptr ret = std::make_shared<DelayTask>(std::move(task));

    // 设置延时执行任务的那个时间
    auto time_line = getCurrentMillisecond() + delay_ms;

    // 将该任务以优先异步的执行方式提交
    async_first([time_line, ret, this]() {
        _delay_task_map.emplace(time_line, ret);
    });

    return ret;
}

9. getSharedBuffer 获取当前线程下所有的socket共享的读缓存

cpp 复制代码
class EventPoller
{
private:
    // _shared_buffer[0] 存储的是tcp数据
    // _shared_buffer[1] 存储的是udp数据
    std::weak_ptr<SocketRecvBuffer> _shared_buffer[2];
};

SocketRecvBuffer::Ptr getSharedBuffer(bool is_udp)
{
    // 获取对应的缓存对象
    auto ret = _shared_buffer[is_udp].lock();
    if (!ret)    // 如果还未创建
    {
        // 创建这个对象
        ret = SocketRecvBuffer::create(is_udp);
        // 存储起来
        _shared_buffer[is_udp] = ret;
    }

    return ret;
}

10. runLoop 执行事件循环的函数,最主要的

cpp 复制代码
// 每个线程有属于自己的这个变量
static thread_local std::weak_ptr<EventPoller> s_current_poller;

/*
    blocked: 是否设置为调用该函数的线程执行轮询,如果是,存储到s_current_poller
    ref_self: 是记录本对象到thread local变量
*/
void EventPoller::runLoop(bool blocked, bool ref_self)
{
    if (blocked)
    {
        if (ref_self)
            s_current_poller = shared_from_this();

        _sem_run_started.post();    // 唤醒创建当前线程的线程
        _exit_flag = false;            
        uint64_t minDelay;
        struct epoll_event events[EPOLL_SIZE];
        // 执行轮询循环
        while (!_exit_flag)
        {
            minDelay = getMinDelay();
            startSleep();    // 统计当前线程负载情况
            int ret = epoll_wait(_event_fd, events, EPOLL_SIZE, minDelay ? minDelay : -1);
            sleepWakeUp();    // 统计当前线程负载情况,也就当前线程等待事件发生所用的时间
            if (ret <= 0)
                continue;    // 没有事件发生,或有错误,重新来

            // 这个是存储之前被删除过的事件
            _event_cache_expired.clear();
            // 循环处理所发生的事件
            for (int i = 0; i < ret; i++)
            {
                struct epoll_event& ev = events[i];
                int fd = ev.data.fd;
                // 这块有问题,上面clear()后,调用count()只会返回0,没什么意义
                if (_event_cache_expired.count(fd))
                    continue;

                // 根据fd从存储所有事件集合中去查找
                auto it = _event_map.find(fd);
                if (it == _event_map.end())    // 如果没有找到,则说明这个fd以前被删除过
                {
                    // 所以也从epoll中将其删除
                    epoll_ctl(_event_fd, EPOLL_CTL_DEL, fd, nullptr);
                    continue;
                }
                // 查找到,则获取该fd所绑定的回调
                auto cb = it->second;
                try {
                    // 执行该fd所对应的回调函数
                    (*cb)(toPoller(ev.events));
                } catch (std::exception& ex) 
                {
                    // 日志输出
                }
            }
        }
    }
    else 
    {
        // 执行到这里,一般是主线程创建EventPoller

        // 创建线程,线程执行函数是runLoop
        _loop_thread = new thread(&EventPoller::runLoop, this, true, ref_self);
        _sem_run_started.wait();    // 等待被创建处理来的线程唤醒
    }
}

11. onPipeEvent 当收到管道的读事件所执行的函数

cpp 复制代码
inline void EventPoller::onPipeEvent(bool flush)
{
    char buf[1024];
    int err = 0;
    if (!flush)
    {
        while (true) 
        {
            // 从管道一直读取数据,直到读不出来
            // 这里将管道设置的是非阻塞,所以如果没有数据则会返回
            if ((err = _pipe.read(buf, sizeof(buf))) > 0)
                continue;

            // 非阻塞的管道如果返回0则代表被关闭了
            // 非阻塞的管道只会返回大于0或者-1然后设置errno为具体的错误,比如EOF、EAGAIN
            if (err == 0 || get_uv_error(true) != UV_EAGIN)
            {
                // 从epoll删除该管道的读事件
                delEvent(_pipe.readFD());
                _pipe.reOpen();    // 将管道重新打开
                addEventPipe();    // 然后重新注册管道的事件
            }
            break;
        }
    }

    // 这里将异步执行的任务全部swap出来
    decltype(_list_task) _list_swap;
    {
        lock_guard<mutex> lock(_mtx_task);
        _list_swap.swap(_list_task);
    }

    // 循环遍历,并一个一个处理需要异步执行的任务
    _list_swap.for_each([&](const Task::Ptr& task) {
        try {
            (*task)();    // 调用回调函数
        } catch(ExitException& ) {
            _exit_flag = true;
        } catch (std::exception& ex) {
            // 日志
        }
    }); 
}

剩下一些函数可以自己去看懂了,下面画一个流程图,清晰的解释一下这个类的工作原理


TcpServer [提供Tcp服务] -> src/Network/TcpServer.h

分析这个之前,我们先分析跟TcpServer相关的两个类 Session(跟连接会话相关)和Socket(跟套接字操作相关,比如读数据)

Session [管理连接的会话] -> src/Network/Session.h

描述

why? 每次连接成功都会生成一个"会话",提供一些功能,比如说向对端发送数据、接收到数据该如何去做、以及发生错误时候该怎么。那么就衍生出这个Session类

通过上面这行描述,它肯定是跟Socket必不可分,所以下面这关系图也表明了。

我们想要处理和对端的接收、发送数据逻辑,那么我们就需要继承自Session类,然后实现它提供的几个 纯虚函数: onRecv() 、onError()、onFlush()、onManager()

下面是Session类的继承体系,建议先过一遍,脑子里有个印象。可以通过类名大概推断出这几个类他们的含义是什么,下面逐个讲解:

SocketHelper [ 封装Socket类和EventPoller绑定 ] -> src/NetWork/Socket.h

该类最主要的是提供几个纯虚函数接口,用于接收发送消息的处理。用户只需要实现这些函数,由内部最终调用这些函数,而不需要关心该什么时候发送、什么时候接收,只需要专注于自己的处理逻辑即可。 还有和EventPoller(单独的线程)进行绑定,可以提交任务交给 单独的线程EventPoller去执行。

SocketSender、SockInfo [用于发送数据、获取关于网络信息] -> src/NetWork/Socket.h

SocketSender重载了一些 "输入运算符"以及提供了send发送数据接口。 SockInfo提供了一些获取网络信息的接口,比如获取本地ip、本地端口、目标ip、目标端口。

终于到了Session类,看上图,它提供两个函数 attachServer : TcpServer创建Session后会把它自身的信息传递进来,如果用户想做一些处理,可以重写这个函数,加上自己的处理逻辑 getIdentifier: 每个Session都有唯一的标识用来区分,所以这个函数就是用来获取标识

Session类讲解到这里,让我们分析TcpServer类

TcpServer类的UML图如下:

下面详细描述一下这个类的工作原理,看不懂描述后面看执行流程图,然后回来再看描述就懂了
这里只专注讲TcpServer的工作原理,想彻底搞明白它到底是怎么做的,肯定会牵扯更多其他的类,所以这个时候不要心急一次性搞懂,先慢慢差不多理解一个类的原理,再去搞懂其他的类原理,就会把关系网连上。

原理:

看上面的UML图,得知这个TcpServer也是深深和EventPoller绑定的,通过前面分析,我们知道一个EventPoller就是一个线程。

所以监听客户端的连接和接收、发送数据也都是EventPoller去做的,然后我们提供onRecv以及onError等回调函数,内部会调用。这样就将这些关于网络处理的细节对用于隐藏了起来

该类的构造函数判断你是从EventPollerPool中自动选择一个EventPoller,还是你主动指定绑定到某个EventPoller。

当我们调用start函数,设置创建session的函数。然后调用启动服务的核心函数 start_l()。在该函数,调用 setupEvent() ,该函数创建一个Socket类(封装了很多网络处理的函数,比如listen、bind这些),但这时并没有真正创建套接字。还设置了当接收客户端连接时应该做哪些事情,以及接收客户端连接之前做什么事情。这个很重要,有客户端数据到来,内部才会调用我们继承Session的onRecv() 函数。

为当前TcpServer创建一个套接字,并绑定地址信息、调用listen(),进行监听客户端连接。

如果你从EventPollerPool中获取EventPoller的话,会为每个EventPoller创建一个Server,配置则从当前TcpServer复制,复制出的Server不会创建套接字,而是和当前TcpServer共享一个套接字。复制出来的这些Server就存储成员变量: _cloned_server中。这些复制的Server有个 _parent 成员变量指向当前TcpServer,表明当前才是主Server。

然后将其他的EventPoller也监听当前TcpServer所创建的套接字。至此,启动服务结束

上面简要介绍了TcpServer类的大概原理,也重点指出了有客户端连接该怎么做,以及发送过来的数据怎么调用我们的函数的(关于如何调用我们的函数,这就是其他类的处理,后面分析)。下面开始源码剖析,慢慢看完代码再加上最后的执行流程图就完全明白了,脑子里先有个大概理解。

源码剖析:

  1. 构造函数
cpp 复制代码
// 构造函数接收一个EventPoller参数,可以指定到某个EventPoller
TcpServer::TcpServer(const EventPoller::Ptr &poller) : Server(poller) 
{
    // 如果没指定poller,那么将_multi_poller标志位设置为true,表示从EventPoller池中获取一个
    _multi_poller = !poller;
    setOnCreateSocket(nullptr);	// 设置创建socket的回调函数,没指定则调用Socket::createSocket函数
}
  1. start启动服务
cpp 复制代码
/*
    port: 要监听的端口号
    host: 绑定的ip地址
    backlog: 监听队列大小
    callback: 当接收到客户端连接时,会创建一个Session,那么创建Session的回调函数就会调用这个回调函数

    下面模板类型SeesionType其实就是我们继承Seesion这个类的派生类类型
*/
template <typename SeesionType>
void start(uint16_t port, const std::string& host = "::", uint32_t backlog = 1024, 
    const std::function<void(std::shared_ptr<SessionType>&)>& callback = nullptr)
{
    // 获取继承自Seesion的类的类型
    // 比如 class MySession : public seesion
    // 那么cls_name就是 MySession
    static std::string cls_name = toolkit::demangle(typeid(SessionType).name());

    // 设置创建Session的回调函数
    _seesion_alloc = [cb](const TcpServer::Ptr& server, const Socket::Ptr& sock) {
        // 创建我们传入的继承Seesion的派生类
        auto session = std::shared_ptr<SessionType>(new SessionType(sock), [](SessionType* ptr) {
            delete ptr;
        });
        
        // 如果设置了回调,则在这里执行
        if (cb) {
            cb(seesion);
        }
        // 创建一个Socket类
        session->setOnCreateSocket(sever->_on_create_socket);
        
        // 将Seesion、TcpServer、类名分装成SeesionHelper返回
        return std::make_shared<SessionHelper>(server, std::move(session), cls_name);
    };

    // 真正启动服务的代码在这个函数里
    start_l(port, host, backlog);
}
  1. start_l 真正启动服务的函数
cpp 复制代码
void TcpServer::start_l(uint16_t port, const std::string& host, uint32_t backlog) 
{
    // 创建socket、设置客户端建立连接执行的回调函数
    setupEvent();

    // 获取当前TcpServer的引用
    weak_ptr<TcpServer> weak_self = std::static_pointer_cast<TcpServer>(shared_from_this());
    // 创建一个定时器,定时的执行onManagerSeesion,管理seesion
    _timer = std::make_shared<Timer>(2.0f, [weak_self]() -> bool {
        auto strong_self = weak_self.lock();
        if (!strong_self) {
            return false;
        }
        strong_self->onManagerSession();
        return true;
    }, _poller);

    // 如果是从EventPoller池中获取的EventPoller
    if (_multi_poller)
    {
        // 遍历EventPollerPool的每一个Poller
        EventPollerPool::Instance().for_each([&](const EventPoller::Ptr poller =
            static_pointer_cast<EventPoller>(executor);
            if (poller == _poller)
                return;
        
            // 设置一个拷贝Server,_cloned_server是unordered_map类型
            auto& serverRef = _cloned_server[poller.get()];
            if (!serverRef) {
                // 为这个Poller创建一个Server
                serverRef = onCreateServer(poller);
            }
            if (serverRef) {
                // 拷贝当前主TcpServer的一些配置信息
                serverRef->cloneFrom(*this);
            }
        });
    }

    // 为当前Server创建套接字,并进行监听
    if (!_socket->listen(port, host.c_str(), backlog)) 
        throw std::runtime_error(err);

    // 如果是从EventPoller池中获取Poller的,那么就有复制的Server
    // 这里将其他的EventPoller也监听当前主TcpServer套接字
    for (auto& pr = _cloned_server)
        pr.second->_socket->cloneSocket(*_socket);
}
  1. setupEvent 设置连接时执行的回调
cpp 复制代码
void TcpServer::setupEvent()
{
    // 创建socket,设置客户端连接时的回调函数
    _socket = createSocket(_poller);
    // 获取当前类的引用
    weak_ptr<TcpServer> weak_self = std::static_pointer_cast<TcpServer>(shared_from_this());
    // 设置accept连接 之前 调用的回调
    _socket->setOnBeforeAccept([weak_self](const EventPoller::Ptr& poller) -> Socket::Ptr {
        // 获取当前对象的强引用
        if (auto strong_self : weak_self.lock()) {
            // 为此连接创建一个socket对象,这是下面这个函数所作的事情
            return strong_self->onBeforeAcceptConnection(poller);
        }
        return nullptr;
    });

    // 设置accept连接的回调函数
    _socket->setOnAccept([weak_self](Socket::Ptr& sock, shared_ptr<void>& complete) {
        // 获取当前对象的强引用
        if (auto strong_self = weak_self.lock()) {
            // 获取当前socket所使用的poller
            auto ptr = sock->getPoller().get();
            // 根据poller获取对应的server
            auto server = strong_self->getSever(ptr);
            
            // 异步提交一个任务给poller
            ptr->async([server, sock, complete]() {
                // 这个函数设置接收数据的回调、发生错误的回调
                server->onAcceptConnection(sock);
            });
        }
    });
}
  1. onAcceptConnection 客户端连接时所执行的函数,用来设置一个回调
cpp 复制代码
Seesion::Ptr TcpServer::onAcceptConnection(const Socket::Ptr& sock) 
{
    // 获取当前对象的引用
    weak_ptr<TcpServer> weak_self = std::static_pointer_cast<TcpServer>(shared_from_this());
    
    // 创建一个SeesionHelper对象,返回的是一个智能指针
    auto helper = _session_alloc(std::static_pointer_cast<TcpServer>(shared_from_this()), this);
    auto session = helper->seesion();	// 通过SeesionHelper获取Seesion对象
    seesion->attachServer(*this);	// 将当前Server的配置传递给seesion

    // 将封装Seesion的SeesionHelper保存起来,返回一个值表示是否插入成功
    auto success = _seesion_map.emplace(helper.get(), helper).second;
    weak_ptr<Session> weak_seesion = seesion;	
    
    // 设置读取数据的回调
    sock->setOnRead([weak_seesion](const Buffer::Ptr& buf, struct sockaddr*, int) {
        auto strong_seesion = wea_seesion.lock();	// 获取seesion的强引用
        if (!strong_seesion) 
            return;
        try {
            strong_seesion->onRead(buf);	// 调用seesion的回调函数 onRead
        } catch (SockException& ex) {
            strong_seesion->shutdown(ex);
        } catch (exception& ex) {
            strong_seesion->shutdown(SockException(Err_shutdown, ex.what()));
        }
    });

    // helper是一个智能指针,调用get() 返回指针
    SeesionHelper* ptr = helper.get();
    auto cls = ptr->className();	// 获取类名

    // 设置错误的回调函数
    sock->setOnErr([weak_self, weak_seesion, ptr, cls](const SockException& err) {
        // onceToken是RAII,两个参数是构造和析构执行的
        // 下面没有设置构造时执行的函数,只设置了析构时执行的函数
        onceToken token(nullptr, [&]() {
            auto strong_self = weak_self.lock();
            if (!strong_self) 
                return;
            // 如果当前没有在执行管理seesion
            if (!strong_self->_is_on_mananger) {
                // 这里从session map中删除
                strong_self->_session_map.erase(ptr);
            }
            else 
            {
                // 异步提交任务
                strong_self->_poller->async([weak_self, ptr]() {
                    auto strong_self = weak_self.lock();
                    if (strong_self) 
                        strong_self->_seesion_map.erase(ptr);
                }, false);
            }
        });

        // 获取Seesion的强引用
        auto strong_session = weak_seesion.lock();
        if (strong_seesion) 
            strong_seesion->onError(err);	// 调用seesion的错误处理函数onError
    });
    return seesion;
}
  1. onManagerSeesion 管理Seesion的回调函数
cpp 复制代码
void TcpServer::onManagerSession() 
{
    // RAII方式,这里设置了构造调用的函数
    onceToken token([&]() {
        _is_on_manager = true; 
    }, [&]() {
        _is_on_manager = false;
    });

    // _seesion_map保存了创建出来的seesion
    for (auto& pr : _seesion_map) 
    {
        try {
            // 调用seesion的管理函数
            pr.second->seesion->onManager();
        } catch (exception& ex) {
            WarnL << ex.what();
        }
    }
}
  1. getServer 根据poller获取TcpServer
cpp 复制代码
TcpServer::Ptr TcpServer::getServer(const EventPoller* poller) const 
{
    // 获取parent Server,这个是自己作为复制出来的Server,才会有parent(也就是主TcpServer)
    auto parent = _parent.lock();
    // 如果没有父 server,那么当前就是主Server。否则从父Server的_cloned_server(复制出来的Server)中获取
    auto &ref = parent ? parent->_cloned_server : _cloned_server;
    auto it = ref.find(poller);	// 然后根据poller查找对应的Server
    if (it != ref.end())
        return it->second;	// 找到了则返回

    // 没有找到的话,则返回主TcpServer
    return static_pointer_cast<TcpServer>(parent ? parent : const_cast<TcpServer*>(this)->shared_from_this());
}
  1. cloneFrom 从主Server复制出Server
cpp 复制代码
// 从主Server复制出来的Server,则调用这个函数完成
void TcpServer::cloneFrom(const TcpServer& that) 
{
    if (!that._socket)
        throw std::invalid_argument("xxxx");

    setupEvent();	// 创建Socket对象(不是套接字,内部封装的)。然后设置一些回调
    _main_server = false;	// 不是主服务,因为当前是被复制出来的
    _on_create_socket = that._on_create_socket;	// 创建socket的回调也是复制主服务的
    _seesion_alloc = that._seesion_alloc;	// 同样分配seesion的函数也是从主服务复制出来的
    // 获取当前对象的引用
    weak_ptr<TcpServer> weak_self = std::static_pointer_cast<TcpServer>(shared_from_this());
    
    // 这里创建个定时器,定时执行onManagerSeesion进行管理
    _timer = std::make_shared<Timer>(2.0f, [weak_self]() -> bool {
        auto strong_self = weak_self.lock();
        if (!strong_self) 
            return false;
        strong_self->onManagerSeesion();
        return return;
    }, _poller);
    
    this->mINI::operator=(that);
    // 指向主服务
    _parent = static_pointer_cast<TcpServer>(const_cast<TcpServer&>(that).shared_from_this());
}

程序分析图:

  1. TcpServer的结构图

注意: 这是未指定EventPoller的结构图

  1. TcpServer的执行流程图

上面这个执行流程图只是指明了主TcpServer针对事件发生时,所执行的过程。注意还有复制的TcpServer当他们监听有事件发生后,也和TcpServer执行同样的过程。

上面对于未指定EventPoller做了详细的说明,指定了EventPoller比上面简单很多,这个可以自行分析了。难的都会了,那简单的更不用说了。

相关推荐
caihuayuan525 分钟前
前端面试2
java·大数据·spring boot·后端·课程设计
郭尘帅6661 小时前
SpringBoot学习(上) , SpringBoot项目的创建(IDEA2024版本)
spring boot·后端·学习
野犬寒鸦1 小时前
MySQL索引详解(下)(SQL性能分析,索引使用)
数据库·后端·sql·mysql
.生产的驴6 小时前
SpringBoot 集成滑块验证码AJ-Captcha行为验证码 Redis分布式 接口限流 防爬虫
java·spring boot·redis·分布式·后端·爬虫·tomcat
野犬寒鸦7 小时前
MySQL索引使用规则详解:从设计到优化的完整指南
java·数据库·后端·sql·mysql
思考的橙子7 小时前
Springboot之会话技术
java·spring boot·后端
兆。10 小时前
电子商城后台管理平台-Flask Vue项目开发
前端·vue.js·后端·python·flask
weixin_4373982110 小时前
RabbitMQ深入学习
java·分布式·后端·spring·spring cloud·微服务·rabbitmq
西京刀客15 小时前
Go多服务项目结构优化:为何每个服务单独设置internal目录?
开发语言·后端·golang
李匠202415 小时前
C++GO语言微服务之gorm框架操作MySQL
开发语言·c++·后端·golang