IO多路复用( 三 )


它接受3个文件描述符的数组,分别监听读取(),写入()和异常()事件 。那么一个 IO多路复用的代码大概是这样:
struct timeval tv = {.tv_sec = 1, .tv_usec = 0};

ssize_t nbytes;
while(1) {
FD_ZERO(&read_fds);
setnonblocking(fd1);
setnonblocking(fd2);
FD_SET(fd1, &read_fds);
FD_SET(fd2, &read_fds);
// 把要监听的fd拼到一个数组里,而且每次循环都得重来一次...
if (select(FD_SETSIZE, &read_fds, NULL, NULL, &tv) < 0) { // block住,直到有事件到达
perror("select出错了");
exit(EXIT_FAILURE);
}
for (int i = 0; i < FD_SETSIZE; i++) {
if (FD_ISSET(i, &read_fds)) {
/* 检测到第[i]个读取fd已经收到了,这里假设buf总是大于到达的数据,所以可以一次read完 */
if ((nbytes = read(i, buf, sizeof(buf))) >= 0) {
process_data(nbytes, buf);
} else {
perror("读取出错了");
exit(EXIT_FAILURE);
}
}
}
}

首先,为了需要构造一个fd数组(这里为了简化,没有构造要监听写入和异常事件的fd数组) 。之后,用监听了中的多个的读取时间 。调用后,程序会Block住,直到一个事件发生了,或者等到最大1秒钟(tv定义了这个时间长度)就返回 。之后,需要遍历所有注册的fd,挨个检查哪个fd有事件到达(返回true) 。如果是,就说明数据已经到达了,可以读取fd了 。读取后就可以进行数据的处理 。
有一些发指的缺点:
poll
poll与类似于 。它大概长这样:
int poll(struct pollfd *fds, nfds_t nfds, int timeout);
poll的代码例子和差不多,因此也就不赘述了 。有意思的是poll这个单词的意思是“轮询”,所以很多中文资料都会提到对IO进行“轮询” 。
上面说的和下文说的epoll本质上都是轮询 。
poll优化了的一些问题 。比如不再有3个数组,而是1个结构的数组了,并且也不需要每次重设了 。数组的个数也没有了1024的限制 。但其他的问题依旧: