Linux网络编程之多路复用I/O

1 输入输出I/O模型

  在UNIX/Linux下主要有4种I/O 模型

1.1 阻塞I/O

1.1.1阻塞I/O概述

  阻塞I/O 模式是最普遍使用的I/O 模式,大部分程序使用的都是阻塞模式的I/O ,其最常用。
  缺省情况下(默认情况下),套接字Socket() 建立后所处于的模式就是阻塞I/O 模式。前面学习的很多读写函数都是阻塞I/O,如下:
1、读操作中的read、recv、recvfrom;
2、写操作中的write、send;
3、其他操作:accept、connect;
注意:UDP下的sendto () 不是阻塞I/O;

1.1.2 读阻塞

  以read函数为例:
1、进程调用read函数从套接字上读取数据,当套接字的接收缓冲区中还没有数据可读,函数read将发生阻塞。
2、它会一直阻塞下去,等待套接字的接收缓冲区中有数据可读。
3、经过一段时间后,缓冲区内接收到数据,于是内核便去唤醒该进程,通过read访问这些数据。
4、如果在进程阻塞过程中,对方发生故障,那这个进程将永远阻塞下去。

1.1.3 写阻塞

1、在写操作时发生阻塞的情况要比读操作少。主要发生在要写入的缓冲区的大小小于要写入的数据量的情况下。
2、这时,写操作不进行任何拷贝工作,将发生阻塞。
3、一旦发送缓冲区内有足够的空间,内核将唤醒进程,将数据从用户缓冲区中拷贝到相应的发送数据缓冲区。
4、UDP不用等待确认,没有实际的发送缓冲区,所以UDP协议中不存在发送缓冲区满的情况,在UDP套接字上执行的写操作永远都不会阻塞。

1.2 非阻塞I/O

1.2.1 非阻塞I/O概述

1、非阻塞I/O概述
  当我们将一个套接字Socket设置为非阻塞模式,我们相当于告诉了系统内核:“当我请求的I/O 操作不能够马上完成,你想让我的进程进行休眠等待的时候,不要这么做,请马上返回一个错误给我。”
2、非阻塞I/O概述需要轮询
  当一个应用程序使用了非阻塞模式的套接字,它需要使用一个循环来不停地测试是否一个文件描述符有数据可读(称做polling)。
  应用程序不停的polling 内核来检查是否I/O操作已经就绪。这将是一个极浪费CPU 资源的操作。
3、使用情况
  可防止进程阻塞在I/O操作。这种模式使用中不普遍。

1.2.2 非阻塞模式的实现(函数)

  可以使用函数将一个I/O由阻塞模式修改为非阻塞模式。以下以套接字为例:当你一开始建立一个套接字描述符的时候,系统内核将其设置为阻塞IO模式,通过以下两个函数都可以将此套接字修改为非阻塞I/O模式。

1.使用fcntl( )函数修改的代码:
函数原型:
int fcntl(int fd, int cmd, long arg);
代码:
int flag;
flag = fcntl(sockfd, F_GETFL, 0);
flag |= O_NONBLOCK;
fcntl(sockfd, F_SETFL, flag);

2.使用ioctl() 函数修改的代码
代码:
int b_on =1;
ioctl(sock_fd, FIONBIO, &b_on);

1.3 信号驱动I/O

1.3.1 信号驱动I/O特点

  信号驱动I/O,属于异步通信模型;而阻塞I/O、非阻塞I/O和多路复用I/O ,属于同步通信模型。

1.3.2 信号驱动I/O应用场景

  首先,在相应驱动程序里打开“信号驱动I/O开关”。当I/O中有事件发生时,在相应应用程序中就会收到一个SIGIO信号,最后应用程序就能根据SIGIO信号做些事情,比如调用事先写好的函数。
   信号驱动I/O一般用在底层驱动和上层应用程序关联互动的情况下。

1.4 多路复用I/O (本章学习重点)

  允许同时对多个I/O进行控制

1.4.1 为什么要引出多路复用I/O

  一、应用程序中同时处理多路输入输出流:
1、若采用阻塞模式,将得不到预期的目的;
2、若采用非阻塞模式,对多个输入进行轮询,但又太浪费CPU时间;
3、若设置多个进程,分别处理一条数据通路,将新产生进程间的同步与通信问题,使程序变得更加复杂。

  二、比较好的方法是使用I/O多路复用,其基本思想是:
1、先构造一张有关描述符的表;
2、然后调用一个函数select();
3、当这些文件描述符中的一个或多个已准备好进行I/O时,函数select()才返回;
4、函数select()返回后,告诉进程那个描述符已就绪可以进行I/O操作。

