linux - I/O复用:select

I/O多路复用:

在多个网络连接中,共用少数几个进程或线程。

支持IO多路复用的进程,需要先通知内核进行监控,等待内核监控到指定的一个或者多个I/O条件就绪(输入准备好被读取,或者文件描述符已经能承受更多的输出),它就通知进程。

5种I/O模型基本区别

输入操作包括两个阶段:

  1. 等待数据准备好
  2. 从内核向进程复制数据
    对于一个套接字上的输入操作,1. 等待数据从网络中到达。当所有等待分组到达时,它被复制到内核中的某个缓冲区。2. 将数据从内核缓冲区复制到应用进程缓冲区。
阻塞式I/O模型

阻塞式I/O模型(blocking I/O),默认情况下所有套接字都是阻塞的。

阻塞式IO模型

非阻塞式I/O模型

非阻塞式I/O模型(nonblocking I/O),进程将一个套接字设置成非阻塞是在通知内核,当所请求的I/O操作非得将本进程投入睡眠才能完成时,不要将本进程投入睡眠。

非阻塞式I/O模型
前三次调用recvfrom时没有数据返回,因此内核转而立即返回一个EWOULDBLOCK错误。第四次调用recvfrom时已有一个数据报准备好,它被复制到应用进程缓冲区,于是recvfrom成功返回。

轮询:一个应用进程对一个非阻塞描述符循环使用recvfrom。也就是,进程持续轮询内核,查看某个操作是否准备就绪。

I/O复用模型

I/O复用(I/O multiplexing),阻塞在系统调用select或者poll之上,而不是阻塞在真正的I/O系统调用之上。
I/O多路复用
阻塞select调用,等待数据报套接字变为可读。当select返回套接字可读时,调用recvfrom把所读数据报复制到应用进程缓冲区。

信号驱动式I/O模型

信号驱动式I/O(signal-driven I/O),让内核在描述符就绪时发送SIGIO信号通知给进程。

信号驱动式I/O模型
首先开启套接字的信号驱动式I/O功能,并且通过sigaction系统调用安装一个信号处理函数。此系统调用将立即返回,进程继续工作,没有阻塞情况发生。当数据报准备好读取时,内核就为进程产生一个SIGIO信号。随后,可以在信号处理中调用recvfrom读取数据报,并通知主循环数据已经准备好待处理,也可以立即通知主循环,让它读取数据报。

异步I/O模型

异步I/O(asynchronous I/O),告知内核启动某个操作,并让内核在整个操作(包括将数据从内核复制到自己的缓冲区)完成后通知进程。与信号模型的主要区别是:信号驱动式I/O是由内核通知何时启动一个I/O操作,异步I/O模型是由内核通知操作I/O何时完成。
异步I/O模型
调用aio_read函数,给内核传递描述符,缓冲区指针,缓冲区大小和文件偏移,并通知内核当整个操作完成时如何通知进程。该系统调用立即返回,而且等待I/O期间,进程不会被阻塞。

各种I/O模型比较

前4中模型的主要区别在等待数据的处理中,因为在将数据从内核复制到用户空间的过程是相同的。在数据从内核复制到调用者的缓冲区期间,进程将阻塞于recvfrom调用。

同步I/O和异步I/O对比
  1. 同步I/O操作(synchronous I/O operation)导致请求进程阻塞,直到I/O操作完成;
  2. 异步I/O操作(asynchronous I/O operation)不导致请求阻塞。
    I/O模型的比较

select函数

select函数允许进程指示内核等待多个事件中的任何一个发生,并只在有一个或者多个事件发生或者经历一段指定的时间后唤醒它。

select实际是通知内核,对某些描述符(读,写或者异常条件)感兴趣以及等待多长时间。描述符不仅限于socket,任何描述符select都可以使用

利用man 2 select查看select函数的具体应用。

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

timeout告知啮合等待所指定描述符中的任何一个就绪可花多长时间。其中,timveval结构用于指定这段时间的秒数和微秒数。

struct timeval{
	long tv_sec;
	long tv_usec;
}

