I/O复用select函数

来源:互联网 发布:淘宝赛罗奥特曼公仔 编辑:程序博客网 时间:2024/05/21 09:18

《UNIX网络编程第六章笔记》

1、liunx下可用的5种I/O模型:阻塞式I/O、非阻塞式I/O、I/O复用、信号驱动I/O、异步I/O

1.1、阻塞式I/O

默认情形下,所有套接字都是阻塞的,以数据报套接字为例,如图所示:


1.2、非阻塞式I/O

当一个应用进程像这样对一个非阻塞描述符循环调用recvfrom时,称之为轮询。应用进程持续轮询内核,以查看某个操作是否就绪。这么做往往消耗大量CPU时间,不过这种模型偶尔也会遇到,通常在专门提供某一种功能的系统中才有。


1.3、I/O复用模型图

使用select需要使用2个而不是单个系统调用,I/O复用还稍有劣势。不过select的优势在于可以等待多个描述符就绪,且可以继续超时等待操作。


1.4、信号驱动式I/O模型

这种模型的优势在于等待数据包到达期间进程不被阻塞,主循环可以继续执行,只要等待来自信号处理函数的通知;既可以是数据已准备好被处理,也可以是数据包已准备好被读取。


1.5、异步I/O模型

其与信号驱动I/O的区别是信号驱动I/O是内核通知我们何时可以启动一个I/O操作,而异步I/O模型是由内核通知我们I/O操作何时完成。


1.6、5种I/O模型的比较如图


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

异步I/O操作:不导致请求进程阻塞。



2、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);

参数timeout用于指定等待时间的秒数和微妙数

struct timeval {<span style="white-space:pre"></span>long tv_sec;          // seconds<span style="white-space:pre"></span>long tv_usec;       // microseconds}
这个参数有以下三种可能:

1)永远等待,仅在有一个描述符准备好I/O才返回,将其设置为空指针

2)等待一段固定时间,在有一个描述符准备好I/O时返回,但不超过该参数指定的秒数和微妙数

3)根本不等待:检测描述符后立即返回,成为轮询,将参数置0

从可移植性考虑,需做好select返回EINTR错误的准备。

另外linux系统会修改timeout结构体的值,返回剩余等待的时间。但其他操作系统可能并不这样实现,从可移植性考虑,应考虑select返回的timeout结构体值是未定义的。

参数readset、writeset和exceptset指定我们要让内核测试读、写和异常条件的描述符

select使用描述符集,通常是一个整数数组,其中每个整数中的每一位对应一个描述符



相关的实现细节与应用程序无关,他们隐藏在名为fd_set的数据类型和以下四个宏中:

void FD_ZERO(fd_set *fdset);         // clear all bits in fdsetvoid FD_SET(int fd, fd_set *fdset);                  // turn on the bit for fd in fdsetvoid FD_CLR(int fd, fd_set *fdset);                 // trun off the bit for fd in fdsetvoid FD_ISSET(int fd, fd_set *fdset);              // is the bit for fd on in fdset

举例:

fd_set rset;FD_ZERO(&rset);                 // initialize the set, all bits off; 如果没有初始化,发生不可预期的后果FD_SET(1, &rset);// turn on bit for fd 1FD_SET(4, &rset);// turn on bit for fd 4

这三个参数如对某个条件不感兴趣,可置空指针,如果都置空,就有了一个比sleep函数更为精确的定时器

这三个参数是值-结果参数,函数返回时,结果指示哪些描述符已就绪,未就绪的描述符对应的位在返回时均清0;为此,每次重新调用select时,都得再次把所有描述符集内关心的位置1

参数maxfdp1指定待测试的描述符个数(提高效率,进行不必要的测试),它的值是待测试的最大描述符加1 (描述符从0开始)


2.1 描述符就绪条件

(1) 满足下列四个条件中的一个,一个套接字准备好读

a)该套接字接收缓冲区中的数据字节数大于等于套接字接收缓冲区低水位标记的当前大小。可以用SO_RCVLOWAT套接字选项设置该套接字的低水位标记。对于TCP和UDP而言,其默认值为1。举例来说,如果我们知道除非至少存在64个字节的数据,否则我们的应用进程没有任何有效工作可做,那么可以把接收低水位标记设置为64,以防止少于64个字节的数据准备好读时select唤醒我们。

b) 该连接的读半部关闭(也就是接收了FIN的TCP连接)

c) 该套接字是一个监听套接字且已完成的连接数不为0,对这样的套接字accept通常不会阻塞

d) 其上有一个套接字错误处理。这些错误处理可以通过指定SO_ERROR套接字选项调用getsockopt获取并清除

(2) 下列四个条件中的任何一个满足时,一个套接字准备好写

a) 该套接字发送缓冲区中的可用空间字节数大于等于套接字发送缓冲区低水位标记的当前大小,并且或者该套接字已连接,或者该套接字不需要连接(如UDP套接字)。可以使用SO_SNDLOWAT套接字选项来设置该套接字的低水位标记。对于TCP和UDP套接字而言,其默认值通常为2048

b) 该连接的写半部关闭。对这样的套接字的写操作将产生SIGPIPE信号

c) 使用非阻塞式connect的套接字已建立连接,或者connect已经已失败告终

d) 其上有一个套接字待错误处理

(3) 如果一个套接存在带外数据或者仍处于带外标记,那么它有异常条件待处理


2.2 

混合使用stdio和select被认为是非常容易犯错的,因为select不知道stdio使用了缓冲区,它只是从read系统调用的角度指出是否有数据可读,而不是从fgets之类调用的角度考虑。还有readline这样自带缓冲区的函数



3、shutdown函数

(1)close把描述符的引用计数减一,仅在该计数变为0时才关闭套接字。使用shutdown可以不管引用计数就激发TCP的正常连接终止

(2)close终止读和写两个方向的数据传送。既然TCP连接是全双工的,有时候我们需要告知对端我们已经完成了数据发送,及时对端有数据要发送给我们,此时可使用shutdonwn函数

#include <sys/socket.h>int shutdown(int sockfd, int howto);

参数howto的值:

SHUT_RD 关闭连接的读一半,套接字不再有数据可接收,而且套接字接收缓冲区中的现有数据都被丢弃。进程不能再对这样的套接字调用任何读函数。对一个TCP套接字这样调用shutdown函数后,由该套接字接收的来自对端的任何数据都被确认,然后悄然丢弃。

SHUT_WR 关闭连接的写一半,对于TCP套接字,这称为半关闭。当前留在套接字发送缓冲区中的数据将被发送掉,后跟TCP的正常连接终止序列。

SHUT_RDWR 连接的读半部和写半部都关闭


0 0
原创粉丝点击