概述
如果有多个IO需要处理
当一个描述符读,然后又写到另一个描述符时,可以用循环的方式访问阻塞io:
#include <stdio.h>
#include <unistd.h>
#define BUF_SIZE 128
int main(){
char buf[BUF_SIZE];
int n;
while ( n = read(STDIN_FILENO, buf, BUF_SIZE) > 0) {
if (write(STDOUT_FILENO, buf, n) != n){
fprintf(stderr, "%sn", "write error!");
break;
}
}
return 0;
}
但是如果要从两个fd读的时候,就不能用阻塞io去读了。因为进程阻塞在某个fd的时候,另一个fd即使输入了数据也无法处理。
比如一个telnet程序的输出有两个来源,用户输入回显和远端回包。如果阻塞在等待远端回包,用户输入就不会有回显了。解决这个问题就几种思路:
-
再fork一个进程,每个进程处理一个fd。这看起来很好,但是处理EOF却成了问题。如果子进程先读到EOF,那么子进程终止,返回SIGCHLD给父进程,然后父进程终止。如果父进程读到EOF,父进程就需要通知子进程结束,此时需要额外的信号(如SIGUSER1).
-
用两个线程来处理,每个线程一个fd。同样的,处理线程之间的同步也会变的比较复杂。
-
用非阻塞的io来轮询。先read一个fd,如果没数据立即返回,然后等待若干时间,然后再read下一个fd,直到某个fd有数据读为止。这个方法有两个缺点,一是频繁的read调用浪费了cpu时间,但是大部分时间是没数据读的。二是每次read返回后等待的时间不好确定,太久会读取不及时,太短会使得cpu更加繁忙。
-
使用异步IO。当fd归属的设备准备好的时候,用信号通知处理进程。这个方法有两个缺点,一是信号在不同的系统上实现不同,移植性较差。二是进程收到的信号只有一种(SIGPOLL或者SIGIO),进程无法分辨是哪个fd。
有没有比较完善的方案?io多路复用来了。这种方案会记录一个我们需要的fd的列表,然后我们去查询,当这个列表中有fd有数据时,查询就会返回这个fd。
IO多路复用:select
int select(int nfds, fd_set *readfds, fd_set *writefds, fd_set *exceptfds, struct timeval *timeout);
select 函数提供了查询fd状态的能力。我们传入希望监听的fd,内核告诉我们哪些fd已经有事件发生并返回。
-
参数 timeout 控制愿意等待的时间。timeval为NULL时,select会一直阻塞等待某个fd准备好。(如果接收到信号,select也会提前返回-1,并把errno设为INTR)
-
参数 readfds, writefds, exceptfds 是我们告诉内核希望监听的fd的指针。如果希望监听某fd的读事件,就加入到readfds中。后面两个用于监听写事件和异常事件。
-
返回时,readfds, writefds, exceptfds 中会留下对应事件已经就绪的fd。此时这些fd是可读的、可写的或发生异常的。
-
参数 nfds 是三个fd集合中的最大值加一,它制定了fd的遍历范围。也可以把它设为FD_SETSIZE,一般为1024,但是这样会导致select遍历系统中所有的fd。
-
返回值:a)如果没有就绪的fd,函数返回0。b)如果有就绪的fd,函数返回就绪的fd数量。c)如果收到信号,返回-1,并把errno设为INTR d)特别地,如果fd到达EOF,函数调用read,然后返回0
-
函数pselect采用了timespec类型的超时设置。此外,还可以设置sigmask用来屏蔽一些信号,防止自己被终止。
fd_set 是一个fd的集合。在实现上是一个大bit数组,它只支持赋值操作和下面宏定义的操作:
// 如果fd在fd_set中返回0
int FD_ISSET(int fd, fd_set *set);
// 初始化fd_set
void FD_ZERO(fd_set *set);
// 设置某个fd
void FD_SET(int fd, fd_set *set);
// 清除某个fd
void FD_CLR(int fd, fd_set *set);
下面给了一个select的例子。他会等待输入五秒钟,如果有输入会立马回显。如果超时,则会直接退出。
#include <stdio.h>
#include <sys/time.h>
#include <sys/types.h>
#include <unistd.h>
#define TIMEOUT 5 /* select timeout in seconds */
#define BUF_LEN 1024 /* read buffer in bytes */
int main( void )
{
struct timeval tv;
fd_set readfds;
int ret;
/* Wait on stdin for input. */
FD_ZERO( &readfds );
FD_SET( STDIN_FILENO, &readfds );
/* Wait up to five seconds. */
tv.tv_sec = TIMEOUT;
tv.tv_usec = 0;
/* All right, now block! */
ret = select( STDIN_FILENO + 1,
&readfds,
NULL,
NULL,
&tv );
if ( ret == -1 )
{
perror("select");
return(1);
} else if ( !ret )
{
printf("%d seconds elapsed.n", TIMEOUT );
return(0);
}
/*
* * Is our file descriptor ready to read?
* * (It must be, as it was the only fd that
* * we provided and the call returned
* * nonzero, but we will humor ourselves.)
* */
if ( FD_ISSET( STDIN_FILENO, &readfds ) )
{
char buf[BUF_LEN + 1];
int len;
/* guaranteed to not block */
len = read( STDIN_FILENO, buf, BUF_LEN );
if ( len == -1 )
{
perror("read");
return(1);
}
if ( len )
{
buf[len] ='