(1)客户端程序要同时处理多个socket
(2)客户端程序要同时处理用户输入和网络连接(3)TCP服务器要同时处理监听socket和连接socket(这时I/O复用使用最多的场合)
(4)服务器要同时处理TCP请求和UDP请求
(5)服务器要同时监听多个端口,或者处理多种服务。需要指出的是,I/O复用虽然能同时监听多个文件描述符,但是它本身是阻塞的。并且当多个文件描述符就绪时,如果不采取额外的措施,程序就只能按顺序依次处理其中的每一个文件描述符,这使得服务器程序看起来像是串行工作。如果要实时并发,只能使用多进程和多线程等手段。
2、select函数
1 #include <sys/select.h>
2 int select(int nfds, fd_set *readfds, fd_set *writefds, fd_set *exceptfds, struct timeval *timeout)
函数参数介绍:
(1)nfds参数指定被监听的文件描述符的总数。它通常被设置为监听的所有文件描述符加一,因为文件描述符是从0开始计数的。
(2)Readfds、writefds、exceptfds参数分别指向可读、可写和异常等事件对应的文件描述符,应用程序调用select函数时,通过这三个参数传入自己感兴趣的文件描述符。Select返回时,内核将修改他们来通知应用程序那些文件描述符已经就绪。这三个参数都是fd_set结构体指针类型。Fd_set结构体仅包含一个整形数组,该数组的每一个元素的每一位标记一个文件描述符,fd_set所能容纳的文件描述符有FD_SETSIZE指定,这就限制了select能同时处理的文件描述符的总量。可以通过下面的宏来访问fd_set结构体中的位
2 FD_ZERO(fd_set *fdset) //清除fdset的所有位
3 FD_SET(int fd, fd_set *fd_set) //设置fdset的位fd
4 FD_CLR(int fd, fd_set *fd_set) //清除fdset的位fd
5 FD_ISSET(int fd, fd_set *fd_set) //测试fdset的位fd是否被设置
(3)timeout用来设置select函数的超时时
2 {
3 Long tv_sec; //秒数
4 Long tv_usev; //微妙数
5 };
这个参数有三种可能:当把参数设置为NULL,则select将一直阻塞,直到某个文件描述符准备就绪;设置为一个固定的时间,在有一个描述符准备好返回,但是不超过指定的时间;设置为0时,select则立即返回。
2、poll函数poll系统调用的select类似,也是在指定时间内轮询一定数量的文件描述符,以测试其是否就绪
2 Int poll(struct pollfd *fds, nfds_t nfds, int timrout);
(1)fds参数是一个pollds类型的结构体数组,它指定在我们感兴趣的文件描述符上发生可读、可写和异常事件。Pollfd结构体的定义
2 {
3 Int fd; //文件描述符
4 Short events; //注册的事件
5 Short revents; //实际发生的事件,有内核完成
6 } ;
每个结构体的events域是监控该文件描述符的事件掩码,由用户来设置这个域,revents域是文件描述符的操作结果事件掩码,内核在调用返回时设置这个域。events域中请求的任何事件都可能在revents域中返回。合法的事件如下:
2 POLLRDNORM 有普通数据可读。
3 POLLRDBAND 有优先数据可读。
4 POLLPRI 有紧迫数据可读。
5 POLLOUT 写数据不会导致阻塞。
6 POLLWRNORM 写普通数据不会导致阻塞。
7 POLLWRBAND 写优先数据不会导致阻塞。
8 POLLMSGSIGPOLL 消息可用。
9 POLLER 指定的文件描述符发生错误。
10 POLLHUP 指定的文件描述符挂起事件。
(2)nfds参数指定被监听事件集合fds的大小
(3)Timeout参数指定poll的超时值。当timeout为-1时,poll调用将永远阻塞,直到某个事件发生;当timeout为0时,poll将立即返回。
3、epoll函数
Epoll是Linux特有的I/O复用函数。它在实际和使用上和select、poll有很大的差异。首先,epoll使用一组函数来完成任务,而不是单个函数。其次,epoll把用户关心的文件描述符上的事件放在内核里的一个事件表中,从而无需想select和poll那样每次调用都要重复传入文件描述符集。但是epoll需要使用一个额外的文件描述符,来唯一标识内核中的这个事件表。
(1)这个文件描述符使用epoll_create函数来创建:
2 Int epoll_wait(int epfd, struct epoll_event *events, int maxevents, int timeout);
该函数返回成功就绪文件描述符的个数,失败是返回-1并设置errno。
类似于select调用。参数events用来从内核得到事件的集合,maxevents的值不能大于创建epoll_create时的size,参数timeout是超时时间
5、LT和ET模式
LT模式(水平触发):当epoll_wait检测到其上有事件发生并将此事件通知应用程序后,应用程序可以不立即处理该事件。这样,当应用程序下一次调用epoll_wait时,epoll_wait还会再次向应用程序通告次事件。ET模式(边沿触发):当epoll_wait检测到其上有事件发生并将此事件通知应用程序后,应用程序必须立即处理该事件,因为后续的epoll_wait调用将不再向应用程序通知这一事件。Epoll工作在ET模式的时候,必须使用非阻塞套接字,以避免由于一个文件句柄阻塞读/阻塞写操作把处理多个文件描述符的任务饿死。
可见,ET模式在很大程度上降低了同一个epoll事件被重复触发的次数,因此效率要比LT模式高。6、三组I/O复用函数的比较
(1)事件集:
Select:Select的参数类型fd_set没有将文件描述符和事件进行绑定,它仅仅是一个文件描述符的集合,因此select的参数需要提供3个这种类型的参数来分别传入和输出可读、可写及异常事件。这一方面使得select不能处理更多类型的事件,另一方面由于内核对fd_set集合的在线修改,应用程序下次调用select前不得不重置3个fd_set集合。Poll:poll的参数pollfd,它把文件描述符和事件都定义在其中,任何事件都被统一处理,从而使编程接口简单的多,并且内核每次修改的是pollfd结构体的revents成员,而events 成员保持不变,因此下次调用poll时应用程序无需重置pollfd类型的事件集合。
Epoll:epoll采用完全不同与select和poll的方式来管理用户注册的事件。它在内核中维护一张事件表,并提供一个独立的系统调用epoll_ctl来控制往其中添加、删除、修改事件。这样,每次epoll_wait调用都直接从内核事件表中取得用户注册的事件,而无需反复从用户空间读入这些事件。(2)最大支持文件描述符数:
Select:select打开的文件描述符fd是由一定限制的,有 fd_size设置,默认为2048,对于那些需要支持上万连接数目的服务器来说显然太少了。
Poll和epoll:poll和epoll分别用nfds和maxevents参数指定最多监听多少个文件描述符和事件,这两个值都能达到系统允许打开的最大文件描述符数目,即65535。(3)工作模式:
Select和poll都只能工作在相对低效的LT模式,而epoll则可以工作在ET高效模式。(4)实现原理:
Select和poll采用的都是轮询的方式,即每次调用都要扫描整个注册文件描述符集合,并将其中就绪的文件描述符返回给用户,因此他们检测就绪事件的算法时间复杂度是O(n)。Epoll的epoll_wait则不同,它采用的是回调函数的方式。内核检测到就绪的文件描述符时,将触发回调函数,回调函数就将该文件描述符上对应的时间插入内核就绪事件队列。内核最后在适当的时候将就绪事件队列中的内容拷贝到用户空间。因此epoll_wait无需轮询整个文件描述符集合来检测那些事件已经准备就绪,其算法事件复杂度O(1)。但是,当活动链接比较多时,epoll_wait的效率未必比select和poll高,因为回调函数被触发得过于频繁。所以epoll_wait适用于连接数量多,但是活动连接少的情况。
(5)使用mmap加速内核与用户空间的消息传递。
这点实际上涉及到epoll的具体实现了。无论是select,poll还是epoll都需要内核把FD消息通知给用户空间,如何避免不必要的内存拷贝就很重要,在这点上,epoll是通过内核于用户空间mmap同一块内存实现的。