1.4.2 多路复用I/O应用对象

  多路复用I/O不仅适用于Linux网络编程I/O(如套接字Socket),也使用与普通文件I/O。

1.4.3 文件描述符与多路复用I/O

1、文件描述符概述
  Linux中每个进程默认情况下最多可打开1024个文件,最多有1024个文件描述符。
2、文件描述符特点
A、非负整数,从0开始;
B、分配时,从最小可用分数字分配;
C、每个进程启动时默认打开0、1、2三个文件描述符,分别代表标准输入、标准输出和标准错误。
3、多路复用I/O与文件描述符特点
  多路复用I/O针对不止网络套接字文件描述符fd,也针对普通文件的文件描述符fd。

2 多路复用I/O 实现框架

2.1 多路复用I/O 实现框架

1、把关心文件描述符fd加入到文件描述符集合中fdset(就是非负整数数组)。
2、调用select () / poll () 函数去监控集合那些文件描述符。此时select () / poll () 函数进入阻塞等待模式。
3、直到集合fdset中有一个或多个文件描述符有数据(即文件I/O有数据),select () / poll () 函数退出阻塞。
4、依次判定集合fdset那个文件描述符有数据。
5、依次处理有数据的文件描述符的数据。

2.2 框架示类(伪代码)

在这里插入图片描述

2.2 框架关键点

在这里插入图片描述
关键点
一、如上图,select( )函数里面的各个文件描述符fdset集合文件描述符在select( )函数调用前后发生了变化,具体如下:
1、 select( )函数调用前:fdset内是关心的文件描述符的集合
2、select( )函数调用后:fdset内是有I/O数据的文件描述符的集合(前提是select( )函数不是因为超时或出错返回)。

二、那么究竟是谁动了fdset集合的“奶酪”?
答曰:Linux操作系统内核kernel

三、思考:这种模式下,多路网络连接时候能否真正多路并发处理?如果能,请说明理由,如不能,请给出改进意见

3 多路复用 I/O 相关函数

3.1 多路复用 I/O 监控函数 select ()

一、select ()函数原型
#include <sys/time.h>
#include <sys/types.h>
#include <unistd.h>
int select(int maxfd, fd_set *read_fds, fd_set *write_fds, fd_set *except_fds, struct timeval *timeout);
二、select ()函数参数
1、maxfd:所有监控的文件描述符中最大的那一个加1;
2、read_fds:所有要读的文件文件描述符的集合 ;
3、write_fds:所有要的写文件文件描述符的集合,一般取NULL ;
4、except_fds:其他要向我们通知的文件描述符,一般取NULL ;
5、timeout:超时设置:
  A、Null:一直阻塞,直到有文件描述符就绪或出错
  B、时间值为0:仅仅检测文件描述符集的状态,然后立即返回
  C、时间值不为0:在指定时间内,如果没有事件发生,则超时返回。
三、select ()函数结果
  在我们调用select () 函数时进程会一直阻塞,直到以下的一种情况发生:
1、有文件可以读;
2、有文件可以写;
3、超时所设置的时间到。
四、select ()函数返回值
1、如有n个文件(文件描述符)有数据,返回n;
2、超时,返回0;
3、错误为,返回-1。

3.2 select ()函数参数timeout 的类型 struct timeval

struct timeval
{
long tv_sec; //秒
long tv_usec; //毫秒
}

3.3 文件描述符集合操作函数

1、文件描述符集合fdset中清除所有的文件描述符 ;
void FD_ZERO(fd_set *fdset)

2、将单个文件(文件描述符)fd加入到文件描述符集合fdset中;
void FD_SET(int fd,fd_set *fdset)

3、将单个文件(文件描述符)fd从文件描述符集合fdset中清除;
void FD_CLR(int fd,fd_set *fdset)

4、判断单个文件(文件描述符)fd是否在文件描述符集合fdset中;
int FD_ISSET(int fd,fd_set *fdset)

3.4多路复用 I/O 其他监控函数

3.4.1 pselect () 函数

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

二、参数
参数类似struct timespec 和sigset_t 。

三、作用
select()的增强型。

3.4.2 poll () 函数

一、原型
int poll(struct pollfd *fds, nfds_t nfds, int timeout);

二、参数
fds类型:
struct pollfd {
int fd; /* file descriptor /
short events; /
requested events /
short revents; /
returned events */
};

三、作用
类似select() ,稍节省空间。

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值