3种可能情况:
2. 永远等待下去:仅在一个描述符准备好I/O时返回,为此,可以将参数设置为空指针。
3. 等待一段时间。
4. 根本不等待:检查描述符后立即返回,称为轮询(polling)

readsetwritesetexceptset

readsetwritesetexceptset分别为读,写,异常的描述符。??select使用描述符集,通常是一个整数数组,其中每个整数的每一位对应一个描述符。

具体地,使用细节为:

void FD_CLR(int fd, fd_set *set);
int  FD_ISSET(int fd, fd_set *set);
void FD_SET(int fd, fd_set *set);
void FD_ZERO(fd_set *set);

分配一个fd_set数据类型的描述符集,并可以利用宏设置和测试集合中的每一位。首先,需要初始化自动变量分配的一个描述符集合。

fd_set rset;
FD_ZERO(&rset);			// 初始化集合,每一位设置为off
FD_SET(1, &rset);		// turn on bit for fd1
FD_SET(4, &rset);		// turn on bit for fd4
FD_SET(5, &rset);		// turn on bit for fd5
maxfdp1

maxfdp1参数指定待测试的描述符个数,它的值是待测试的最大描述符加1。以上面1, 4和5为例,maxfdp1值就是6,是6而不是5的原因是:指定的是描述符的个数而不是最大值,描述符是从0开始的。

select函数修改由指针readset、writeset和exceptset所指向的描述符集合,所以这3个参数都是值-结果参数。调用此函数时,需要指定所关心的描述符的值,**函数返回时,将指示哪些描述符已经就绪。**该函数返回后,使用FD_ISSET宏来测试fd_set数据类型中的描述符。**描述符集合内任何与为就绪描述符对应的位返回时均置为0。**所以,每次重新调用select函数时,都得再次把所有描述符集合内所关心的位置为1。

描述符就绪条件

等待某个描述符准备好I/O:
对于普通文件的即是读写。
对于socket的“就绪”:
select返回某个套接字就绪的条件小结

select的最大文件描述符

str_cli函数

str_cli函数中select处理的各种条件

客户端的套接字上的三个处理条件:

  1. 对端TCP 发送数据,那么套接字变为可读,并且read返回一个大于0的值(即读入数据的字节数)。
  2. 对端TCP 发送一个FIN(对端进程终止),那么套接字变为可读,并且read返回0(EOF)。
  3. 对端TCP 发送 一个RST (对端主机奔溃并重新启动),该套接字变为可读,并且read返回-1,而errno中含有确切的错误码。

str_cli函数摘录

#include "unp.h"

void str_cli(FILE *fd, int sockfd){
	int maxfdp1;
	fd_set rset;
	char sendline[MAXLINE],	 recvline[MAXLINE];

	/* 检查可读性的描述符集合。*/
	FD_ZERO(&rset);									// 描述符集合初始化
	for( ; ; ) {
		FD_SET(fileno(fp), &rset);					// 打开标准I/O文件指针fp
		FD_SET(sockfd, &rset);						// 打开套接字sockfd
		maxfd1 = max(fileno(fd), sockfd) + 1;		// fileno将fd转换为文件描述符的值
		select(maxfd1, &rset, NULL, NULL, NULL);	// 调用select阻塞,直到某个文件描述符就绪	
		/ *处理可读的套接字* /
		if (FD_ISSET(sockfd, &rset)) {				// 如果socket可读
			if (Readline(sockfd, recvline, MAXLINE) == 0)	// readline读入回射文本行
				err_quit("str_cli: server terminated prematurely");
			Fputs(recvline, stdout);				// fputs输出
		}
		/* 处理标准读入 */
		// 如果标准读入可读, 将先用fgets读入一行文本, 再用writen将它写入到套接字中。
		if (FD_ISSET(fileno(fd), &rset)) {			/* input is readable */
			if (Fgets(sendline, MAXLINE, fd) == NULL )
				return;								/* all done */
			Writen(sockfd, sendline, strlen(sendline));
		}
	}
}

此处的代码是利用fgetswritenreadlinefputs。它们在本函数中的驱动流发生了变化,新版本的是由select来调用驱动,而之前的是由fgets调用来驱动。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值