由于uv__io_t和uv__io_poll的强相关性,所以在这里就一起介绍了。本篇文章会通过发起一个http request,通过断点调试,详细介绍uv__io_t句柄结构体和event-loop的poll阶段
uv__io_t声明
从unix.h切入:
struct uv__io_s {
uv__io_cb cb;
void* pending_queue[2];
void* watcher_queue[2];
unsigned int pevents; /* Pending event mask i.e. mask at next tick. */
unsigned int events; /* Current event mask. */
int fd;
UV_IO_PRIVATE_PLATFORM_FIELDS
};
watcher_queue
观察者队列- pevents 下一个事件的位掩码(bitmask)
- events 当前事件位掩码(bitmask)
由于本人在MacOS环境下测试,所以 UV_IO_PRIVATE_PLATFORM_FIELDS
宏展开为:
#define UV_IO_PRIVATE_PLATFORM_FIELDS \
int rcount; \
int wcount; \
- rcount (read count)用于保存在POLLIN事件中可读取的字节量
- wcount(write count)用于保存在POLLOUT事件中可写缓存区的字节量
这两个结构体成员变量取值全部由kevent->data
返回。
uv__io_poll详解
变量声明
首先我们看一下uv__io_poll
中变量的定义:
前端发起了一个http request,而这个request的回调会在poll阶段被触发,上图便是刚进入到poll阶段时候的截图,我们介绍几个主要的变量:
- events[1024] 为kevent结构体数组(在下面的介绍中我们会称之为eventlist),每一项均为一个kevent结构体变量,如图所示:
ev
为kevent的指针,它会指向eventlist 中放置事件的kevent结构体变量nevents
为改变的eventlist数量(截图的时候nevents
为2,因为在断点调试的时候有一个未完成的favicon.ico请求,刷新页面之后的http request请求在老页面favicon.ico请求之后。)revents
为要执行的事件的bit掩码,主要用来传递给cbq
用于取出当前的watcher_queue
(即保存当前w的地方)w
为uv__io_t
的指针,指向当前的io观察者filter
为 kqueue过滤器
io观察者
下面我们看一下主要的代码逻辑:
q = QUEUE_HEAD(&loop->watcher_queue);
QUEUE_REMOVE(q);
QUEUE_INIT(q);
w = QUEUE_DATA(q, uv__io_t, watcher_queue);
这段代码是通过QUEUE_DATA
宏,从watcher_queue
中取出当前的io观察者w
。拿到了当前的io观察者,后面的事情就好办了。
事件(kqueue)初始化
事件的初始化是通过EV_SET
宏实现的:
if ((w->events & POLLIN) == 0 && (w->pevents & POLLIN) != 0) {
filter = EVFILT_READ;
fflags = 0;
op = EV_ADD
// ...
EV_SET(events + nevents, w->fd, filter, op, fflags, 0, 0);
if (++nevents == ARRAY_SIZE(events)) {
// ...
}
}
首先,我们通过位运算来判断本次请求的事件,由于是http request,所以这里就进入到了POLLIN
事件中,进入流程之后的第一件事情就是改写过滤器为EVFILT_READ
,之后通过EV_SET
宏来初始化kevent结构体。
EV_SET
宏形参介绍:
EV_SET(_kev, ident, filter, flags, fflags, data, udata);
_kev
kevent 结构体ident
fd文件描述符filter
kqueue过滤器flags
kqueue事件操作fflags
特定过滤器标志data
特定过滤器的数据udata
用户传递的自定义数据
通过形参的介绍,估计大家对上面EV_SET(events + nevents, w->fd, filter, op, fflags, 0, 0);
这段代码的理解就比较清晰了。EV_SET
会定义kqueue结构体,并通过这些传参对结构体初始化。在这之后通过++nevents
记录了改变的eventlist数量。
事件(kevent)注册
事件的注册是通过kevent实现的:
nfds = kevent(loop->backend_fd,
events,
nevents,
events,
ARRAY_SIZE(events),
timeout == -1 ? NULL : &spec);
kevent
的定义如下:
kevent(int kq, const struct kevent *changelist, int nchanges,
struct kevent *eventlist, int nevents,
const struct timespec *timeout);
kq
kqueue 描述符changelist
增加或删除事件之后的eventlist 的指针nchanges
增加或删除的数量eventlist
为指向eventlist的指针nevents
eventlist的大小timeout
超时时间
通过参数的介绍,大家可以清晰的理解上文中的kevent调用,而kevent的返回值为待处理的事件数,在这里由ndfs
来记录。
事件监听
事件的监听则是通过ndfs
取出eventlist中的kevent,然后通过对filter
过滤器的判断,实现不同事件类型的监听:
for (i = 0; i < nfds; i++) {
ev = events + i;
fd = ev->ident;
// ...
if (ev->filter == EVFILT_READ) {
if (w->pevents & POLLIN) {
revents |= POLLIN;
w->rcount = ev->data;
} else {
// ...
}
}
// ....
if (w == &loop->signal_io_watcher)
have_signals = 1;
else
w->cb(loop, w, revents);
}
下面为单步的详解:
events + i
拿到待处理kevent的指针ev->filter == EVFILT_READ
过滤器来对事件类型进行过滤(同级if语句还有别的类型判断,在这里不做过多阐述)w->pevents & POLLIN
实现对POLLIN
事件的判断w->rcount = ev->data;
把数据保存到了w
即io观察者中- 最终通过
w->cb(loop, w, revents);
触发回调
参考资料:
原文地址:https://github.com/xtx1130/blog/issues/34。如果文中介绍或者逻辑有问题,欢迎大佬留言斧正。
by 小菜