网络模型
OSI网络模型,图来源:http://www.colasoft.com.cn/download/protocols_map.php
不同网络层设备
- 物理层:网卡,网线,集线器,中继器,调制解调器
- 数据链路层:网桥,交换机
- 网络层:路由器
- 传输层:网关等软件实现
IO模型
《UNIX网络编程:卷一》第六章——I/O复用。书中提及了5种类UNIX下可用的I/O模型:(可参考https://zhuanlan.zhihu.com/p/115912936)
-
阻塞式I/O;
-
非阻塞式I/O;
-
I/O复用(select,poll,epoll...);
-
信号驱动式I/O(SIGIO);
-
异步I/O(POSIX的aio_系列函数);
应用程序一次网络数据的输入获取过程:应用程序的进程发起系统调用,通过操作系统内核获取网络数据。从内核的角度可以分为2个步骤:
- 等待网络数据准备就绪。(准备就绪的条件:https://blog.csdn.net/pechs/article/details/40049151),因为应用之间发送消息是间断性的,TCP缓存区可能还没有要读的数据,即数据没有准备就绪。
- 将数据从内核空间拷贝到应用的应用空间
其中阻塞/非阻塞对应的是第一个步骤,这个概念关注程序在等待调用结果时的状态;同步/异步对应第二个步骤,关注消息通信的机制
以下参考:https://www.zhihu.com/question/19732473/answer/26101328
对于一个套接字上的输入操作,第一步通常涉及等待数据从网络中到达。当所有等待分组到达时,它被复制到内核中的某个缓冲区。第二步就是把数据从内核缓冲区复制到应用程序缓冲区。 好,下面我们以阻塞套接字的recvfrom的的调用图来说明阻塞
标红的这部分过程就是阻塞,直到阻塞结束recvfrom才能返回。
非阻塞式I/O: 以下这句话很重要:进程把一个套接字设置成非阻塞是在通知内核,当所请求的I/O操作非得把本进程投入睡眠才能完成时,不要把进程投入睡眠,而是返回一个错误。看看非阻塞的套接字的recvfrom操作如何进行
可以看出recvfrom总是立即返回。
I/O多路复用:虽然I/O多路复用的函数也是阻塞的,但是其与以上两种还是有不同的,I/O多路复用是阻塞在select,epoll这样的系统调用之上,而没有阻塞在真正的I/O系统调用如recvfrom之上。如图
信号驱动式I/O:用的很少,就不做讲解了。直接上图
异步I/O:这类函数的工作机制是告知内核启动某个操作,并让内核在整个操作(包括将数据从内核拷贝到用户空间)完成后通知我们。如图:
注意红线标记处说明在调用时就可以立马返回,等函数操作完成会通知我们。
其实前四种I/O模型都是同步I/O操作,他们的区别在于第一阶段,而他们的第二阶段是一样的:在数据从内核复制到应用缓冲区期间(用户空间),进程阻塞于recvfrom调用。相反,异步I/O模型在这两个阶段都要处理。
再看POSIX对这两个术语的定义:
-
同步I/O操作:导致请求进程阻塞,直到I/O操作完成;
-
异步I/O操作:不导致请求进程阻塞。
好,下面我用我的语言来总结一下阻塞,非阻塞,同步,异步
-
阻塞,非阻塞:进程/线程要访问的数据是否就绪,进程/线程是否需要等待;
-
同步,异步:访问数据的方式,同步需要主动读写数据,在读写数据的过程中还是会阻塞;异步只需要I/O操作完成的通知,并不主动读写数据,由操作系统内核完成数据的读写。
问题
1. 为什么 IO 多路复用要搭配非阻塞 IO?
https://www.zhihu.com/question/37271342
假如我调用了一个 select 函数,并且关注了几个描述字, select 函数就会一直阻塞直到我关注的事件发生. 假如当有套接口可读时, select 函数就返回了,告诉我们套接口已经可读,然后我们去读这个套接口,可以用阻塞的read或者非阻塞的 read,阻塞 read 是无数据可读就阻塞进程,非阻塞 read是无数据可读就返回一个 EWOULDBLOCK 错误。那么问题来了:既然 select 都返回可读了,那就表示一定能读了,阻塞函数read也就能读取了也就不会阻塞了,非阻塞read的话,也有数据读了,也不会返回错误了,那么这俩不都一样了?
虽然select触发的时候内核已经准备好了,但是数据还是不一定是“好的”。实际运行中可能出现各种意外导致不能被正常送到用户,比如:经过校验是坏数据,丢弃;锁没加好,数据被另外一个线程拿走;被内核丢弃等。这样还是可能发生阻塞的。使用阻塞IO可能导致read阻塞,使得多路复用的管道被堵住。