Linux的网络IO模型
网络IO的本质是socket的读写,socket在Linux中被抽象为流,IO可以理解为对流的操作。
IO的分类和范畴
IO本身可以分为内存IO、网络IO和磁盘IO还有缓存IO等,一般讨论IO时更多是指后(网络IO和磁盘IO,因为这两个是最慢的哈哈),此处特别分析和说明网络IO。
操作处理的分类
阻塞/非阻塞
针对函数/方法的实现方式而言,即数据就绪之前是立刻返回还是等待,即发起IO请求后是否会阻塞。
阻塞IO机制
阻塞IO情况下,当用户调用read后,用户线程会被阻塞,等内核数据准备好并且数据从内核缓冲区拷贝到用户态缓存区后read才会返回。可以看到是阻塞的两个部分。
-
CPU把数据从磁盘读到内核缓冲区。
- CPU把数据从内核缓冲区拷贝到用户缓冲区。
![【技术原理】网络IO模型分析介绍(上)]()
非阻塞IO机制
![【技术原理】网络IO模型分析介绍(上)]()
同步/异步
IO读操作指数据流经:网络 -> 内核缓冲区 -> 用户内存
对于一个网络IO,会涉及到两个系统对象,一个是调用这个IO的process(or thread)【用户态】,另一个就是系统内核(kernel)【内核态】
当一个用户态发生read操作发生时,它会经历两个阶段:
网络IO的模型大致有如下几种:
阻塞IO模型(blocking IO)
在Linux中,默认情况下所有的socket都是blocking,一个典型的读操作流程如下:
![【技术原理】网络IO模型分析介绍(上)]()
当用户进程调用了recvfrom这个系统调用,如上所述,会有两个阶段
准备数据:
Blocking IO的特点就是IO执行的两个阶段都是block了的。
非阻塞IO模型(non-blocking IO)[poll]
在Linux中,可以通过设置socket使其变为non-blocking,其流程如下:
![【技术原理】网络IO模型分析介绍(上)]()
多路复用IO模型(multiplexing IO)
select/epoll/evpoll,也被称作是Event-Driven IO。好处是单个process可以同时处理多个网络连接的IO。
![【技术原理】网络IO模型分析介绍(上)]()
Select/Epoll的优势并不是对于单个连接能处理得更快,而是在于能处理更多的连接。
在IO多路复用模型中,实际中,对于每一个socket,一般都设置成为non-blocking,但是,如上图所示,整个用户的process其实是一直被block的。只不过process是被select这个函数block,而不是被socket IO给block。
异步IO(asynchronous IO)
用户进程发起read操作之后,立刻就可以开始去做其它的事。
![【技术原理】网络IO模型分析介绍(上)]()
-
从kernel的角度,当它受到一个asynchronous read之后,首先它会立刻返回,所以不会对用户进程产生任何block。
- kernel会等待数据准备完成,然后将数据拷贝到用户内存,当这一切都完成之后,kernel会给用户进程发送一个signal,告诉它read操作完成了。
比较
![【技术原理】网络IO模型分析介绍(上)]()
非阻塞和异步的区别
经过上面的介绍,会发现non-blocking IO和asynchronous IO的区别还是很明显的。
IO复用技术
在IO编程过程中,当需要处理多个请求时,可以使用多线程和IO复的方式进行处理。
IO复用是什么?
把多个IO的阻塞复用到一个select之类的阻塞上,从而使得系统在单线程的情况下同时支持处理多个请求。
IO复用常见的应用场景:
select和poll的原理基本相同:
-
注册待侦听的fd(这里的fd创建时最好使用非阻塞)
-
每次调用都去检查这些fd的状态,当有一个或者多个fd就绪的时候返回
- 返回结果中包括已就绪和未就绪的fd
select和poll与epoll机制的比较
Linux网络编程过程中,相比于select/poll,epoll是有着更明显优势的一种选择。
IO效率可能随着文件描述符数目的增加而线性下降。
IO模型的总结
最后,再举几个不是很恰当的例子来说明这四个IO Model,有A,B,C,D四个人在钓鱼:
-
A用的是最老式的鱼竿,所以呢,得一直守着,等到鱼上钩了再拉杆;(同步阻塞)
-
B的鱼竿有个功能,能够显示是否有鱼上钩,所以呢,B就和旁边的MM聊天,隔会再看看有没有鱼上钩,有的话就迅速拉杆;(非阻塞)
-
C用的鱼竿和B差不多,但他想了一个好办法,就是同时放好几根鱼竿,然后守在旁边,一旦有显示说鱼上钩了,它就将对应的鱼竿拉起来;(io多路复用机制)
- D是个有钱人,干脆雇了一个人帮他钓鱼,一旦那个人把鱼钓上来了,就给D发个短信。(异步机制)