四种基本的I/O模型

常用的四种IO模型:

1)blocking IO(阻塞IO模型)

客户端需要等待服务端返回数据,整个过程是串行的

2)non-blocking IO(非阻塞IO)

服务端立即相应客户端,数据没有准备好就返回Error,客户端需要轮询服务端获取想要的数据,直到数据准备好并返回

3)IO multiplexing(IO多路复用)

IO多路复用的三种方式:

  • 1、select效率最低,但有最大描述符限制,在linux为1024。
  • 2、poll和select一样,但没有最大描述符限制。
  • 3、epoll效率最高,没有最大描述符限制,支持水平触发与边缘触发。

IO多路复用的优势:同时可以监听多个连接,用的是单线程,利用空闲时间实现并发。

4)Asynchronous I/O(异步IO)

用户进程发起read操作之后,立刻就可以开始去做其它的事。而另一方面,从kernel的角度,当它受到一个asynchronous read之后,首先它会立刻返回,所以不会对用户进程产生任何block。然后,kernel会等待数据准备完成,然后将数据拷贝到用户内存,当这一切都完成之后,kernel会给用户进程发送一个signal,告诉它read操作完成了。
异步最大特点:全程无阻塞

sellect、poll、epoll三者的区别 :

select:

  • 目前支持几乎所有的平台,默认单个进程能够监视的文件描述符的数量存在最大限制,在linux上默认只支持1024个socket,可以通过修改宏定义或重新编译内核(修改系统最大支持的端口数)的方式提升这一限制。
  • 内核准备好数据后通知用户有数据了,但内核不告诉用户是哪个连接有数据,用户只能通过轮询的方式来获取数据,假定select让内核监视100个socket连接,当有1个连接有数据后,内核就通知用户100个连接中有数据了,但是不告诉用户是哪个连接有数据了,此时用户只能通过轮询的方式一个个去检查然后获取数据,那么如果有上万个,十万个连接,那就得轮询上万次,上十万次,而所取的结果仅仅就那么1个,这样就会浪费很多系统开销
  • 只支持水平触发
  • 每次调用select,都需要把fd集合从用户态拷贝到内核态,这个开销在fd很多时会很大

    poll:

  • 与select没有本质上的差别,仅仅是没有了最大文件描述符数量的限制

  • 只支持水平触发
  • 只是一个过渡版本,很少用

epoll:

  • Linux2.6才出现epoll,具备了select和poll的一切优点,公认为性能最好的多路IO就绪通知方法,没有最大文件描述符数量的限制,不支持windows平台
  • 内核准备好数据以后会通知用户哪个连接有数据了,IO效率不随fd数目增加而线性下降
  • 同时支持水平触发和边缘触发
  • 使用mmap加速内核与用户空间的消息传递(内存零拷贝)

水平触发与边缘触发:

水平触发: 将就绪的文件描述符告诉进程后,如果进程没有对其进行IO操作,那么下次调用epoll时将再次报告这些文件描述符,这种方式称为水平触发

边缘触发: 只告诉进程哪些文件描述符刚刚变为就绪状态,它只说一遍,如果我们没有采取行动,那么它将不会再次告知,这种方式称为边缘触发

select和epoll的特点:

select:

select通过一个select()系统调用来监视多个文件描述符的数组,当select()返回后,该数组中就绪的文件描述符便会被内核修改标志位,使得进程可以获得这些文件描述符从而进行后续的读写操作。由于网络响应时间的延迟使得大量TCP连接处于非活跃状态,但调用select()会对所有socket进行一次线性扫描,所以这也浪费了一定的开销。

epoll:

  • epoll同样只告知那些就绪的文件描述符,而且当我们调用epoll_wait()获得就绪文件描述符时,返回的不是实际的描述符,而是一个代表就绪描述符数量的值,你只需要去epoll指定的一个数组中依次取得相应数量的文件描述符即可,这里也使用了内存映射(mmap)技术,这样便彻底省掉了这些文件描述符在系统调用时复制的开销。
  • 另一个本质的改进在于epoll采用基于事件的就绪通知方式。在select/poll中,进程只有在调用一定的方法后,内核才对所有监视的文件描述符进行扫描,而epoll事先通过epoll_ctl()来注册一个文件描述符,一旦基于某个文件描述符就绪时,内核会采用类似callback的回调机制,迅速激活这个文件描述符,当进程调用epoll_wait()时便得到通知。

  • select和poll都需要在返回后,通过遍历所有文件描述符来获取已经就绪的socket。事实上,同时连接的大量客户端在一时刻可能只有很少的处于就绪状态,因此随着监视的描述符数量的增长,其效率也会线性下降。

IO操作分两个阶段:

  • 1、等待数据准备好(数据读到内核缓存)
  • 2、将数据从内核读到用户空间(进程空间)
    一般来说第一阶段操作花费的时间远远大于第二阶段。

同步IO & 异步IO:

同步IO :包括 blocking IO、non-blocking、select、poll、epoll(故:epool只是伪异步而已)(有阻塞)
异步IO :包括:asynchronous (无阻塞)
主要区别 :IO操作的第二阶段是阻塞的,然而这阶段操作花费时间远远小于第一个阶段,epoll和kqueue已经做到很好了。

Reactor & Proactor

Reacor模式 包括:epoll(*nux), kqueue(FreeBSD)、select(POSIX标准)

Proactor模式 包括:IOCP(Windows)

相信技术的力量,原创技术文章,感谢您的支持!