#include#include #include int kqueue(void); int kevent(int kq, const struct kevent *changelist, int nchanges, struct kevent *eventlist, int nevents, const struct timespec *timeout); void EV_SET(struct kevent *kev, uintptr_t ident, short filter, u_short flags, u_int fflags, intptr_t data, void *udata); #include struct kevent{ uintptr_t ident; // identifier (e.g., file descriptor) short filter; // filter type (e.g., EVFILT_READ) u_short flags; // action flags (e.g., EV_ADD) u_int fflags; // filter-specific flags intptr_t data; // filter-specific data void *udata; // opaque user data };
其中,kevent 函数需要使用 kqueue 函数返回的 kqueue 描述符来注册所关注的事件和确定是否有所关注的事件发生。其参数 changelist 和 nchanges 表示对所关注事件做出的更改,若无更改则分别为 NULL 和 0。kevent 函数会在 nchanges 不为 0 时执行 changelist 数组中所请求的每个事件过滤器更改。对于条件已经触发的事件(包括刚在 changelist 中增设的那些事件),则由 eventlist 参数返回,它指向一个由 nevents 个元素构成的 kevent 结构数组。eventlist 中返回的事件数目将作为函数返回值,0 表示发生超时。超时通过 timeout 参数设置,其处理类似 select:NULL 阻塞进程,非 0 值指定明确的超时值,0 值执行非阻塞事件检测。
在 kevent 结构中,flags 成员在调用时指定过滤器更改行为,在返回时额外给出条件,如下图所示。
filter 成员指定的过滤器类型如下图所示。
作为例子,这里将 I/O 复用之select 函数一节中 str_cli 函数的 select 版本使用 kqueue 接口改下如下。
void str_cli(FILE *fp, int sockfd){ struct stat st; int isfile = ((fstat(fileno(fp), &st) == 0) && (st.st_mode & S_IFMT) == S_IFREG); struct kevent kev[2]; EV_SET(&kev[0], fileno(fp), EVFILT_READ, EV_ADD, 0, 0, NULL); EV_SET(&kev[1], sockfd, EVFILT_READ, EV_ADD, 0, 0, NULL); int kq = kqueue(); struct timespec ts; ts.tv_sec = ts.tv_nsec = 0; kevent(kq, kev, 2, NULL, 0, &ts); int nev, i, n, stdineof = 0; char buf[MAXLINE]; for(;;){ nev = kevent(kq, NULL, 0, kev, 2, NULL); for(i=0; i0) write(sockfd, buf, n); else if(n == 0 || (isfile && n == kev[i].data)){ stdineof = 1; shutdown(sockfd, SHUT_WR); // send FIN kev[i].flags = EV_DELETE; kevent(kq, &kev[i], 1, NULL, 0, &ts); // remove kevent } } } } }
参考书籍:
1、《UNIX 网络编程卷1:套接字联网API》第 14 章——高级 I/O 函数。