我对网络IO的理解( 二 )


一个异步I/O操作不会引起请求进程阻塞 。
从这个官方定义中,不管是Blocking I/O还是Non-Blocking I/O,其实都是synchronous I/O 。因为它们一定都会阻塞在第二阶段拷贝数据那里 。只有异步IO才是异步的 。

我对网络IO的理解

文章插图
同步异步对比
图片来源于知乎
阻塞和非阻塞的区别
阻塞和非阻塞主要区别其实是在第一阶段等待数据的时候 。但是在第二阶段,阻塞和非阻塞其实是没有区别的 。程序必须等待内核把收到的数据复制到进程缓冲区来 。换句话说,非阻塞也不是真的一点都不”阻塞”,只是在不能立刻得到结果的时候不会傻乎乎地等在那里而已 。
IO多路复用Select、Poll、Epoll的区别
【我对网络IO的理解】Select、poll、epoll都是I/O多路复用的机制,I/O多路复用就是通过一种机制,一个进程可以监视多个文件描述符fd,一旦某个描述符就绪(一般是读就绪或者写就绪),能够通知程序进行相应的读写操作 。但select,poll,epoll本质上都是同步I/O,因为他们都需要在读写事件就绪后自己负责进行读写,也就是说这个读写过程是阻塞的,而异步I/O则无需自己负责进行读写,异步I/O的实现会负责把数据从内核拷贝到用户空间 。
select
int select (int n, fd_set *readfds, fd_set *writefds, fd_set *exceptfds, struct timeval *timeout);select 函数监视的文件描述符分3类,分别是writefds、readfds、和exceptfds 。调用后select函数会阻塞,直到有描述符就绪(有数据 可读、可写、或者有except),或者超时(timeout指定等待时间,如果立即返回设为null即可),函数返回 。当select函数返回后,可以 通过遍历fdset,来找到就绪的描述符 。
select支持几乎所有的平台,跨平台是它的优点 。
select缺点是:1)单个进程支持监控的文件描述符数量有限,Linux下一般是1024,可以修改提升限制,但是会造成效率低下 。2)select通过轮询方式通知消息,效率比较低 。
poll
int poll (struct pollfd *fds, unsigned int nfds, int timeout);不同于select使用三个位图来表示三个fdset的方式,poll使用一个pollfd的指针实现 。
struct pollfd { int fd; /* file descriptor */ short events; /* requested events to watch */ short revents; /* returned events witnessed */};pollfd结构包含了要监视的event和发生的event,不再使用select“参数-值”传递的方式 。同时,pollfd并没有最大数量限制(但是数量过大后性能也是会下降) 。和select函数一样,poll返回后,需要轮询pollfd来获取就绪的描述符 。
从上面看,select和poll都需要在返回后,通过遍历文件描述符来获取已经就绪的socket 。事实上,同时连接的大量客户端在一时刻可能只有很少的处于就绪状态,因此随着监视的描述符数量的增长,其效率也会线性下降 。
epoll
epoll是在2.6内核中提出的,是之前的select和poll的增强版本,是Linux特有的 。相对于select和poll来说,epoll更加灵活,没有描述符限制 。epoll使用一个文件描述符管理多个描述符,将用户关系的文件描述符的事件存放到内核的一个事件表中,这样在用户空间和内核空间的copy只需一次 。
int epoll_create(int size);//创建一个epoll的句柄,size用来告诉内核这个监听的数目一共有多大int epoll_ctl(int epfd, int op, int fd, struct epoll_event *event);int epoll_wait(int epfd, struct epoll_event * events, int maxevents, int timeout);执行epoll_create时,创建了红黑树和就绪list链表;执行epoll_ctl时,如果增加fd,则检查在红黑树中是否存在,存在则立即返回,不存在则添加到红黑树中,然后向内核注册回调函数,用于当中断事件到来时向准备就绪的list链表中插入数据 。执行epoll_wait时立即返回准备就绪链表里的数据即可 。
工作模式
1. LT模式
LT(level triggered)是缺省的工作方式,并且同时支持block和no-block socket,在这种做法中,内核告诉你一个文件描述符是否就绪了,然后你可以对这个就绪的fd进行IO操作 。如果你不做任何操作,内核还是会继续通知你的 。
2. ET模式
ET(edge-triggered)是高速工作方式,只支持no-block socket 。在这种模式下,当描述符从未就绪变为就绪时,内核通过epoll告诉你 。然后它会假设你知道文件描述符已经就绪,并且不会再为那个文件描述符发送更多的就绪通知,直到你做了某些操作导致那个文件描述符不再为就绪状态了(比如,你在发送,接收或者接收请求,或者发送接收的数据少于一定量时导致了一个EWOULDBLOCK/EAGAIN 错误) 。但是请注意,如果一直不对这个fd作IO操作(从而导致它再次变成未就绪),内核不会发送更多的通知(only once),因此必须把缓存区buff数据读取完毕,不然就可能会丢数据 。


推荐阅读