高级轮询技术——/dev/poll接口与kqueue

写在转载之前的:

在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;

                }

            }

        }

    }

}

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章