我是靠谱客的博主 淡淡篮球,这篇文章主要介绍libevent IO多路复用之epoll,现在分享给大家,希望可以做个参考。

libevent事件驱动, 同步而非阻塞的. 为了保证非阻塞我们在前面已经看到过多次了, 设置文件描述符为非阻塞. libevent内部也是为了满足非阻塞所以集成了一系列的多路复用函数, 但并非每个都会使用. libevent会根据不同系统选择不同的复用函数. 这里我们主要分析linux的复用机制(epoll).

统一接口

不管是在windows还是linux, 也不管使用select还是epoll都统一调用eventop定义的函数指针, 这也可以叫做事件多路分发器.

复制代码
1
2
3
4
5
6
7
8
9
10
11
struct eventop { const char *name; void *(*init)(struct event_base *); // 初始化 int (*add)(void *, struct event *); // 注册事件 int (*del)(void *, struct event *); // 删除事件 int (*dispatch)(struct event_base *, void *, struct timeval *); // 事件分发 void (*dealloc)(struct event_base *, void *); // 注销,释放资源 /* set if we need to reinitialize the event base */ int need_reinit; };

而分发器调用的函数都是根据编译器选择默认设置.

复制代码
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
static const struct eventop *eventops[] = { #ifdef HAVE_EVENT_PORTS &evportops, #endif #ifdef HAVE_WORKING_KQUEUE &kqops, #endif #ifdef HAVE_EPOLL &epollops, 35 #endif #ifdef HAVE_DEVPOLL &devpollops, #endif #ifdef HAVE_POLL &pollops, #endif #ifdef HAVE_SELECT &selectops, #endif #ifdef WIN32 &win32ops, #endif NULL };

epollop 结构

该结构源码在epoll.c

复制代码
1
2
3
4
5
6
7
8
9
struct epollop { // 对应的event的管理, 由 fds[fd] 来管理具体哪一个文件描述符的 IO 操作 struct evepoll *fds; int nfds; // fd 的数量 struct epoll_event *events; // epoll 的事件 int nevents; // 事件的数量 int epfd; // epoll_create 返回对应的文件描述符 };
复制代码
1
2
3
4
5
6
// epoll IO struct evepoll { struct event *evread; struct event *evwrite; };

epollops默认初始化设置的5种函数都是对epoll函数的封装而已. 下面我们就来具体分析这些函数的封装.

复制代码
1
2
3
4
5
6
7
8
9
10
11
// epoll 对应的函数 const struct eventop epollops = { "epoll", epoll_init, epoll_add, epoll_del, epoll_dispatch, epoll_dealloc, 1 /* need reinit */ };

epoll_init 函数

复制代码
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
// 初始化 // 调用 epoll_create 初始化 epoll 并且初始化 epollop 结构 static void * epoll_init(struct event_base *base) { int epfd; struct epollop *epollop; /* Disable epollueue when this environment variable is set */ if (evutil_getenv("EVENT_NOEPOLL")) return (NULL); /* Initalize the kernel queue */ // 创建 epoll 连接 if ((epfd = epoll_create(32000)) == -1) { if (errno != ENOSYS) event_warn("epoll_create"); return (NULL); } // 设置文件描述符 FD_CLOSEONEXEC(epfd); if (!(epollop = calloc(1, sizeof(struct epollop)))) return (NULL); // 初始化监听信号 epollop->epfd = epfd; /* Initalize fields */ // 分配空间 epollop->events = malloc(INITIAL_NEVENTS * sizeof(struct epoll_event)); if (epollop->events == NULL) { free(epollop); return (NULL); } epollop->nevents = INITIAL_NEVENTS; epollop->fds = calloc(INITIAL_NFILES, sizeof(struct evepoll)); if (epollop->fds == NULL) { free(epollop->events); free(epollop); return (NULL); } // 事件标志设置为已初始化 epollop->nfds = INITIAL_NFILES; // 并且对信号也设置初始化 evsignal_init(base); return (epollop); }

epoll_init函数其实就是调用epoll_create并且对描述符设置为非阻塞, 再对event分配返回epoll事件的内存大小, 最后设置初始化标志并初始化信号.

epoll_add 函数

复制代码
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
// 添加 epoll 事件 // 其实调用 epoll_ctl 选项的 EPOLL_CTL_MOD 和 EPOLL_CTL_ADD static int epoll_add(void *arg, struct event *ev) { struct epollop *epollop = arg; struct epoll_event epev = {0, {0}}; struct evepoll *evep; int fd, op, events; // 如果事件是信号, 则添加到信号中 if (ev->ev_events & EV_SIGNAL) return (evsignal_add(ev)); // 如果文件描述符大于了 nfds 时, 重新处理分配处理事件的空间大小 fd = ev->ev_fd; if (fd >= epollop->nfds) { /* Extent the file descriptor array as necessary */ if (epoll_recalc(ev->ev_base, epollop, fd) == -1) return (-1); } // evep 是当前事件的 IO状态 evep = &epollop->fds[fd]; op = EPOLL_CTL_ADD; events = 0; // 如果事件本身可读(即 已经在epoll中), 所以设置方法为 EPOLL_CTL_MOD if (evep->evread != NULL) { events |= EPOLLIN; op = EPOLL_CTL_MOD; } // 同理 if (evep->evwrite != NULL) { events |= EPOLLOUT; op = EPOLL_CTL_MOD; } // 如果事件是 EV_READ 表示是读事件 if (ev->ev_events & EV_READ) events |= EPOLLIN; // 事件事件是 EV_WRITE 表示是写事件 if (ev->ev_events & EV_WRITE) events |= EPOLLOUT; epev.data.fd = fd; // 设置监听的文件描述符 epev.events = events; // 设置当前的监听事件 if (epoll_ctl(epollop->epfd, op, ev->ev_fd, &epev) == -1) return (-1); /* Update events responsible */ // 更新事件的监听状态, 之所以没有上面同时修改, 是要保证 epoll_ctl 调用成功 if (ev->ev_events & EV_READ) evep->evread = ev; if (ev->ev_events & EV_WRITE) evep->evwrite = ev; return (0); }

epoll_del 删除事件

复制代码
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
// 删除事件 static int epoll_del(void *arg, struct event *ev) { struct epollop *epollop = arg; struct epoll_event epev = {0, {0}}; struct evepoll *evep; int fd, events, op; int needwritedelete = 1, needreaddelete = 1; // 如果是信号, 则交给信号处理函数处理 if (ev->ev_events & EV_SIGNAL) return (evsignal_del(ev)); fd = ev->ev_fd; if (fd >= epollop->nfds) return (0); evep = &epollop->fds[fd]; op = EPOLL_CTL_DEL; events = 0; if (ev->ev_events & EV_READ) events |= EPOLLIN; if (ev->ev_events & EV_WRITE) events |= EPOLLOUT; // 如果事件本身已经存在 epoll 中 if ((events & (EPOLLIN|EPOLLOUT)) != (EPOLLIN|EPOLLOUT)) { // 如果是可写 if ((events & EPOLLIN) && evep->evwrite != NULL) { needwritedelete = 0; events = EPOLLOUT; op = EPOLL_CTL_MOD; // 如果是可读 } else if ((events & EPOLLOUT) && evep->evread != NULL) { needreaddelete = 0; events = EPOLLIN; op = EPOLL_CTL_MOD; } } epev.events = events; epev.data.fd = fd; if (needreaddelete) evep->evread = NULL; if (needwritedelete) evep->evwrite = NULL; if (epoll_ctl(epollop->epfd, op, fd, &epev) == -1) return (-1); return (0); }

epoll_dispatch 函数

epoll_dispatch 分发事件, 这是epoll封装的一个重要的东西.

复制代码
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
// 分发事件 // 调用 epoll_wait 函数处理到来的事件. 并检查信号和事件的流量大小 static int epoll_dispatch(struct event_base *base, void *arg, struct timeval *tv) { struct epollop *epollop = arg; struct epoll_event *events = epollop->events; struct evepoll *evep; int i, res, timeout = -1; // 计算时间 if (tv != NULL) timeout = tv->tv_sec * 1000 + (tv->tv_usec + 999) / 1000; // 如果时间大于了最大 epoll 设置时间就设置为 epoll 最大时间 if (timeout > MAX_EPOLL_TIMEOUT_MSEC) { /* Linux kernels can wait forever if the timeout is too big; * see comment on MAX_EPOLL_TIMEOUT_MSEC. */ timeout = MAX_EPOLL_TIMEOUT_MSEC; } // 调用 epoll_wait 等待事件到来 res = epoll_wait(epollop->epfd, events, epollop->nevents, timeout); if (res == -1) { if (errno != EINTR) { event_warn("epoll_wait"); return (-1); } // 如果是信号则加入将信号加入到就绪队列中 evsignal_process(base); return (0); } else if (base->sig.evsignal_caught) { // 如果 epoll_wait 正常返回, 检查是否有信号标识被设置, 有则处理信号 evsignal_process(base); } event_debug(("%s: epoll_wait reports %d", __func__, res)); // 处理IO事件 for (i = 0; i < res; i++) { // IO事件的类型 int what = events[i].events; struct event *evread = NULL, *evwrite = NULL; int fd = events[i].data.fd; if (fd < 0 || fd >= epollop->nfds) continue; evep = &epollop->fds[fd]; // 异常 if (what & (EPOLLHUP|EPOLLERR)) { evread = evep->evread; evwrite = evep->evwrite; } else { // 可写 if (what & EPOLLIN) { evread = evep->evread; } // 可读 if (what & EPOLLOUT) { evwrite = evep->evwrite; } } if (!(evread||evwrite)) continue; // 触发 IO 操作 if (evread != NULL) event_active(evread, EV_READ, 1); if (evwrite != NULL) event_active(evwrite, EV_WRITE, 1); } // 如果事件的数量太多, 则重新分配更大的处理事件 if (res == epollop->nevents && epollop->nevents < MAX_NEVENTS) { /* We used all of the event space this time. We should be ready for more events next time. */ int new_nevents = epollop->nevents * 2; struct epoll_event *new_events; new_events = realloc(epollop->events, new_nevents * sizeof(struct epoll_event)); if (new_events) { epollop->events = new_events; epollop->nevents = new_nevents; } } return (0); }

我将比较重要的部分提取了出来, 就是关于信号的处理.

复制代码
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
// 调用 epoll_wait 等待事件到来 res = epoll_wait(epollop->epfd, events, epollop->nevents, timeout); if (res == -1) { if (errno != EINTR) { event_warn("epoll_wait"); return (-1); } // 如果是信号则加入将信号加入到就绪队列中 evsignal_process(base); return (0); } else if (base->sig.evsignal_caught) { // 如果 epoll_wait 正常返回, 检查是否有信号标识被设置, 有则处理信号 evsignal_process(base); }

在这里可以看到, epoll_wait等待事件

  • 如果被中断且中断源是信号, 则调用evsignal_process将信号加入就绪队列中
  • 如果正常退出, 但是事件的信号标志被设置, 也将信号加入到就绪队列中.

总结

本节基本上没有做太多的分析, 因为基本都是关于网络编程的基础, 所以这里也就不花篇幅来分析.

  • 在epoll_dispatch中执行epoll_wait, 并且通过返回后判断类型来决定是否将信号就如就绪队列.

最后

以上就是淡淡篮球最近收集整理的关于libevent IO多路复用之epoll的全部内容,更多相关libevent内容请搜索靠谱客的其他文章。

本图文内容来源于网友提供,作为学习参考使用,或来自网络收集整理,版权属于原作者所有。
点赞(54)

评论列表共有 0 条评论

立即
投稿
返回
顶部