unix下共有5种I/O模型:
- 阻塞式I/O
- 非阻塞式I/O
- I/O复用(select和poll)
- 信号驱动式I/O(SIGIO)
- 异步I/O(POSIX的aio_系列函数)
同步I/O与异步I/O:
- 同步I/O操作导致请求进程阻塞,直到I/O操作完成。前4种模型都是同步I/O模型,因为其中真正的I/O操作将阻塞进程。
- 异步I/O操作不导致请求进程阻塞。
select函数读就绪条件:
- 该套接字接收缓冲区中的数据字节数大于等于套接字接收缓冲区低水位标记的当前大小。对上述套接字执行读操作不会阻塞并将返回一个大于0的值。
- 该连接读半部关闭(也就是接收了FIN的TCP连接)。对这样的套接字的读操作将不阻塞并返回0(也就是返回EOF)。
- 该套接字是一个监听套接字并且已完成连接数不为0。
- 其上有一个套接字错误待处理。
select函数写就绪条件:
- 该套接字发送缓冲区中的可用空间字节数大于等于套接字发送缓冲区低水位标记的当前大小,并且或者该套接字已连接,或该套接字不需要连接(例如UDP套接字)。
- 该连接的写半部关闭。对这样的套接字的写操作将产生SIGPIPE信号。
- 使用非阻塞式connect的套接字已经建立连接,或者connect已经以失败返回。
- 其上有一个套接字错误待处理。
当某个套接字上发生错误时,它将由select标记为既可读又可写。
这里要对读写操作的一些特点进行总结:
FIN | RST | |
读 | 对这样的套接字的读操作将不阻塞并返回0(也就是返回EOF) | 从已经接收到RST的套接字中读取数据,函数将返回ECONNRESET(“connection reset by peer”,对方复位连接错误)。 |
写 | 向已经接收到FIN的套接字中写入数据,不会引发问题,但会引发对端发送RST | 向接收到RST的套接字中写入数据,内核会向进程发送SIGPIPE信号,并使进程终止。 |
在批量方式下,标准输入中的EOF并不意味着我们同时也完成了从套接字的读入;可能仍有请求在去往服务器的路上,或者仍有应答在返回客户的路上。
对于上述问题的解决方法是关闭TCP连接其中的一半。
终止网络连接的通常方法是调用close函数。不过close有两个限制,却可以使用shutdown来避免。
- close把描述符引用计数减1,直到为0时才关闭套接字。但shutdown可以直接激发TCP的正常终止序列。
- close终止读和写两个方向的数据传送。
函数原型如下:
#include <sys/socket.h>
extern int shutdown (int __fd, int __how) __THROW;
__how有以下三种情况:
- SHUT_RD,关闭连接的读一半。对一个TCP套接字调用shutdown函数后,由该套接字接收的来自对端的任何数据都被确认,并悄悄丢弃。
- SHUT_WR,关闭连接的写一半。激发正常终止序列。
- SHUT_RDWR,连接的读半部和写半部都关闭。
这里先给大家总结select与poll函数的第一个区别,也是我自己总结的。
select函数在使用时通过fd_set类型的数据对其中等待的描述符进行记录,但对这一结构并不是直接对应于每一个描述符,我们还需要专门的函数对这一类型进行操作,因此在使用select函数时还需要使用一个专门的数组用于记录相应的描述符。
但poll函数通过struct pollfd对等待的描述符进行记录,这一结构的第一个字段直接记录了这一描述符,因此不再需要一个专门的数组用于存放描述符。