多路复用I/O–select
select定义
#include <sys/select.h>
int select(int maxfdp1, fd_set *restrict readfds, fd_set *restrict writefds, fd_set *restrict exceptfds, struct timeval *restrict tvptr);
返回值:准备就绪的描述符数目:若超时,返回0;若出错,返回-1
参数说明
(1)先来说明最后一个参数,它指定愿意等待的时间长度,单位为秒和微秒。有以下3种情况。
tvptr == NULL
永远等待。如果捕捉到一个信号则中断此无限期等待。当所指定的描述符中的一个已准备好或捕捉到一个信号则返回。如果捕捉到一个信号,则select返回-1,errno设置为EINTR。
tvptr->tv_sec != 0 && tvptr->tv_usec == 0
根本不等待。测试所有指定的描述符并立即返回。
tvptr->tv_sec != 0 || tvptr->tv_usec != 0
等待指定的秒数和微秒数。当指定的描述符之一已准备好,或当指定的时间值已经超过时立即返回。如果在超时到期时还没有一个描述符准备好,则返回值是0。
(2)中间3个参数readfds、writefds和exceptfds是指向描述符集的指针。这3个描述符集说明了我们关心的可读、可写或处于异常条件的描述符集合。每个描述符集存储在一个fd_set数据类型中。
对于fd_set数据类型,唯一可以进行的处理是:分配一个这种类型的变量,将这种类型的一个变量值赋给同类型的另一个变量,或对这种类型的变量使用下列4个函数中的一个。
#include <sys/select.h>
int FD_ISSET(int fd, fd_set *fdset);
返回值:若fd在描述符集中,返回非0值;否则,返回0
void FD_CLR(int fd, fd_set *fdset);
void FD_SET(int fd, fd_set *fdset);
void FD_ZERO(fd_set *fdset);
- 调用FD_ZERO将一个fd_set变量的所有位设置为0.
- 要开启描述符集中的一位,可以调用FD_SET。
- 调用FD_CLR可以清除一位。
- 最后,可以调用FD_ISSET测试描述符集中的一个指定位是否已打开。
从select返回时,可以用FD_ISSET测试该集中的一个给定位是否仍处于打开状态,如下:
if (FD_ISSET(fd, &fdset)) {
……
}
select的中间3个参数中的任意一个(或全部)可以是空指针,此时select提供了比sleep更精确的定时器。
(3)select的第一个参数maxfdp1的意思是”最大的文件描述符编号值加1“。考虑所有3个描述符集,在3个描述符集中找出最大描述符编号值,然后加1,这就是第一个参数值。也可将maxfdp1设置为FD_SETSIZE,即系统中定义的最大描述符数(经常是1024)。
返回值
select有3个可能的返回值:
- 返回值-1表示出错。
- 返回值0表示没有描述符准备好。若指定的描述符一个都没指定好,指定的时间就过了,此时所有描述符集都会置0。
- 一个正返回值说明了已经准备好的描述符数。该值是3个描述符集中已准备好的描述符数之和,所以如果同一描述符已准备好读和写,那么在返回值中会对其计两次数。
实例讲解
下面已一个例子来演示select的用法。
我们有两个程序,select.c和write_fifo.c。
select.c中,我们循环监听了两个描述符,STDIN_FILENO标准输入描述符和命名管道fd。不同的fd上有数据反馈,在显示器上会打印不同的输出。
write_fifo.c中,每隔5秒向命名管道fd写入”this is for test”。
/**
* select.c
* 多路复用select的用法
*/
#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
#include <fcntl.h>
#define fifo_filename "test_fifo"
int main(int argc, char *argv[])
{
fd_set rfds;
struct timeval tv;
int ret;
int fd;
ret = mkfifo(fifo_filename, 0666);
if (ret != 0)
perror("mkfifo error");
fd = open(fifo_filename, O_RDWR);
if (fd < 0) {
perror("open error");
exit(-1);
}
ret = 0;
while (1) {
FD_ZERO(&rfds);
FD_SET(0, &rfds);
FD_SET(fd, &rfds);
tv.tv_sec = 1;
tv.tv_usec = 0;
ret = select(FD_SETSIZE, &rfds, NULL, NULL, NULL);
if (ret == -1) {
perror("select error");
} else if (ret > 0) {
char buf[100] = {0};
if (FD_ISSET(0, &rfds)) {
read(0, buf, sizeof(buf));
printf("stdin buf = %s\n", buf);
} else if (FD_ISSET(fd, &rfds)) {
read(fd, buf, sizeof(buf));
printf("fifo buf = %s\n", buf);
}
} else if (ret == 0) {
printf("time out\n");
}
}
exit(0);
}
/**
* write_fifo.c
* 给命名管道发送信息
*/
#include <stdio.h>
#include <unistd.h>
#include <string.h>
#include <stdlib.h>
#include <fcntl.h>
#define fifo_filename "test_fifo"
int main(int argc, char *argv[])
{
int ret = 0;
int fd;
ret = mkfifo(fifo_filename, 0666);
if (ret != 0) {
perror("mkfifo error");
}
fd = open(fifo_filename, O_RDWR);
if (fd < 0) {
perror("open error");
exit(-1);
}
while (1) {
char *str = "this is for test";
write(fd, str, strlen(str));
printf("after write to fifo\n");
sleep(5);
}
exit(0);
}
编译两个程序
/myblog/myblog/source/select# gcc select.c -o select
/myblog/source/select# gcc write_fifo.c -o write_fifo
启动select程序,如下:
/myblog/source/select# ./select
fifo buf = this is for test
fifo buf = this is for test
fifo buf = this is for test
hello
stdin buf = hello
fifo buf = this is for test
fifo buf = this is for test
^C
启动write_fifo程序,如下:
/myblog/source/select# ./write_fifo
mkfifo error: File exists
after write to fifo
after write to fifo
after write to fifo
after write to fifo
after write to fifo
^C
从程序运行结果可以看出,当write_fifo不断往命名管道写入”this is for test”时,select监听到命名管道fd已准备好,从fd上读出数据并打印到显示器上;当我们从标准输入中打出hello时,select监听到STDIN_FILENO(描述符为0)已准备好,从描述符0中读出数据并打印到显示器上。