1. 程式人生 > >多路複用I/O--select

多路複用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中讀出資料並列印到顯示器上。