多路复用I/O--select

多路复用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中读出数据并打印到显示器上。

猜你喜欢

转载自blog.csdn.net/teffi/article/details/76563313