UNIX网络编程6_IO复用

概述:I/O复用技术

I/O多路复用是指内核一旦发现进程指定的一个或者多个I/O条件准备就绪,它就通知该进程。I/O复用适用于以下场合:
当客户处理多个描述符(一般是交互式输入或网络套接字),必须适用I/O复用(套接字是一个抽象出来的概念,本质上也是一个文件描述符)
当一个客户处理多个套接字时,这种情况很少见,但也可能出现
当一个TCP服务器既要处理监听套接字,又要处理已连接套接字,一般就要使用I/O复用
如果一个服务器既要适用TCP,又要适用UDP,一般就要使用I/O复用
如果一个服务器要处理多个服务或者多个协议,一般就要使用I/O复用
与多线程和多进程技术相比,I/O复用技术的最大优势就是系统开销小,系统不必创建进程/线程,也不必维护这些进程/进程,从而大大减小了系统的开销。

I/O模型

Unix下常见的I/O模型有五种,分别是:阻塞式I/O,非阻塞式I/O,I/O复用,信号驱动式I/O和异步I/O。
Unix下对于一个输入操作,通常包含两个不同的阶段
1、等待数据准备好
2、从内核向进程复制数据
例如:对于一次read函数操作来说,数据先会被拷贝到操作系统内核的缓冲区去,然后才会从操作系统内核的缓冲区拷贝到应用程序的地址空间。
再比如对于一次socket流传输来说,首先等待网络上的数据到达,然后复制到内核的某个缓冲区,然后再把内核缓冲区的数据复制到进程缓冲区。

阻塞式I/O

默认情形下,所使用的套接字模型都是阻塞式I/O模型。
阻塞式I/O模型
图中示例是以UDP为例子,UDP较为简单,要么数据报到了,要么没有。TCP则需要额外考虑一些其他的变量,关注数据报是否完全到了等,比较复杂。

该例子中,recvfrom为系统调用。进程调用recvfrom,其系统调用直到数据报到达且被复制到应用进程的缓冲区才返回成功(遇到错误返回错误)。 进程从调用recvfrom到返回指示这段时间内处于阻塞状态,该种IO方式称为阻塞式IO模型。

优点:能够及时获得数据,没有延迟,
缺点:对用户来说,这段时间一直要处于等到状态,不能去做其他的事情,在性能方面付出了代价。

非阻塞时IO模型

非阻塞式I/O模型
进程把套接字设置成非阻塞是在通知内核:当所请求的I/O操作需要把进程投入阻塞时,不要处理,而是返回一个错误。
如图所示,进程前三次调用recvfrom时,数据报没有准备好,内核返回EWOULDBLOCK的错误,直到准备好数据报,才返回成功指示。

优点在于:应用进程不必阻塞在recvfrom调用中,而是可以去处理其他事情
缺点在于:在网络模型中即可以表现在任务完成的响应延迟增大了,隔一段时间轮询一次recvform,数据报可能在两次轮询之间的任意时间内准备好,这将会导致整体数据吞吐量的降低。

I/O复用模型

有了I/O复用模型,可以调用select 和 poll,阻塞在这两个系统调用中的某一个之上,而不是阻塞在真正的I/O系统调用上。

I/O复用模型

如上图所示,进程受阻于select调用,等待可能多个套接字中的任一个变为可读。当select返回套接字可读这一条件时,应用进程就调用recvfrom把所读的数据报复制到应用进程缓冲区。
进程阻塞在select,如果进程还有其他的任务的话就能体现到I/O复用技术的好处,那个任务先返回可读条件,就去执行哪个任务。从单一的等待变成多个任务的同时等待。
这种模型较之前的模型来说,可以不必多次轮询内核,而是等到内核的通知。

信号驱动式I/O 模型

内核在就绪后发送sigio信号通知程序,称为信号驱动式的I/O模型。

信号驱动式I/O 模型

如上图所示,进程建立SIGIO的信号处理程序,并通过sigaction系统调用安装一个信号处理函数,该系统调用将立即返回,进程继续工作,知道数据报准备好后,内核产生一个SIGIO信号,告知应用进程以及准备好,于是就在信号处理程序中调用recvfrom读取数据报,并通知主循环数据已准备好待处理,也可以立即通知主循环让他读取数据报。
这种模型的好处就是,在数据报没有准备好的期间,应用进程不必阻塞,继续执行主循环,只要等待来自信号处理函数的通知即可。

异步I/O模型

告知内核启动某个操作,并让内核在整个操作(包括将数据从内核复制到我们自己的缓冲区)完成后通知我们。这种模型与前一节介绍的信号驱动模型的主要区别在于:信号驱动I/O是由内核通知我们如何启动一个I/O操作,而异步I/O模型是由内核通知我们I/O操作何时完成。

