C/C++协程学习笔记丨C/C++实现协程及原理分析视频( 五 )


  1. 创建 socket , 监听在本机的 1024 端口 , 并设置为非阻塞;
  2. 主线程使用函数 readwrite_coroutine 创建多个读写协程 , 调用 co_resume 启动协程运行直到其挂起 。 这里我们忽略掉无关的多进程 fork 的过程;
  3. 主线程继续创建 socket 接收协程 accpet_co , 同样调用 co_resume 启动协程直到其挂起;
  4. 主线程调用函数 co_eventloop 实现事件的监听和协程的循环切换;
函数 readwrite_coroutine 在外层循环中将新创建的读写协程都加入到队列 g_readwrite 中 , 此时这些读写协程都没有具体与某个 socket 连接对应 , 可以将队列 g_readwrite 看成一个 coroutine pool 。 当加入到队列中之后 , 调用函数 co_yield_ct 函数让出 CPU , 此时控制权回到主线程 。
主线程中的函数 co_eventloop 监听网络事件 , 将来自于客户端新进的连接交由协程 accept_co 处理 , 关于 co_eventloop 如何唤醒 accept_co 的细节我们将在后续介绍 。 accept_co 调用函数 accept_routine 接收新连接 , 该函数的流程如下:
  1. 检查队列 g_readwrite 是否有空闲的读写 coroutine , 如果没有 , 调用函数 poll 将该协程加入到 Epoll 管理的定时器队列中 , 也就是 sleep(1000) 的作用;
  2. 调用 co_accept 来接收新连接 , 如果接收连接失败 , 那么调用 co_poll 将服务端的 listen_fd 加入到 Epoll 中来触发下一次连接事件;
  3. 对于成功的连接 , 从 g_readwrite 中取出一个读写协程来负责处理读写;
再次回到函数 readwrite_coroutine 中 , 该函数会调用 co_poll 将新建立的连接的 fd 加入到 Epoll 监听中 , 并将控制流程返回到 main 协程;当有读或者写事件发生时 , Epoll 会唤醒对应的 coroutine, 继续执行 read 函数以及 write 函数 。
上面的过程大致说明了控制流程是如何在不同的协程中切换 , 接下来我们介绍具体的实现细节 , 即如何通过 Epoll 来管理协程 , 以及如何对系统函数进行改造以满足 libco 的调用 。
通过 Epoll 管理和唤醒协程Epoll 监听 FD
上一章节中介绍了协程可以通过函数 co_poll 来将 fd 交由 Epoll 管理 , 待 Epoll 的相应的事件触发时 , 再切换回来执行 read 或者 write 操作 , 从而实现由 Epoll 管理协程的功能 。 co_poll 函数原型如下:
int co_poll(stCoEpoll_t *ctx, struct pollfd fds[],
nfds_t nfds, int timeout_ms)
stCoEpoll_t 是为 libco 定制的 Epoll 相关数据结构 , fds 是 pollfd 结构的文件句柄 , nfds 为 fds 数组的长度 , 最后一个参数表示定时器时间 , 也就是在 timeout 毫秒之后触发处理这些文件句柄 。 这里可以看到 , co_poll 能够同时将多个文件句柄同时加入到 Epoll 管理中 。 我们先看 stCoEpoll_t 结构:
struct stCoEpoll_t
{
int iEpollFd; // Epoll 主 FD
static const int _EPOLL_SIZE = 1024 * 10; // Epoll 可以监听的句柄总数
struct stTimeout_t *pTimeout; // 时间轮定时器
struct stTimeoutItemLink_t *pstTimeoutList; // 已经超时的时间
struct stTimeoutItemLink_t *pstActiveList; // 活跃的事件
co_epoll_res *result; // Epoll 返回的事件结果
};
以 stTimeout_ 开头的数据结构与 libco 的定时器管理有关 , 我们在后面介绍 。 co_epoll_res 是对 Epoll 事件数据结构的封装 , 也就是每次触发 Epoll 事件时的返回结果 , 在 Unix 和 MaxOS 下 , libco 将使用 Kqueue 替代 Epoll , 因此这里也保留了 kevent 数据结构 。
struct co_epoll_res
{
int size;
struct epoll_event *events; // for linux epoll
struct kevent *eventlist; // for Unix or MacOs kqueue


推荐阅读