【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;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值