i/o多路复用


最常见的i/o多路复用就是:select poll epoll了,下面说说他们的一些特点和区别吧。


select

/* 可读、可写、异常三种文件描述符集的申明和初始化。*/
fd_set readfds, writefds, exceptionfds;
FD_ZERO(&readfds);
FD_ZERO(&writefds);
FD_ZERO(&exceptionfds);

int max_fd;

sock = socket(...);
bind(sock, ...);
listen(sock, ...);

FD_SET(&readfds, sock);			//添加关心的sock
max_fd = sock;

while(1) {
	int i;
	fd_set r,w,e;

	memcpy(&r, &readfds, sizeof(fd_set));
	memcpy(&w, &writefds, sizeof(fd_set));
	memcpy(&e, &exceptionfds, sizeof(fd_set));

	/* 利用临时变量调用select()阻塞等待,等待时间为永远等待直到发生事件。*/
	select(max_fd + 1, &r, &w, &e, NULL);

	if(FD_ISSET(&r, sock)){
		new_sock = accept(sock, ...);
		FD_SET(&readfds, new_sock);
		FD_SET(&writefds, new_sock);
		max_fd = MAX(max_fd, new_sock);
	}
	for(i= sock+1; i<max_fd+1; ++i) {
		if(FD_ISSET(&r, i))
			doReadAction(i);
		if(FD_ISSET(&w, i))
			doWriteAction(i);
	}
}


poll

利用poll多路复用I/O的Web服务应用模型
/* 新建并初始化文件描述符集。*/
struct pollfd fds[MAX_NUM_FDS];
int max_fd;

/* socket配置和监听。*/
sock = socket(...);
bind(sock, ...);
listen(sock, ...);

/* 对socket描述符上发生关心的事件进行注册。*/
fds[0].fd = sock;
fds[0].events = POLLIN;
max_fd = 1;

while(1) {
    int i;

    /*调用poll()阻塞等待,等待时间为永远等待直到发生事件。*/
    poll(fds, max_fd, -1);

    /* 测试是否有客户端发起连接请求,如果有则接受并把新建的描述符加入监控。*/
    if(fds[0].revents & POLLIN){
        new_sock = accept(sock, ...);
        fds[max_fd].fd = new_sock;
        fds[max_fd].events = POLLIN | POLLOUT;
        ++ max_fd;
    }
    /* 对其它描述符发生的事件进行适当处理。*/
    for(i=1; i<max_fd+1; ++i) {
        if(fds.revents & POLLIN)
                doReadAction(i);
        if(fds.revents & POLLOUT)
                doWriteAction(i);
    }
}

epoll
利用epoll多路复用I/O的Web服务应用模型
/* 新建并初始化文件描述符集。*/
struct epoll_event ev;
struct epoll_event events[MAX_EVENTS];

/* 创建epoll句柄。*/
int epfd = epoll_create(MAX_EVENTS);

/* socket配置和监听。*/
sock = socket(...);
bind(sock, ...);
listen(sock, ...);

/* 对socket描述符上发生关心的事件进行注册。*/
ev.events = EPOLLIN;
ev.data.fd = sock;
epoll_ctl(epfd, EPOLL_CTL_ADD, sock, &ev);

while(1) {
    int i;
    /*调用epoll_wait()阻塞等待,等待时间为永远等待直到发生事件。*/
    int n = epoll_wait(epfd, events, MAX_EVENTS, -1);
    for(i=0; i<n; ++i) {
    /* 测试是否有客户端发起连接请求,如果有则接受并把新建的描述符加入监控。*/
        if(events.data.fd == sock) {
        if(events.events & POLLIN){
            new_sock = accept(sock, ...);
            ev.events = EPOLLIN | POLLOUT;
            ev.data.fd = new_sock;
            epoll_ctl(epfd, EPOLL_CTL_ADD, new_sock, &ev);
        }
        }else{
            /* 对其它描述符发生的事件进行适当处理。*/
            if(events.events & POLLIN)
                doReadAction(i);
            if(events.events & POLLOUT)
                doWriteAction(i);
        }
    }
}

为什么select是落后的?
首先,在Linux内核中,select所用到的FD_SET是有限的,即内核中有个参数__FD_SETSIZE定义了每个FD_SET的句柄个数,在我用的2.6.15-25-386内核中,该值是1024,
搜索内核源代码得到:
include/linux/posix_types.h:#define __FD_SETSIZE        1024
也就是说,如果想要同时检测1025个句柄的可读状态是不可能用select实现的。或者同时检测1025个句柄的可写状态也是不可能的。
其次,内核中实现select是用轮询方法,即每次检测都会遍历所有FD_SET中的句柄,显然,select函数执行时间与FD_SET中的句柄个数有一个比例关系,即select要检测的句柄数越多就会越费时。

epoll的优点:
1.支持一个进程打开大数目的socket描述符(FD)
    select 最不能忍受的是一个进程所打开的FD是有一定限制的,由FD_SETSIZE设置,默认值是2048。对于那些需要支持的上万连接数目的IM服务器来说显然太少了。这时候你一是可以选择修改这个宏然后重新编译内核,不过资料也同时指出这样会带来网络效率的下降,二是可以选择多进程的解决方案(传统的 Apache方案),不过虽然linux上面创建进程的代价比较小,但仍旧是不可忽视的,加上进程间数据同步远比不上线程间同步的高效,所以也不是一种完美的方案。不过 epoll则没有这个限制,它所支持的FD上限是最大可以打开文件的数目,这个数字一般远大于2048,举个例子,在1GB内存的机器上大约是10万左右,具体数目可以cat /proc/sys/fs/file-max察看,一般来说这个数目和系统内存关系很大。
2.IO效率不随FD数目增加而线性下降
    传统的select/poll另一个致命弱点就是当你拥有一个很大的socket集合,不过由于网络延时,任一时间只有部分的socket是"活跃"的,但是select/poll每次调用都会线性扫描全部的集合,导致效率呈现线性下降。但是epoll不存在这个问题,它只会对"活跃"的socket进行操作---这是因为在内核实现中epoll是根据每个fd上面的callback函数实现的。那么,只有"活跃"的socket才会主动的去调用 callback函数,其他idle状态socket则不会,在这点上,epoll实现了一个"伪"AIO,因为这时候推动力在os内核。在一些 benchmark中,如果所有的socket基本上都是活跃的---比如一个高速LAN环境,epoll并不比select/poll有什么效率,相反,如果过多使用epoll_ctl,效率相比还有稍微的下降。但是一旦使用idle connections模拟WAN环境,epoll的效率就远在select/poll之上了。
3.使用mmap加速内核与用户空间的消息传递。
    这点实际上涉及到epoll的具体实现了。无论是select,poll还是epoll都需要内核把FD消息通知给用户空间,如何避免不必要的内存拷贝就很重要,在这点上,epoll是通过内核于用户空间mmap同一块内存实现的。而如果你想我一样从2.5内核就关注epoll的话,一定不会忘记手工 mmap这一步的。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值