网络编程之IO模型与Epoll
一、IO模型
区分同步&异步,阻塞&非阻塞
-
区分同步或异步(
synchronous/asynchronous
)。简单来说,同步是一种可靠的有序运行机制,当我们进行同步操作时,后续的任务是等待当前调用返回,才会进行下一步;而异步则相反,其他任务不需要等待当前调用返回,通常依靠事件、回调等机制来实现任务间次序关系。(最简单:同步一次干一件事,异步干一件事的同事还可以干别的事情,而且多用回调实现) -
区分阻塞与非阻塞(
blocking/non-blocking
)。用户线程调用内核IO操作的方式:阻塞是指IO操作需要彻底完成后才返回到用户空间;而非阻塞是指IO操作被调用后立即返回给用户一个状态值,无需等到IO操作彻底完成。
操作系统的IO过程,通常将其分为两个阶段:
- 1.等待远程数据就绪。网卡会将数据报文传给协议栈,封装处理之后拷贝到内核缓冲区中(外部->内核态)
- 2.将数据从内核缓冲区拷贝到进程中(内核态->用户态)
根据这上面两个事情就可以分为四种IO模型:
1.同步阻塞I/O(Synchrohous, blocking I/O)
数据从外部介质到内核,从内核到用户进程,每一步都是同步阻塞式的。

2.同步非阻塞I/O(Synchrohous, non-blocking I/O)
在非阻塞IO模型中,数据从外部到内核虽然不是阻塞的,但用户线程需要不断地询问内核数据是否就绪,也就说非阻塞IO不会交出CPU,而会一直占用CPU。

3.I/O多路复用(I/O Multiplexing)
linux中常见的select和epoll模型就是这种,虽然两个阶段都阻塞,但使用select以后可以在一个线程内同时处理多个socket的IO请求。用户可以注册多个socket,然后不断地调用select读取被激活的socket,即可达到在同一个线程内同时处理多个IO请求的目的。而在同步阻塞模型中,必须通过多线程的方式才能达到这个目的。(Java的NIO也是采用此种实现机制)

4.异步I/O(Asynchronous I/O)
两个进程都是异步非阻塞的,完成后内核会给用户进程发送一个signal,告诉它read操作完成了。

二、IO多路复用之Select/Poll和Epoll
Linux的内核将所有的外部设备都看作是一个文件来操作。对一个文件的操作会调用内核提供的系统命令,然后返回一个file descriptor(fd,文件描述符)。而对一个socket的读写也会有相应描述符,称socketfd(socket描述符),描述符是一个数字,它指向内核中的一个结构体。
select,poll,epoll都是IO多路复用的机制。I/O多路复用就是通过一种机制,一个进程可以监视多个描述符,一旦某个描述符就绪(一般是读就绪或者写就绪),能够通知程序进行相应的读写操作。但select,poll,epoll本质上都是同步I/O,因为他们都需要在读写事件就绪后自己负责进行读写,也就是说把数据从内核拷贝到用户空间是阻塞的,而异步I/O则无需自己负责进行读写,异步I/O的实现会负责把数据从内核拷贝到用户空间与多进程和多线程技术相比,I/O多路复用技术的最大优势是系统开销小,系统不必创建进程/线程,也不必维护这些进程/线程,从而大大减小了系统的开销。
Epoll的工作原理如下:
主要过程:把socket放到epoll文件系统里file对象对应的红黑树上,红黑树中是否存在,立即返回,不存在则添加到红黑树上。 所有添加到epoll中的事件都会与设备(网卡)驱动程序建立回调关系,也就是说,当相应的事件发生时会调用这个回调方法。这个回调方法在内核中叫ep_poll_callback,它会将发生的事件添加到rdlist双链表中。

和select的区别也就在这里:select/poll每次调用都要传递所要监控的所有fd给select/poll系统调用,这意味着每次调用都要将fd列表从用户态拷贝到内核态,当fd数目很多时,这会造成低效。