Unix下有5种I/O模型: 阻塞型I/O, 非阻塞型I/O, I/O多路复用, 信号驱动I/O, 异步I/O. (Page.154)
阻塞型I/O是最普遍的I/O模型, 并且是socket的默认模式. 此模式下I/O会阻塞至数据准备完毕并切拷贝到进程内存才会返回. (Page.154)
非阻塞型I/O就是在一次I/O操作时, 如果进程不休眠等待, 就无法完成本次操作(即当前没有数据准备好), 那么就返回一个错误(EWOULDBLOCK)而不是一直阻塞. (Page.155)
I/O多路复用也会阻塞, 但是与阻塞型I/O不同, I/O多路复用是阻塞在一个多路复用函数(如select, poll)而不是阻塞在直接的读写操作(read, write), 并且I/O多路复用的阻塞可以同事等待多个描述符. 与之相似的有多线程/多进程+阻塞型I/O(Page.157)
信号驱动I/O就是在内核准备好数据之后, 想进程发送一个SIGIO信号告知可以进行I/O操作了. (Page.157)
异步I/O与信号驱动I/O相似, 不同点在于异步I/O会在数据准备好之后, 自动把数据拷贝进进程指定的内存空间, 之后才通知进程I/O操作完成. (Page.158)
注意在从内核中拷贝数据到用户进程内存也是阻塞的一种, 所以只有异步I/O才是真正的非阻塞异步, 而其余四种I/O模型都算是阻塞型的. (Page.160)
I/O多路复用的本质就是, 告知内核我们对某些指定的描述符的指定状态感兴趣, 如果在指定的时间内出现了该状态, 那么返回告知, 否则超时后返回告知没有出现指定状态. (Page.161)
可以设置I/O多路复用的等待时间, 可以永远等待(select: NULL; poll: INFTIM), 不等待马上返回(select, poll设置0), 等待指定时间(指定相应字段). (Page.161).
Select在阻塞时会被信号中断, 并且在不同的平台, 不能保证select会被重启, 所以应做好接受select返回的EINTR错误并重启的准备(poll也是应该这么做). (Page.162)
select和poll等待时间的精度会受到内核时钟分辨率以及内核调度的影响. (Page.162)
select所等待的描述符只会在两种情况下返回异常状态, 其中与网络编程相关的是收到了带外数据, 文件符返回错误并不是异常状态. (Page.162)
select使用的fdset可以直接用等号赋值. (Page.163)
如果对某种状态不感兴趣, 可以直接吧对应状态的fdset指针设为NULL. (Page.163)
可以通过FD_SETSIZE常量获得fdset的最大容量(通常为1024). (Page.163)
select的返回值是三种状态的fdset中仍然打开的位的个数. 所以如果对一个描述符的多个状态感兴趣, 那么如果这个描述符同时可读可写, 那么应该算作两个位. (Page164).
在以下四种情况一个描述符为可读: (1)内核中的数据量大于SO_RCVLOWAT选项指定的值(TCP和UDP的默认值都为1个字节); (2)链接的读链接被关闭(收到了FIN); (3)监听socket的监听队列中有已经完成3次握手的链接; (4)描述符有错误发生. (Page.164)
在以下四种情况一个描述符为可写: (1)内核的发送缓存的空间不小于SO_SNDLOWAT指定的值(TCP和UDP通常为2048); (2)写链接被关闭; (3)socket通过非阻塞connect完成了TCP三次握手或者失败; (4)描述符有错误发生. (Page.164)
一般来说, select返回监听socket可读时, 对这个socket的描述符进行accept操作是不会出现阻塞的, 但是在某些情况下仍然可能阻塞, 所以这里要小心处理, 应把监听socket设为非阻塞. (Page.164)
Select返回一个socket可写时, 虽然内核的发送缓存空间不小于SO_SNDLOWAT指定的值, 但是如果需要发送的数据量超过了缓存空间, 仍然可能出现阻塞. 所以应把socket设为非阻塞才能真正避免阻塞. (Page.164)
如果对一个没有”reader”的描述符进行写操作, 那么就会产生SIGPIPE, 写操作返回错误, 错误码为EPIPE. 因此对关闭了写链接的socket或则收到RST的socket进行写操作都会出现这种现象. (Page.164)
当一个socket出现错误时, 那么就会被标记为即可读也可写, 但是这不是异常状态. (Page.164)
UDP是无连接的, 它并没有实际的内核发送缓存, 所以只要SO_SNDLOWAT所设定的值比SO_SNDBUF所设定的值小即可. (Page164)
目前大部分系统提供”无限”的文件描述符, 描述符的数量由系统的内存和管理员设定的限制决定. (Page.165)
如果想要增大FD_SET所能容纳的文件描述符的数量, 最为可靠和根本的方法是修改FD_SETSIZE的值, 并重新编译内核. (Page.165)
select等I/O多路复用函数是不适宜和一些有自己实现内部缓存的函数共用的, 例如select与stdio共用, 通过fgets读取文件, 如果stdio一次把多行放入自己的缓存, 而fgets一次就只读取一行, 导致重复多次调用select以获取可读的描述符, 而stdio对应的描述符其实是一直可读的, 因此后面调用的select都是空转. (Page.171)
shutdown和close的区别: (1)调用shutdown会马上关闭指定链接, 而close会等到描述符的引用计数器为0时才会开始关闭链接; (2)close会同时关闭两个链接, 而shutdown值关闭指定链接; (3)close后文件描述符不再可用, shutdown后文件描述符是可用的. (Page.172)
对一个sockeet描述符shutdown了SHUT_RD后, 按照UNPv1的说法是, 如果另一方继续发送数据, 那么这些数据仍然会被接收方确认, 只是接收方会自动删掉这些数据, 不会交给用户进程. 而在linux下面, 如果想这样一个shutdown了SHUT_RD的链接发送数据, 发送方会收到RST. (Page.173)
对一个socket描述符shutdown SHUT_WR, 那么内核会在发送完缓存中的数据后发送FIN启动断开连接. (Page.173)
SHUT_RDWR相当于先shutdown SHUT_RD, 再shutdown SHUT_WR. (Page.173)
应对DoS攻击的简单方法有: (1)使用非阻塞I/O; (2)一个线程/进程单独服务一个客户; (3)使用超时机制. (Page.180)
pselect的精度更高(纳秒), 而且可以设置信号掩码. (Page.181)
与select不同, poll的某些状态不需要自行设定(也无法自行设定)就会在出现这种状态的时候返回: POLLERR, POLLHUP和POLLNVAL. (Page.183)
POLL不同状态对应的选项比较混乱, 可能一种状态对应两种可能的选项, 需要查看书本. (Page.184)