1. 数据结构: 见下图
struct eloop_data结构体是一个统领全局的数据结构,只有一个实例,即Line 75:
点击(此处)折叠或打开
- static struct eloop_data eloop;
- * @EVENT_TYPE_READ: Socket has data available for reading
- * @EVENT_TYPE_WRITE: Socket has room for new data to be written
- * @EVENT_TYPE_EXCEPTION: An exception has been reported
Timeout事件: 每个struct eloop_timeout都被放在一个双向链表中, 链表头就是eloop_data中的”timeout”项。这些struct eloop_timeout按超时先后排序。
Signal事件:每个struct eloop_signal放在动态数组中
疑问: 为什么Socket和signal事件不使用链表,而是动态的扩大和缩小数组?
2. 基于Select的多Event处理
void eloop_run(void) 是个总函数,我们看它是怎么用select系统调用实现多路复用监听多个事件的。
- while (!eloop.terminate &&
- (!dl_list_empty(&eloop.timeout) || eloop.readers.count > 0 ||
- eloop.writers.count > 0 || eloop.exceptions.count > 0)) {
如果eloop.terminate变为非零值,就会退出循环。这是为了提供一种从外部结束循环的方法。
如果eloop.terminate为零,只要timeout链表或者任一个Socket不为空,都会继续循环。
Select系统调用的原型是:
- int select(int nfds, fd_set *readfds, fd_set *writefds, fd_set *exceptfds, struct timeval *timeout);
(1)找到timeout链表的第一项(因为是按超时先后排序的,所以第一项肯定是最先超时的),计算超时时间距现在还有多久, 并据此设置select的timeout参数。
(2)设置readfd, writefds和exceptfds三个fd_set: 方法是遍历各个eloop_sock_table,把每个sock描述符加入相应的fd_set里面。
(3)调用select。 可能阻塞在此处。
(4)eloop_process_pending_signals(); 处理Signal事件。后面会分析。
(5)判断是否有超时发生,如果是则调用其Handler, 并从timeout链表移除。然后继续下次循环。
(6)如果不是超时事件,则应该是readfds, writefds或者exceptfds事件, fd_set里面会被改变,存放发生事件的描述符。因此分别遍历三个sock_table, 如果其描述符在fd_set里面则调用其Handler.
(7)继续下次循环.
值得一提的是,这里对Signal的处理有点特别。在eloop_register_signal() 函数注册的signal的Handler并不是当Signal发生时就会自动执行的。当Signal发生时只会对该struct eloop_signal的 signaled变量加1,以表明Signal已收到并处于Pendning状态。在select()超时或者有Socket事件方式时才会顺便调用eloop_process_pending_signals(), 对每个处于Pending状态的struct eloop_signal调用其Handler.疑问:这样做有什么好处?如果select阻塞很久,岂不是Signal也要Pending很久才得到处理?