异步I/O 模型
不同于信号驱动式I/O模型,信号是在数据已复制到进程缓冲区才产生的。
各种I/O模型的比较 以一张图来说明五种I/O操作的差异:

I/O 模型比较

同步I/O操作:导致请求进程阻塞,直到I/O操作完成
异步I/O操作:不导致进程阻塞

可知,前四种都属于同步I/O操作慢系统都会阻塞与recvfrom操作,而异步I/O不会。

select 函数

select函数用于I/O复用,该函数允许进程指示内核等待多个事件中的任何一个发生,并只在有一个或多个事件发生或经历一段指定的事件才唤醒它。
即,我们调用select函数告知内核对哪些描述符感兴趣及等待多长时间,

#include <sys/select.h>
#include <sys/time.h>
int select(int maxfdp1, fd_set *readset, fd_set *writeset , fd_set *exceptset , const struct timeval *timeout); 
//若有就绪描述符则返回其数目,超时则为0,若出错则为-1

对于timeout参数:
(1) timeout==NULL,表示要永远等待下去,直到有一个描述符准备好I/O时才返回
(2) *timeout的值为0,表示不等待,检查描述符就立即返回,这称为轮询。
(2) *timeout的值不为0,表示等待一段固定的时间,再有一个描述符准备好I/O时返回,但是不能超过由该参数制定的时间。
对于readset,writeset和exceptset三个参数:
这三个描述符说明了可读,可写和处于异常条件的描述符集合
对于描述集fd_set结构,提供了如下四个操作函数

#include <sys/select.h>
int FD_ISSET(int fd,fd_set *fdset); //设定描述集中的某个描述符
void FD_CLR(int fd,fd_set *fdset);//关掉描述集中的某个描述符
void FD_SET(int fd,fd_set *fdset);//打开描述集中的某个描述符
void FD_ZERO(fd_set *fdset);//清除集合内所有元素

对于maxfdp1参数:
指定待测试的描述符个数,它的值时待测试的最大描述符编号加1,即从上面三个描述符集中的最大描述符编号加1。
对于返回值:
select返回值有三种情况:
(1) 返回值为-1时,表示出错,如果在指定的描述符一个都没有准备好时捕捉一个信号,则返回-1
2) 返回0,表示没有描述符准备好,指定的时间就超过了
(3) 返回正数,表示已经准备好的描述符个数,在这种情况下,三个描述符集中依旧打开的位对应于已准备好的描述符

使用select函数修改的str_cli函数

#include	"unp.h"
void
str_cli(FILE *fp, int sockfd)
{
	int			maxfdp1;
	fd_set		rset;
	char		sendline[MAXLINE], recvline[MAXLINE];
	FD_ZERO(&rset);
	for ( ; ; ) {
		FD_SET(fileno(fp), &rset);//标准输入描述符
		FD_SET(sockfd, &rset);//socket描述符
		maxfdp1 = max(fileno(fp), sockfd) + 1;//最大描述符编号+1
		Select(maxfdp1, &rset, NULL, NULL, NULL);//调用select,阻塞于此
  //如果返回的套接字可读,就用readline读入回射文本
		if (FD_ISSET(sockfd, &rset)) {	/* socket is readable */
			if (Readline(sockfd, recvline, MAXLINE) == 0)
				err_quit("str_cli: server terminated prematurely");
			Fputs(recvline, stdout);
		}
  //如果标准输入可读,就先用fgets读入一行文本
		if (FD_ISSET(fileno(fp), &rset)) {  /* input is readable */
			if (Fgets(sendline, MAXLINE, fp) == NULL)
				return;		/* all done */
			Writen(sockfd, sendline, strlen(sendline));
		}
	}
}

批量输入

在上节的程序中,存在问题,假设客户在标准输入中批量输入数据,在输入完最后一个数据后,碰到了EOF,str_cli返回到main函数,main函数随后终止。但是,在这个过程中,标准输入的EOF终止符并不意味着我们也同时完成了从套接字的读入,可能仍有请求在去往服务器的路上,或者仍有应答在返回客户的路上。
原因为

if (Fgets(sendline, MAXLINE, fp) == NULL)
				return;		/* all done */

当碰到EOF终止符的时候,str_cli函数选择了立即返回,而此时,我们更需要的是找到一个条件来判断套接字的读取是否完成。
可使用shutdown函数解决问题,

poll 函数

poll函数的功能与select相似,不过在处理流设备时,它能够提供额外的信息。

#include <poll.h>
int poll(struct pollfd *fdarray,nfds_t nfds,int timeout);//若有就绪描述符就返回其数目,如超时则返回0,若出错就返回-1

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

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值