首页 > 我的观点, 程序人生 > 浅析常用事件驱动库

浅析常用事件驱动库

通常,我们写服务器处理模型的程序时,有以下几种模型:

(1)每收到一个请求,创建一个新的进程,来处理该请求;

(2)每收到一个请求,创建一个新的线程,来处理该请求;

(3)每收到一个请求,放入一个事件列表,让主进程通过非阻塞I/O方式来处理请求

上面的几种方式,各有千秋,第(1)中方法,由于创建新的进程的开销比较大,所以,会导致服务器性能比较差,但实现比较简单。第(2)种方式,由于要涉及到线程的同步,有可能会面临死锁等问题。第(3)种方式,在写应用程序代码时,逻辑比前面两种都复杂。综合考虑各方面因素,一般普遍认为第(3)种方式是大多数网络服务器采用的方式,这也是本文讨论的重点—事件驱动处理库。

1. select

select在linux和windows平台上都支持的,接口基本上相同,但参数的含义略有不同。通常,使用select库的步骤是:

(1)创建所关注的事件的描述符集合(fd_set),对于一个描述符,可以关注其上面的读(read)、写(write)、异常(exception)事件,所以通常,要创建三个fd_set, 一个用来收集关注读事件的描述符,一个用来收集关注写事件的描述符,另外一个用来收集关注异常事件的描述符集合。

(2)调用select(),等待事件发生。这里需要注意的一点是,select的阻塞与是否设置非阻塞I/O是没有关系的。select的原型如下所示:
int select(int nfds ,
fd_set* readfds ,
fd_set* writefds ,
fd_set* exceptfds,
const struct timeval* timeout
);
其中,最后一个参数timeout,可以设置select等待的时间。如果该值设置为0,那么,select()在有事件发生的时候就立即返回。如果该值不为0,那么,select()会等待指定的时间,然后再返回。select()的返回值指定了发生事件的fd个数。

(3)轮询所有fd_set中的每一个fd ,检查是否有相应的事件发生,如果有,就进行处理。

2. poll

poll库是在linux2.1.23中引入的,windows平台不支持poll. poll与select的基本方式相同,都是先创建一个关注事件的描述符的集合,然后再去等待这些事件发生,然后再轮询描述符集合,检查有没有事件发生,如果有,就进行处理。因此,poll有着与select相似的处理流程:

(1)创建描述符集合,设置关注的事件

(2)调用poll(),等待事件发生。下面是poll的原型:
int poll(struct pollfd *fds, nfds_t nfds, int timeout);

类似select,poll也可以设置等待时间,效果与select一样。

(3)轮询描述符集合,检查事件,处理事件。

在这里要说明的是,poll与select的主要区别在与,select需要为读、写、异常事件分配创建一个描述符集合,最后轮询的时候,需要分别轮询这三个集合。而poll只需要一个集合,在每个描述符对应的结构上分别设置读、写、异常事件,最后轮询的时候,可以同时检查三种事件。

3. epoll

epoll是和上面的poll和select不同的一个事件驱动库,它是在linux 2.5.44中引入的,它属于poll的一个变种。上面的poll和select库,它们的最大的问题就在于效率。它们的处理方式都是创建一个事件列表,然后把这个列表发给内核,返回的时候,再去轮询检查这个列表,这样在描述符比较多的应用中,效率就显得比较低下了。一种比较好的做法是,把描述符列表交给内核,一旦有事件发生,内核把发生事件的描述符列表通知给进程,这样就避免了轮询整个描述符列表。epoll就是这样一种模型。下面对epoll的使用进行说明:

(1).创建一个epoll描述符,调用epoll_create()来完成,epoll_create()有一个整型的参数size,用来告诉内核,要创建一个有size个描述符的事件列表(集合)

int epoll_create(int size)

(2).给描述符设置所关注的事件,并把它添加到内核的事件列表中去,这里需要调用epoll_ctl()来完成。

int epoll_ctl(int epfd, int op, int fd, struct epoll_event *event)

这里op参数有三种,分别代表三种操作:

a. EPOLL_CTL_ADD,  把要关注的描述符和对其关注的事件的结构,添加到内核的事件列表中去

b. EPOLL_CTL_DEL,把先前添加的描述符和对其关注的事件的结构,从内核的事件列表中去除

c. EPOLL_CTL_MOD,修改先前添加到内核的事件列表中的描述符的关注的事件

(3). 等待内核通知事件发生,得到发生事件的描述符的结构列表,该过程由epoll_wait()完成。得到事件列表后,就可以进行事件处理了。
int epoll_wait(int epfd, struct epoll_event * events, int maxevents, int timeout)

在使用epoll的时候,有一个需要特别注意的地方,那就是epoll触发事件的文件有两种方式:

(1)Edge Triggered(ET),在这种情况下,事件是由数据到达边界触发的。所以要在处理读、写的时候,要不断的调用read/write,直到它们返回EAGAIN,然后再去epoll_wait(),等待下次事件的发生。这种方式适用要遵从下面的原则:

a. 使用非阻塞的I/O;b.直到read/write返回EAGAIN时,才去等待下一次事件的发生。

(2)Level Triggered(LT), 在这种情况下,epoll和poll类似,但处理速度上可能比poll快。在这种情况下,只要有数据没有读、写完,调用epoll_wait()的时候,就会有事件被触发。

  1. 2009年9月18日07:41 | #1

    Apache就是用的select模式,而Nginx用的是epoll模式,所以性能上比Apache要好很多,而且Ddos攻击对Nginx来说基本上没用。

  2. 2009年9月19日01:19 | #3

    Where are you from? Is it a secret? 🙂
    Thank you

  1. 本文目前尚无任何 trackbacks 和 pingbacks.
您必须在 登录 后才能发布评论.