写在转载之前的:

在nginx源码src/event/ngx_event.h中涉及了好几种网络模型:

windows select
windows IOCP
select
poll
epoll
devpoll
kqueue
eventport
废弃的:
glibc aio
rtsig

可以说不同的系统,使用的网络模型不尽相同,是时候对它们总结一下了。


这里介绍两种机制,它们跟select和poll这两个函数具备类似的特性。

/dev/poll接口

Solaris上名为/dev/poll的特殊文件提供了一个可扩展的轮询大量描述符的方法。select和poll存在的一个问题是,每次调用它们都得传递待查询的文件描述符。轮询设备能在调用之间维持状态,因此轮询进程可以预先设置好待查询描述符的列表,然后进入一个循环等待事件发生,每次循环回来时不必再次设置该列表。

打开/dev/poll之后,轮询进程必须先初始化一个pollfd结构(即poll函数使用的结构,不过本机制不使用其中的revents成员)数组,再调用write往/dev/poll设备上写这个结构数组以把它传递给内核,然后执行DP_POLL命令阻塞自身以等待事件发生。

传递给ioctl调用的结构如下:

struct dvpoll {
    struct pollfd     *dp_fds;
    int                   dp_nfds;
    int                   dp_timeout;
};

其中dp_fds成员指向一个缓冲区,供ioctl在返回时存放一个pollfd结构数组。dp_nfds成员指定该缓冲区。ioctl调用将一直阻塞到任何一个被轮询描述符上发生所关心的事件,或者流逝时间超过经由dp_timeout成员指定的毫秒数为止。dp_timeout指定为0将导致ioctl立即返回,从而提供了使用本接口的非阻塞手段。dp_timeout指定为-1表示没有超时设置。

以下利用/dev/poll机制对函数str_cli进行改编。

#include "unp.h"
#include 
void
str_cli(FILE *fp, int sockfd)
{
    int stdineof;
    char buf[MAXLINE];
    int n;
    int wfd;
    struct pollfd pollfd[2];
    struct dvpoll dopoll;
    int i;
    int result;
    wfd = open("/dev/poll", O_RDWR, 0);
    pollfd[0].fd = fileno(fp);
    pollfd[0].events = POLLIN;
    pollfd[0].revents = 0;
    pollfd[1].fd = sockfd;
    pollfd[1].events = POLLIN;
    pollfd[1].revents = 0;
    write(wfd, pollfd, sizeof(struct pollfd) * 2);
    stdineof = 0;
    for ( ; ; ) {
        dopoll.dp_timeout = -1;
        dopoll.dp_nfds = 2;
        dopoll.dp_fds = pollfd;
        result = ioctl(wfd, DP_POLL, &dopoll);
        for (i = 0; i < result; i++) {
            if (dopoll.dp_fds[i].fd == sockfd) {
                if ( (n = read(sockfd, buf, MAXLINE)) == 0) {
                    if (stdineof == 1)
                        return; 
                    else
                        err_quit("str_cli: server terminated prematurely");
                }
                write(fileno(stdout), buf, n);
            }
            else {
                if ((n = read(fileno(fp), buf, MAXLINE)) == 0) {
                    stdineof = 1;
                    shutdown(sockfd, SHUT_WR);     
                    continue;
                }
                write(sockfd, buf, n);
            }
        }
    }
}

kqueue接口

FreeBSD引入了kqueue接口。本接口允许进程向内核注册描述所关注kqueue事件的事件过滤器。事件除了与select所关注类似的文件I/O和超时外,还有异步I/O、文件修改通知(例如文件被删除或修改时发出的通知)、进程跟踪(例如进程调用exit或fork时发出的通知)和信号处理。kqueue接口有如下两个函数和一个宏。

#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);

kqueue函数返回一个新的kqueue描述符,用于后续的kevent调用中。kevent函数既用于注册所关注的事件,也用于确定是否有所关注的事件发生。changelist和nchanges这两个参数给出对所关注事件做出的更改,若无更改则分别取值NULL和0。如果nchanges不为0,kevent函数就执行changelist数组中所请求的每个事件过滤器更改。其条件已经触发的任何事件(包括刚在changelist中增设的那些事件)由kevent函数通过eventlist参数返回,它指向一个由nevents个元素构成的kevent结构数组。kevent函数在eventlist中返回的事件数目作为函数返回值返回,0表示超时。超时通过timeout参数设置,其处理类似select:NULL阻塞进程,非0值timespec指定明确的超时值,0值timespec执行非阻塞事件检查。注意,kevent使用的timespec结构不同于select使用的timeval结构,前者的分辨率为纳秒,后者的分辨率为微秒。

kevent结构在头文件中定义:

struct kevent {
    uintptr_t ident;     
    short filter;         
    u_short flags;      
    u_int fflags         
    intptr_t data;       
    void *udata;        
};

其中的flags成员在调用时指定过滤器更改行为,在返回时额外给出条件,如下图所示。

filter成员指定的过滤器类型如下图所示。

以下利用kqueue机制对函数str_cli进行改编。

void
str_cli(FILE *fp, int sockfd)
{
    int kq, i, n, nev, stdineof = 0, isfile;
    char buf[MAXLINE];
    struct kevent kev[2];
    struct timespec ts;
    struct stat st;
    isfile = ((fstat(fileno(fp), &st) == 0) &&
    (st.st_mode & S_IFMT) == S_IFREG);
    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);
    kq = kqueue();
    ts.tv_sec = ts.tv_nsec = 0;
    kevent(kq, kev, 2, NULL, 0, &ts);
 
    for ( ; ; ) {
        nev = kevent(kq, NULL, 0, kev, 2, NULL);
 
        for (i = 0; i < nev; i++) {
            if (kev[i].ident == sockfd) { 
                if ( (n = read(sockfd, buf, MAXLINE)) == 0) {
                    if (stdineof == 1)
                        return; 
                    else
                        err_quit("str_cli: server terminated prematurely");
                }
                write(fileno(stdout), buf, n);
            }
 
            if (kev[i].ident == fileno(fp)) {  
                n = read(fileno(fp), buf, MAXLINE);
                if (n > 0)
                    write(sockfd, buf, n);
 
                if (n == 0 || (isfile && n == kev[i].data)) {
                    stdineof = 1;
                    shutdown(sockfd, SHUT_WR); 
                    kev[i].flags = EV_DELETE;
                    kevent(kq, &kev[i], 1, NULL, 0, &ts); 
                    continue;
                }
            }
        }
    }
}