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