【Linux 應用編程】IO 多路轉接 - select 和 poll

Linux 中,read 和 write 函數默認實現的是阻塞式的 IO。例如:

while ((n = read(STDIN_FILENO, buf, BUFSIZ) > 0) {
	if (write(STDOUT_FILENO, buf, n) != n) {
		perror("write");
		eixt(EXIT_FAILURE);
	}
}

如果需要同時從多個描述符讀,則不能阻塞。

異步 IO(asynchronous IO)藉助內核監控描述符的狀態,當描述符可以進行 IO 時,內核會向進程發送一個信號。但是這種方式有兩個缺點:

  • 兼容性差,各個 UNIX 具體版本接口不一
  • 可用信號只有一個(SIGPOLL 或 SIGIO),最多隻能用於一個描述符

IO 多路轉接(IO multiplexing)使用時,需要先構造一張描述符列表,然後調用多路轉接函數並阻塞。當至少一個描述符準備好 IO 時,函數返回。

select 和 poll 對比

select 函數的缺點

  • 最大監聽描述符受限,最大1024(進程默認最大打開1024個文件,即使修改了也不影響 select)
  • 入參和返回參數同一個,每次調用都需要重新初始化入參
  • 內核和應用程序每次都需要順序掃描描述符,直到入參指定的最大描述符ID,低效

poll 函數比 select 有所改進

  • 通過 vim /etc/security/limits.conf 修改進程最大可打開的描述符限制後,可以擴大 poll 監聽的描述符數量(可以先用 cat /proc/sys/fs/file-max 查看硬件限制)
  • 入參跟返回參數獨立,不需要每次調用前都初始化

select 和 pselect 函數

#include <sys/select.h>

int select(int nfds, fd_set *readfds, fd_set *writefds,
           fd_set *exceptfds, struct timeval *timeout);

void FD_ZERO(fd_set *set); /* 清空位圖 */
void FD_SET(int fd, fd_set *set); /* 指定描述符在位圖中置位 */
void FD_CLR(int fd, fd_set *set); /* 清空位圖指定描述符對應的位 */
int  FD_ISSET(int fd, fd_set *set); /* 判斷指定描述符是否在位圖中置位 */

fd_set 實際是個位圖,每個位表示一個文件描述符。下面4個函數專門用來操作這個位圖。

傳入傳出參數:函數會直接修改傳入的參數。

參數:

  • nfds:最大的描述符序號加一。0/1/2 被標準輸入輸出和錯誤流佔用,用戶可用描述符序號從 3 開始。用於指示內核掃描列表的數據量。
  • readfds:傳入傳出參數。監聽哪些描述符的可讀事件,並返回哪些已經準備好了。
  • writefds:傳入傳出參數。監聽哪些描述符的可寫事件
  • exceptfds:傳入傳出參數。監聽哪些描述符的異常事件
  • timeout:超時時間,兩種格式:
 struct timeval {
    long    tv_sec;         /* seconds */
    long    tv_usec;        /* microseconds 微秒 */
};

struct timespec {
    long    tv_sec;         /* seconds */
    long    tv_nsec;        /* nanoseconds 毫秒 */
};

返回值:總的滿足條件的描述符個數,不同事件下的同一個描述符可以累加。例如監聽 fd1 的讀寫事件,這兩個事件同時滿足時返回值會加2。

#include <sys/select.h>

int pselect(int nfds, fd_set *readfds, fd_set *writefds,
            fd_set *exceptfds, const struct timespec *timeout,
            const sigset_t *sigmask);

select 代碼示例

下面創建10個管道,用select函數同時監控所有管道的讀端,並定時向寫端寫數據。設置了10s的超時時間,超時後select函數會返回0:

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/select.h>
#include <pthread.h>
#include <string.h>

int pipefds[10][2];

void *my_fun(void *args) {
	int i;
	char buf[10] = "hello wor";
	for (i = 0; i < 10; i++) {
		write(pipefds[i][1], buf, strlen(buf));
		sleep(1);
	}
	return (void*)0;
}

int main() {
	fd_set readfds;
	int i, ret;
	pthread_t tid;
	struct timeval tv;
	char buf[BUFSIZ];

	FD_ZERO(&readfds); // 清空 讀描述符集合
	tv.tv_sec = 10;
	tv.tv_usec = 0;

	if (pthread_create(&tid, NULL, my_fun, NULL) != 0) {
		perror("pthread_create");
		exit(EXIT_FAILURE);
	}
	while(1) {
		for (i = 0; i < 10; i++) {
			if (pipe(pipefds[i]) == -1) {
				perror("pipe");
				exit(EXIT_FAILURE);
			}
			/* 因爲select函數返回時會修改描述符集合,所以每次都需要初始化 */
			FD_SET(pipefds[i][0], &readfds);
		}
		ret = select(pipefds[9][1] + 1, &readfds, NULL, NULL, &tv);
		if (ret == 0) {
			printf("time out\n");
			return 0;
		}
		printf("ret of select is %d\n", ret);
		for (i = 0; i < 10; i++) {
			if (FD_ISSET(pipefds[i][0], &readfds)) {
				read(pipefds[i][0], buf, sizeof(buf));
				printf("seq is:%d, data is:%s\n", i, buf);
			}
		}
	}
	return 0;
}

poll 代碼示例

下面創建10個管道,用 poll 函數同時監控所有管道的讀端,並定時向寫端寫數據。設置了負數超時時間,所以進程會永遠等在這裏:

#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
#include <string.h>
#include <poll.h>
#include <pthread.h>

int pipefds[10][2];

void * func(void *args) {
	int i;
	char buf[20] = "hello world\n";
	for (i = 0; i < 10; i++) {
		write(pipefds[i][1], buf, strlen(buf));
		sleep(1);
	}
	return (void *)0;
}

int main() {
	struct pollfd pfds[10];
	int ret, i;
	char buf[BUFSIZ];
	pthread_t tid;
	for (i = 0; i < 10; i++) {
		ret = pipe(pipefds[i]);
		if (ret < 0) {
			perror("pipe");
			return -1;
		}
	}
	ret = pthread_create(&tid, NULL, func, NULL);
	if (ret < 0) {
		perror("pthread_create");
		return -1;
	}
	for (i = 0; i < 10; i++) {
		pfds[i].fd = pipefds[i][0];
		pfds[i].events = POLLIN;
	}
	for ( ; ; ) {
		ret = poll(pfds, 10, -1);
		if (ret < 0) {
			perror("poll");
			return -1;
		}
		for (i = 0; i < 10; i++) {
			if (pfds[i].revents & POLLIN) {
				read(pipefds[i][0], buf, sizeof(buf));
				puts(buf);
			}
		}
	}
	return 0;
}
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章