This commit is contained in:
Dragon
2021-04-08 00:56:56 +08:00
parent 3c12ef4585
commit 785faca8b4

View File

@@ -807,100 +807,6 @@ proactor: 这有十个字节数据,收好了跟我说一声。
> 想要理解这两设计模式,还是要真正的学习设计模式。
# 五种IO模型的区别
> 从下面这两个文章总结的:
>
> * https://blog.csdn.net/sehanlingfeng/article/details/78920423
> * http://www.tianshouzhi.com/api/tutorials/netty/221
>
> 下面总结的地方如果有不懂的,看上面的文章内容
>
> 下面这两篇文章看起来不错,不过还没认真看:
>
> * https://blog.csdn.net/ocean_fan/article/details/79622956
> * https://blog.csdn.net/ZWE7616175/article/details/80591587
在这里我们以一个网络IO来的read来举例它会涉及到两个东西一个是产生这个IO的进程另一个就是系统内核(kernel)。当一个read操作发生时它会经历两个阶段
**阶段1**等待数据准备
**阶段2**将数据从内核拷贝到进程中
## 阻塞IO
当用户进程进行recvfrom这个系统调用内核就开始了IO的第一个阶段等待数据准备。对于network io来说很多时候数据在一开始还没有到达比如还没有收到一个完整的UDP包这个时候**内核**就要等待足够的数据到来。而在用户进程这边,整 个进程会被阻塞。当**内核**一直等到数据准备好了,它就会将数据从**内核**中拷贝到用户内存,然后**内核**返回果,用户进程才解除 block的状态重新运行起来。**所以blocking IO的特点就是在IO执行的两个阶段都被block了。**
<img src="https://cdn.jsdelivr.net/gh/youthlql/lqlp@v1.0.0/computer_network/summary/0003.png" width=80%>
<img src="http://images.cnitblog.com/blog/405877/201411/142330286789443.png" width=70%>
## 非阻塞IO
1. 当用户进程发出read操作时如果kernel中的数据还没有准备好那么它并不会block用户进程而是立刻返回一个error。
2. 从用户进程角度讲 它发起一个read操作后并不需要等待而是马上就得到了一个结果。用户进程判断结果是一个error时它就知道数据还没有准备好。用户线程需要不断地发起IO请求直到数据到达后才真正读取到数据继续执行。
3. 虽然用户线程每次发起IO请求后可以立即返回但是为了等到数据仍需要不断地轮询、重复请求消耗了大量的CPU的资源。一般很少直接使用这种模型而是在其他IO模型中使用非阻塞IO这一特性。
4. **所以,用户进程第一个阶段不是阻塞的,需要不断的主动询问内核数据好了没有;第二个阶段依然总是阻塞的。**
<img src="https://cdn.jsdelivr.net/gh/youthlql/lqlp@v1.0.0/computer_network/summary/0004.png" width=80%>
<img src="http://images.cnitblog.com/blog/405877/201411/142332004602984.png" width=70%>
## 多路复用IO
1. IO多路复用模型是建立在内核提供的多路分离函数select基础之上的使用select函数可以避免同步非阻塞IO模型中轮询等待的问题。利用了新的select系统调用由内核来负责本来是请求进程该做的轮询操作
2. 它的基本原理就是select /epoll这个函数会不断的轮询所负责的所有socket当某个socket有数据到达了就通知用户进程正式发起read请求。
3. 从流程上来看使用select函数进行IO请求和同步阻塞模型没有太大的区别甚至还多了添加监视socket以及调用select函数的额外操作效率更差。但是使用select以后最大的优势是用户可以在一个线程内同时处理多个socket的IO请求。用户可以注册多个socket然后不断地调用select读取被激活的socket(也就是数据准备好了的socket)即可达到在同一个线程内同时处理多个IO请求的目的。而在同步阻塞模型中必须通过多线程的方式才能达到这个目的。
<img src="https://cdn.jsdelivr.net/gh/youthlql/lqlp@v1.0.0/computer_network/summary/0005.png" width=80%>
### select函数的其它好处
> handle_events实现事件循环
>
> handle_event进行读/写等操作
1. 使用select函数的优点并不仅限于此。虽然上述方式允许单线程内处理多个IO请求但是每个IO请求的过程还是阻塞的在select函数上阻塞平均时间甚至比同步阻塞IO模型还要长。
2. 如果用户线程只注册自己感兴趣的socket或者IO请求然后去做自己的事情等到数据到来时再进行处理则可以提高CPU的利用率。
3. IO多路复用模型使用了Reactor设计模式实现了这一机制。
4. 通过Reactor的方式可以将用户线程轮询IO操作状态的工作统一交给handle_events事件循环进行处理。用户线程注册事件处理器之后可以继续执行做其他的工作异步而Reactor线程负责调用内核的select函数检查socket状态。当有socket被激活时(就是数据准备好的时候)则通知相应的用户线程或执行用户线程的回调函数执行handle_event进行数据读取、处理的工作。
5. 由于select函数是阻塞的因此多路IO复用模型也被称为异步阻塞IO模型。注意这里的所说的阻塞是指select函数执行时线程被阻塞而不是指socket。(一般在使用IO多路复用模型时socket都是设置为NONBLOCK的不过这并不会产生影响因为用户发起IO请求时数据已经到达了用户线程一定不会被阻塞。)
<img src="http://images.cnitblog.com/blog/405877/201411/142333254136604.png" width=80%>
## 信号驱动IO
1. 在信号驱动IO模型中当用户线程发起一个IO请求操作会给对应的socket注册一个信号函数然后用户线程会继续执行当内核数据就绪时会发送一个信号给用户线程用户线程接收到信号之后便在信号函数中调用IO读写操作来进行实际的IO请求操作。
2. 这个一般用于UDP中对TCP套接口几乎是没用的原因是该信号产生得过于频繁并且该信号的出现并没有告诉我们发生了什么事情
3. 信号驱动IO放佛很像异步IO它的第一阶段不是阻塞的。但是很遗憾它的数据拷贝阶段(第二阶段),任然是阻塞的。
<img src="https://cdn.jsdelivr.net/gh/youthlql/lqlp@v1.0.0/computer_network/summary/0006.png" width=80%>
## 异步IO
1. 真正”的异步IO需要操作系统更强的支持。在IO多路复用模型中由用户线程自行读取数据、处理数据。
2. 而在异步IO模型中用户进程发起read操作之后立刻就可以开始去做其它的事。
3. 而另一方面,从**内核**的角度,当它受到一个异步读之后,首先它会立刻返回,所以不会对用户进程产生任何阻塞。然后,内核会等待数据准备完成,然后将数据拷贝到用户内存,当这一切都 完成之后,**内核**会给用户进程发送一个信号告诉它read操作完成了用户线程直接使用即可。 在这整个过程中,进程完全没有被阻塞。
4. 异步IO模型使用了Proactor设计模式实现了这一机制。**(具体怎么搞得,看上面的文章链接)**
<img src="https://cdn.jsdelivr.net/gh/youthlql/lqlp@v1.0.0/computer_network/summary/0007.png" width=80%>
<img src="http://images.cnitblog.com/blog/405877/201411/142333511475767.png">
# select、poll、epoll的区别
> select, poll, epoll 都是I/O多路复用的具体的实现之所以有这三个存在其实是他们出现是有先后顺序的。
@@ -927,11 +833,3 @@ poll本质上和select没有区别采用**链表**的方式替换原有fd_set
# IO疑难点
https://blog.csdn.net/m0_38109046/article/details/89449305
https://www.zhihu.com/question/19732473
[漫画讲IO](https://mp.weixin.qq.com/s?__biz=Mzg3MjA4MTExMw==&mid=2247484746&idx=1&sn=c0a7f9129d780786cabfcac0a8aa6bb7&source=41&scene=21#wechat_redirect)