楔子
while ((n = read(STDIN_FILENO, buf, BUFSIZE)) > 0 )
if (write(STDOUT_FILENO, buf ,n) != n )
err_sys("write error")
上面这段代码很简单:从标准输入读入数据输出至标准输出。这里涉及两个描述符,这段代码的顺利完成要求两个描述符都准备好,只要其一没有准备好程序就无法进行下去,I/O复用就是用来处理这种问题的方法之一,涉及到的函数主要为: select poll & epoll。下面一一总结。
select函数
UNIX环境高级编程(第二版)声明如下:
返回:若有就绪的描述符则返回其数目,超时返回0,出错返回-1
#include<sys/select.h>
int select(int maxfdp1, fd_set *restrict readfds, fd_set *restrict writefds, fd_set *restrict exceptfds, struct timeval *restrict tvptr);
restrict是C99标准的关键字,表示只能通过着一个引用进行修改内存的值,用于编译器优化。
tvptr表示超时时间,结构体timeval声明为
struct timeval {
long tv_sec; /* 秒 */
long tv_usec; /* 微秒 */
};
这个参数有三种可能:
- NULL 表示永远等待,仅当至少有一个描述符准备好时才返回
- 0 表示不等待,查询描述符后立刻返回,即为轮询
- >0 表示等待指定的时间内,至少有一个描述符准备好时才返回,否则超时
中间的三个参数readset, writeset, exceptset表示让select去查询的读描述符集合,写描述符集合以及异常描述符集合。
如何给这个三个描述符集指定一个或多个描述符呢?select通常使用一个整型数组,每个整数中的每一位对应一个描述符
fd_set可以进行的操作为
void FD_ZERO(fd_set *fdset) // 将fdset置0
void FD_SET(int fd, fd_set *fdset) // 指定一个描述符
void FD_CLR(int fd, fd_set *fdset) // 清空一个描述符
void FD_ISSET(int fd, fd_set *fdset) // 判断一个藐视符是否准备好
maxfdp1为所有描述符中值最大的+1,表示select会检测的描述符范围
下面是一个简单的应用:共有两个进程,通过管道进行通讯,在子进程中select对管道进行查询,管道准备好就读数据,然后输出至标准输出
#include <sys/select.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <sys/termios.h>
#include <sys/ioctl.h>
#include <stdio.h>
#include <stdlib.h>
#include <stddef.h>
#include <string.h>
#include <unistd.h>
#include <signal.h>
#define MAXLINE 4096
int main(void)
{
int n;
int fd[2];
pid_t pid;
char line[MAXLINE];
if (pipe(fd) < 0)
printf("pipe error\n");
if ((pid = fork()) < 0) {
printf("fork error\n");
} else if (pid > 0) { /* parent */
sleep(3);
close(fd[0]);
write(fd[1], "hello world\n", 12);
} else { /* child */
struct timeval tv;
tv.tv_sec = 0;
tv.tv_usec = 0;
close(fd[1]);
fd_set readset;
while (1) {
FD_ZERO(&readset);
FD_SET(fd[0], &readset);
switch (select(fd[0]+1, &readset, NULL, NULL, &tv)>0) {
case -1 : printf("error\n");exit(-1);break;
case 0 : printf("wait\n");sleep(1);break;
default :
if (FD_ISSET(fd[0], &readset)) {
n = read(fd[0], line, MAXLINE);
write(STDOUT_FILENO, line, n);
return ;
}
break;
}
}
}
exit(1);
}
poll函数
UNIX环境高级编程(第二版)声明如下:
返回:若有就绪的描述符则返回其数目,超时返回0,出错返回-1
#include <poll.h>
int poll(struct pollfd fdarray[], nfds_t nfds, int timeout);
与select不用,poll不是为每个状态构造一个描述符集,而是构造一个polldf结构数组,每个数组元素指定一个描述符以及所关心的状态
struct pollfd{
int fd;
short events;
short revents;
};
fdarray数组长度由参数nfds进行说明,参数timeout与select的超时参数含义相同,单位为毫秒
events为描述符关心的状态,revents为poll查询后该描述符的状态,状态如下表
上面的例子通过poll修改后如下所示
#include <poll.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <sys/termios.h>
#include <sys/ioctl.h>
#include <stdio.h>
#include <stdlib.h>
#include <stddef.h>
#include <string.h>
#include <unistd.h>
#include <signal.h>
#define MAXLINE 4096
int main(void)
{
int n;
int fd[2];
pid_t pid;
char line[MAXLINE];
if (pipe(fd) < 0)
printf("pipe error\n");
if ((pid = fork()) < 0) {
printf("fork error\n");
} else if (pid > 0) { /* parent */
sleep(3);
close(fd[0]);
write(fd[1], "hello world\n", 12);
} else { /* child */
close(fd[1]);
struct pollfd fdarray[10];
fdarray[0].fd = fd[0];
fdarray[0].events = POLLRDNORM;
while (1) {
switch (poll(fdarray, 1, 0)>0) {
case -1 : printf("error\n");exit(-1);break;
case 0 : printf("wait\n");sleep(1);break;
default :
if ((fdarray[0].revents & POLLRDNORM) == POLLRDNORM) {
n = read(fdarray[0].fd, line, MAXLINE);
write(STDOUT_FILENO, line, n);
return ;
}
break;
}
}
}
exit(1);
}