首先介绍阻塞与非阻塞:
阻塞是个什么概念呢?好比某个时候你在等快递,可是你不知道快递何时过来,并且你没有别的事能够干(或者说接下来的事要等快递来了才能作);那么你能够去睡觉了,由于你知道快递把货送来时必定会给你打个电话(假定必定能叫醒你)。
非阻塞忙轮询。接着上面等快递的例子,若是用忙轮询的方法,那么你须要知道快递员的手机号,而后每分钟给他挂个电话:“你到了没?”
while true { for i in stream[]; { if i has data read until unavailable } }
while true { select(streams[]) for i in streams[] { if i has data read until unavailable } }
while true { active_stream[] = epoll_wait(epollfd) for i in active_stream[] { read or write till unavailable } }
kqueue与epoll很是类似,最初是2000年Jonathan Lemon在FreeBSD系统上开发的一个高性能的事件通知接口。注册一批socket描述符到 kqueue 之后,当其中的描述符状态发生变化时,kqueue 将一次性通知应用程序哪些描述符可读、可写或出错了。数组
kqueue的接口包括 kqueue()、kevent() 两个系统调用和 struct kevent 结构:多线程
struct kevent {
uintptr_t ident; /* 事件 ID */
short filter; /* 事件过滤器 */
u_short flags; /* 行为标识 */
u_int fflags; /* 过滤器标识值 */
intptr_t data; /* 过滤器数据 */
void *udata; /* 应用透传数据 */
};
在一个 kqueue 中,{ident, filter} 肯定一个惟一的事件:
事件的 id,通常设置为文件描述符。并发
能够将 kqueue filter 看做事件。内核检测 ident 上注册的 filter 的状态,状态发生了变化,就通知应用程序。kqueue 定义了较多的 filter:socket
与socket读写相关的filter:ide
行为标志flags:性能
过滤器标识值:ui
注册事件到 kqueuespa
bool Register(int kq, int fd)
{
struct kevent changes[1];
EV_SET(&changes[0], fd, EVFILT_READ, EV_ADD, 0, 0, NULL);
int ret = kevent(kq, changes, 1, NULL, 0, NULL);
return true;
}
Register 将 fd 注册到 kq 中。注册的方法是经过 kevent() 将 eventlist 和 neventlist 置成 NULL 和 0 来达到的。
人们通常将 socket IO 设置成非阻塞模式,以提升读写性能的同时,避免 IO 读写不当心被锁定。为了达到某种目的,有人会经过 getsocketopt 来偷看 socket 读缓冲区的数据大小或写缓区可用空间的大小。在 kevent 返回时,将读写缓冲区的可读字节数或可写空间大小告诉应用程序。基于这个特性,使用 kqueue 的应用通常不使用非阻塞 IO。每次读时,根据 kevent 返回的可读字节大小,将接收缓冲区中的数据一次性读完;而发送数据时,也根据 kevent 返回的写缓冲区可写空间的大小,一次只发可写空间大小的数据。线程
文章部分整合知乎上关于epoll和select的回答:https://www.zhihu.com/question/20122137