一 I/O模型:
(1)阻塞式I/O:最常用的I/O模型。默认情况下,所有套接字都是阻塞的。
(2)非阻塞式I/O:不把进程投入睡眠,而是返回一个错误。
(3)I/O复用:调用select或poll,阻塞在这两个系统调用中的某一个之上,而不是阻塞在真正的I/O系统调用上。
(4)信号驱动I/O:让内核在描述符就绪时发信号通知进程。优势:等待数据到达期间进程不被阻塞。
(5)异步I/O:告诉内核执行某个操作,并让内核在整个操作完成后通知我们。与信号驱动I/O区别:信号驱动I/O告诉我们何时启动一个I/O操作;异步I/O则是由内核通知我们I/O操作何时完成。
二 I/O复用在网络上的应用场合(可以用于其他方面):
(1)当客户处理多个描述符,必须使用I/O复用。
(2)如果一个TCP服务器既要处理监听套接字,又要处理已连接套接字,一般要使用I/O复用。
(3)如果一个服务器要处理多个服务或者协议(例如inetd守护进程),一般要使用I/O复用。
(4)等等...
三 select和pselect:
1)概述
*select:允许进程指定内核等待多个事件中任何一个发生,并且在只有一个或多个事件发生或经历一段指定的时间后才唤醒它。
*pselect是POSIX发明的。相对于select有两个变化:pselect使用timespec结构;pselect增加了第六个参数:一个指向信号掩码的指针。
2)格式:
3)参数:
*nfds:指定待测试的描述符的个数,它的值是待测试的最大描述符加1。头文件<sys/select.h>中定义的FD_SETSIZE常值是数据类型fd_set中的描述符总数,其值通常是1024。
(1)readfds:指定要让内核测试读的描述符。
*一个套接字准备好读的四个条件:
~该套接字接收缓存区中的数据字节大于等于套接字接收缓存区低水位标记的当前大小(可用套接字选项SO_RCVLOWAT设置)。
~该连接的读半部关闭(即受到了FIN的TCP连接),对这样的套接字读不阻塞返回0(即EOF)。
~该套接字是一个监听套接字而且已完成连接数不为0。
~其上由一个套接字错误待处理。
(2)writefds:指定要让内核测试读的描述符。
*一个套接字准备好写的四个条件:
~该套接字发送缓存区中的数据字节大于等于套接字发送缓存区低水位标记的当前大小(可用套接字选项SO_SNDLOWAT设定),并且或者该套接字已连接,或者不需要连接(UDP)。
~该连接读半部关闭,对这样套接字写操作将产生SIGPIPE信号。
~使用非阻塞connect的套接字已建立链接,或者connect已经以失败告终。
~其上由一个套接字错误待处理。
(3)exceptfds:指定要让内核测试异常的描述符。
*目前支持的异常条件:
~某个套接字的带外数据到达;
~某个已设置为分组模式的伪终端存在可从其主端读取的控制状态信息。
(4)timeout:告知内核等待所指定描述符中的任何一个就绪的时间。
*三种可能:
~永远等下去:仅在一个描述符准备好I/O才返回,把参数设置为空指针。
~等待一段固定时间:在有一个描述符准备好I/O返回,但不超过由参数timeout指定的固定时间。
~根本不等待:轮询,检查完描述符后立即返回。参数timeout结构中的定时器值为0。
(5)返回值:若有就绪描述符则为其数目;若超时则为0;若出错则为1。
*注意:当对某一条件不敢兴趣就可以设置为空指针。
4)使用:
*描述符集的初始化很重要(FD_ZERO)。
*select返回后,结果将指示哪些描述符就绪,使用FD_ISSET()来测试fd_set数据类型中的描述符。
*描述符集内任何与未就绪描述符对应的位返回时将清成0,因此,每次重新调用select函数时,要重新设置所关心的描述符集。
*当描述符超过1024个时,可以用poll代替select。
四 poll:
(1)格式:
#include<poll.h>
int poll( struct polled *fdarray,unsigned long nfds,int timeout )
(2)参数:
*fdarray:指向一个polled结构数组第一个元素的指针。每个元素都是一个polled结构,用于指定测试某个给定描述符fd的条件。
struct polled
{
int fd;/*descriptor to check*/
short events;/*events of interest on fd:指定要测试的条件*/
short revents;/*events that occurred on fd*/
}
*nfds:指定polled结构数组中元素的个数。
*timeout:指定poll函数返回前等待多长时间。
(3)返回值:
*若有就绪描述符则为其数目;若超时则为0;若失败则为-1。
(4)poll与select比较:
*poll没有描述符个数限制,可以设置更多的描述符(select的fd_set类型限制为1024)。因为分配polled数组并把元素的数目通知内核是调用者的责任。
五 epoll
(1)概述:
*epoll是Linux下多路复用IO接口select/poll的增强版本,它能显著提高程序在大量并发连接中只有少量活跃的情况下的系统CPU利用率,因为它会复用文件描述符集合来传递结果而不用迫使开发者每次等待事件之前都必须重新准备要被侦听的文件描述符集合,另一点原因就是获取事件的时候,它无须遍历整个被侦听的描述符集,只要遍历那些被内核IO事件异步唤醒而加入Ready队列的描述符集合就行了。
*epoll通过epoll_create创建一个用于epoll轮询的描述符,通过epoll_ctl添加/修改/删除事件,通过epoll_wait检查事件,epoll_wait的第二个参数用于存放结果。
(2)三个相关函数(注意细节):
*epoll_create:创建一个epoll句柄,它会占用一个fd的值,可通过/proc/进程id/fd/查看,所以使用完epoll后必须调用close关闭,否则导致fd被耗尽。
*epoll_ctl:epoll的事件注册函数。
*epoll_wait:等待事件发生。
(3)epoll的两种工作模式(需好好研究):(默认是LT,可通过epoll_ctl设置)
1.LT(level triggered)水平触发:默认模式。
2.ET(edge triggered)边界触发:只支持非阻塞socket。
3.区别:
*LT:水平触发,效率会低于ET触发,尤其在大并发,大流量的情况下。但是LT对代码编写要求比较低,不容易出现问题。LT模式服务编写上的表现是:只要有数据没有被获取,内核就不断通知你,因此不用担心事件丢失的情况。
*ET:边缘触发,效率非常高,在并发,大流量的情况下,会比LT少很多epoll的系统调用,因此效率高。但是对编程要求高,需要细致的处理每个请求,否则容易发生丢失事件的情况。
六 相互比较:
(1)epoll与select:
1.传统select/poll一个致命缺点:获取事件的时候需遍历整个监听的文件描述符集合,当拥有一个很大的集合而只有部分是“活跃”的时候,效率很低;而epoll它只对“活跃”的文件描述符集合进行操作。
2.select受一个进程所能打开的FD限制,由FD_SETSIZE设置,默认是1024,对于支持大量并发的服务器来说不够;epoll没有这个限制,它所支持的FD上限是最大可以打开的文件的数目。
3.epoll可以复用文件描述符集来传递结果,而不用每次等待事件之前都必须重新准备要监听的文件描述符集合;select则每次都要把关心的文件描述符集合重新设置。