I/O复用
IO多路复用是指内核一旦发现进程指定的一个或者多个IO条件准备读取,它就通知该进程。IO多路复用适用如下场合:
(1)当客户处理多个描述字时(一般是交互式输入和网络套接口),必须使用I/O复用。
(2)当一个客户同时处理多个套接口时,而这种情况是可能的,但很少出现。
(3)如果一个TCP服务器既要处理监听套接口,又要处理已连接套接口,一般也要用到I/O复用。
(4)如果一个服务器即要处理TCP,又要处理UDP,一般要使用I/O复用。
(5)如果一个服务器要处理多个服务或多个协议,一般要使用I/O复用。
3个I/O复用函数
int select(int nfds, fd_set *readset, fd_set *writeset, fd_set *exceptset, const struct timeval *timeout);
//返回值 就绪描述符的数目,超时返回0,出错返回-1
//第一个参数nfds 指定待测试的描述字的个数,值是待测试的最大描述字+1
//中间三个参数(readset,writeset,exceptset)指定让内核测试read,write,except的描述字,可以通过4个宏设置
/*
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); //检查集合中指定的文件描述符是否可以读写
*/
//最后一个参数tiemout 告知内核等待所指定描述字中的任何一个就绪可以花费的时间。
/*
timeval结构用于指定这段时间的秒数和微秒数。
struct timeval
{
long tv_sec; //秒
long tv_usec; //微妙
};
这个参数有三种可能:
(1)永远等待下去:仅在有一个描述字准备好I/O时才返回。为此,把该参数设置为空指针。
(2)等待一段固定时间:在有一个描述字准备好I/O时返回,但是不超过由该参数所指向的timeval结构中指定的秒数和微秒数。
(3)根本不等待:检查描述字后立即返回,这称为轮询。为此,该参数必须指向一个timeval结构,而且其中的定时器值必须为0。
*/
poll
int poll(struct pollfd *fds,unsigned int nfds, int timeout);
/*
struct pollfd
{
int fd; //文件描述符
short events; //等待的事件
short revents; //实际发生了的事件
};
*/
//nfds_t类型的参数,用于标记数组fds中的结构体元素的总数量
//timeout参数指定等待的毫秒数,无论I/O是否准备好,poll都会返回。
//timeout指定为负数值表示无限超时,使poll()一直挂起直到一个指定事件发生
//timeout为0指示poll调用立即返回并列出准备好I/O的文件描述符,但并不等待其它的事件。这种情况下,poll()就像它的名字那样,一旦选举出来,立即返回。
//每个结构体的events域是监视该文件描述符的事件掩码,由用户来设置这个域
/*
POLLIN 有数据可读。
POLLRDNORM 有普通数据可读。
POLLRDBAND 有优先数据可读。
POLLPRI 有紧迫数据可读。
POLLOUT 写数据不会导致阻塞。
POLLWRNORM 写普通数据不会导致阻塞。
POLLWRBAND 写优先数据不会导致阻塞。
POLLMSGSIGPOLL 消息可用。
*/
epoll
//使用一个文件描述符管理多个描述符,将用户关心的事件存放在内核的一个事件表中,只需要copy一次(用户空间<->内核空间)
int epoll_create(int size);
//创建一个epolll对象
//size是最大fd监听+1
int epoll_ctl(int epfd ,int op ,int fd ,struct epoll_event *event);
//op的取值有:EPOLL_CTL_ADD、EPOLL_CTL_MOD、EPOLL_CTL_DEL,表示你要从监听集中添加、去除或修改某个文件描述符。
int epoll_wait(int epfd ,struct epoll_event *events ,int maxevent);
//成功返回就绪的文件描述符的个数,失败-1,并设置errno
//等待 I/O 事件的发生
//epfd: 由 epoll_create() 生成的 Epoll 专用的文件描述符
//epoll_event: 用于回传代处理事件的数组
//maxevents: 每次能处理的事件数
//timeout: 等待 I/O 事件发生的超时值 返回发生事件数。
//epoll数据结构
/*
struct epoll_event {
__uint32_t events; // Epoll events
epoll_data_t data; // User data variable
};
typedef union epoll_data {
void *ptr;
int fd;
__uint32_t u32;
__uint64_t u64;
} epoll_data_t;
*/
//工作方式 LT:会重复通告,直到被处理
// ET:必须立即处理,不会重复通告
3个函数特点
//select,poll实现需要自己不断轮询所有fd集合,直到设备就绪,期间可能要睡眠和唤醒多次交替
//poll其实也需要调用epoll_wait不断轮询就绪链表,期间也可能多次睡眠和唤醒交替,但是它是设备就绪时,调用回调函数,把就绪fd放入就绪链表中,并唤醒在epoll_wait中进入睡眠的进程
//select
/*
一个进程可监视的fd数量被限制,最大是FD_SIZE,默认是1024
需要维护一个用来存放大量fd的数据结构,这样会使得用户空间和内核空间在传递该结构时复制开销大
对socket进行扫描时是线性扫描
*/
//poll
/*
poll优点
1)poll() 在应付大数目的文件描述符的时候相比于select速度更快
2)它没有最大连接数的限制,原因是它是基于链表来存储的。
poll缺点
1)大量的fd的数组被整体复制于用户态和内核地址空间之间,而不管这样的复制是不是有意义。
2)与select一样,poll返回后,需要轮询pollfd来获取就绪的描述符
*/
//epoll
/*
epoll优点
1)支持一个进程打开大数目的socket描述符(FD)
2)IO效率不随FD数目增加而线性下降
3)使用mmap加速内核与用户空间的消息传递。
*/
关于epoll不一定比select高效的场景
并发量低,socket都比较活跃的情况下,两者效率近似
个人理解
select对每个感兴趣的事件,每次调用时都需要把FD集合从用户态拷贝到内核态
poll和select很相似,只是一个是struct pollfd,另一个是fd_set
epoll是在每次增加fd的时候,在epoll_ctl()就将fd拷贝进内核,减少了很多重复拷贝,保证每个fd只会被拷贝1次
epoll使用了callback(回调函数)机制,效率远高于轮询,所以提升了性能
推荐阅读
http://blog.csdn.net/lizhiguo0532/article/details/6568964#comments
http://blog.csdn.net/lizhiguo0532/article/details/6568968
http://blog.csdn.net/lizhiguo0532/article/details/6568969
http://m.blog.csdn.net/article/details?id=52226501 (!!!有面试需要的可以看这篇)
http://www.cnblogs.com/Anker/p/3265058.html (!!!这篇很不错)