多路复用I/O--select

来源:互联网 发布:黑手党 知乎 编辑:程序博客网 时间:2024/05/20 17:23

多路复用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值;否则,返回0void 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# ./selectfifo buf = this is for testfifo buf = this is for testfifo buf = this is for testhellostdin buf = hellofifo buf = this is for testfifo buf = this is for test^C

启动write_fifo程序,如下:

/myblog/source/select# ./write_fifomkfifo error: File existsafter write to fifoafter write to fifoafter write to fifoafter write to fifoafter write to fifo^C

从程序运行结果可以看出,当write_fifo不断往命名管道写入”this is for test”时,select监听到命名管道fd已准备好,从fd上读出数据并打印到显示器上;当我们从标准输入中打出hello时,select监听到STDIN_FILENO(描述符为0)已准备好,从描述符0中读出数据并打印到显示器上。

原创粉丝点击