文章参考自:http://blog.csdn.net/tennysonsky/article/details/45745887(秋叶原 — Mike VS 麦克《Linux系统编程——I/O多路复用select、poll、epoll的区别使用》)
此外,还有一篇好文:epoll机制:epoll_create、epoll_ctl、epoll_wait、close(鱼思故渊的专栏)
在上一篇中,我简单学习了 IO多路复用的基本概念,这里我将初学其三种实现手段:select,poll,epoll。
I/O 多路复用是为了解决进程或线程阻塞到某个 I/O 系统调用而出现的技术,使进程或线程不阻塞于某个特定的 I/O 系统调用。
select(),poll(),epoll()都是I/O多路复用的机制。I/O多路复用通过一种机制,可以监视多个描述符,一旦某个描述符就绪(一般是读就绪或者写就绪,就是这个文件描述符进行读写操作之前),能够通知程序进行相应的读写操作。但select(),poll(),epoll()本质上都是同步I/O,因为他们都需要在读写事件就绪后自己负责进行读写,也就是说这个读写过程是阻塞的,而异步I/O则无需自己负责进行读写,异步I/O的实现会负责把数据从内核拷贝到用户空间。
与多线程(TPC(Thread Per Connection)模型)和多进程(典型的Apache模型(Process Per Connection,简称PPC))相比,I/O 多路复用的最大优势是系统开销小,系统不需要建立新的进程或者线程,也不必维护这些线程和进程。
select()的使用
所需头文件:
#include <sys/select.h>
函数原型:
int select(int nfds, fd_set *readfds, fd_set *writefds, fd_set *exceptfds, struct timeval *timeout);
监视并等待多个文件描述符的属性变化(可读、可写或错误异常)。select()函数监视的文件描述符分 3 类,分别是writefds、readfds、和 exceptfds。调用后 select() 函数会阻塞,直到有描述符就绪(有数据可读、可写、或者有错误异常),或者超时( timeout 指定等待时间),函数才返回。当 select()函数返回后,可以通过遍历 fdset,来找到就绪的描述符。
nfds: 要监视的文件描述符的范围,一般取监视的描述符数的最大值+1,如这里写 10, 这样的话,描述符 0,1, 2 …… 9 都会被监视,在 Linux 上最大值一般为1024。
readfd: 监视的可读描述符集合,只要有文件描述符读操作准备就绪,这个文件描述符就存储到这。
writefds: 监视的可写描述符集合。
exceptfds: 监视的错误异常描述符集合。
三个参数 readfds、writefds 和 exceptfds 指定我们要让内核监测读、写和异常条件的描述字。如果不需要使用某一个的条件,就可以把它设为NULL 。
几个较为重要的宏:
//清空集合void FD_ZERO(fd_set *fdset); //将一个给定的文件描述符加入集合之中void FD_SET(int fd, fd_set *fdset);//将一个给定的文件描述符从集合中删除void FD_CLR(int fd, fd_set *fdset);//检查集合中指定的文件描述符是否可以读写 int FD_ISSET(int fd, fd_set *fdset); timeout: 超时时间,它告知内核等待所指定描述字中的任何一个就绪可花多少时间。其 timeval 结构用于指定这段时间的秒数和微秒数。struct timeval{
time_t tv_sec; /* 秒 */suseconds_t tv_usec; /* 微秒 */};
三种可能的函数返回情况:
1)永远等待下去:timeout 设置为空指针 NULL,且没有一个描述符准备好。
2)等待固定时间:timeout 设置为某个固定时间,在有一个描述符准备好时返回,如果时间到了,就算没有文件描述符准备就绪,这个函数也会返回 0。
3)不等待(不阻塞):检查描述字后立即返回,这称为轮询。为此,struct timeval变量的时间值指定为 0 秒 0 微秒,文件描述符属性无变化返回 0,有变化返回准备好的描述符数量。
函数返回值:
成功:就绪描述符的数目(同时修改readfds、writefds 和 exceptfds 三个参数),超时返回 0;
出错:-1。
下面用 Socket 举例,两个客户端,其中一个每隔 5s 发一个固定的字符串到服务器,另一个采集终端的键盘输入,将其发给服务器,一个服务器,使用 IO 多路复用处理这两个客户端。代码如下:
服务器:
#include <cstdio>#include <sys/select.h>#include <unistd.h>#include <stdlib.h>#include <cstring>#include <cassert>#include <sys/types.h>#include <sys/socket.h>#include <netinet/in.h>#include <arpa/inet.h>const int BUFFER_SIZE = 4096;const int SERVER_PORT = 2222;inline int max(int a, int b){ return (a > b ? : a, b);}int main(){ int server_socket; char buff1[BUFFER_SIZE]; char buff2[BUFFER_SIZE]; fd_set rfds; struct timeval tv; int ret; int n; server_socket = socket(AF_INET, SOCK_STREAM, 0); assert(server_socket != -1); struct sockaddr_in server_addr; memset(&server_addr, 0, sizeof(server_addr)); server_addr.sin_family = AF_INET; server_addr.sin_port = htons(SERVER_PORT); server_addr.sin_addr.s_addr = htonl(INADDR_ANY); assert(bind(server_socket, (struct sockaddr *)&server_addr, sizeof(server_addr)) != -1); assert(listen(server_socket, 5) != -1); struct sockaddr_in client_addr1, client_addr2; socklen_t client_addr_len = sizeof(struct sockaddr_in); printf("waiting...\n"); //此处先建立两个 TCP 连接 int connfd1 = accept(server_socket, (struct sockaddr*)&client_addr1, &client_addr_len); assert(connfd1 != -1); printf("connect fr