UNIX 网络编程定义的5种I/O模型

1,251 阅读5分钟

I/O(英语:Input/Output),即输入/输出,通常指数据在存储器(内部和外部)或其他周边设备之间的输入和输出,是信息处理系统(例如计算机)与外部世界(可能是人类或另一信息处理系统)之间的通信。输入是系统接收的信号或数据,输出则是从其发送的信号或数据。

参考上图,简单概括:处理器访问任何寄存器和 Cache 等封装以外的数据资源都可以当成 I/O 操作,包括内存,磁盘,显卡等外部设备。

UNIX 定义的5种网络 I/O 模型

为了方便了解网络I/O,我们来探讨一下一条消息如何从A传输到B的。

基本过程:应用 A 通过TCP/IP协议,经过服务对应的发送缓冲区、网络传输、应用B的接收缓冲区。而后应用B从缓冲区内读取到消息。

这时我们将目光聚集到读取数据的地方,现在我们面临的问题是如何将缓冲区的数据读取到应用层。

如果你不是很理解专业术语,我们可以用一个小故事来讲解。

OK,我们现在将进行角色扮演,故事的背景是你在某网买了一个东西,然后等待包裹被快递送到家......

阻塞 I/O ( Blocking I/O )

由于你急切的想得到这个物品,所以你就一直站在门外等着,直到包裹被快递员送到。

使用系统调用,并一直阻塞直到内核将数据准备好,之后再由内核缓冲区复制到用户态,在等待内核准备的这段时间什么也干不了。

非阻塞 I/O ( Non-blocking I/O )

虽然你急切的想得到这个物品,但是你还有别的事情要做。所以,你打算回去做一会儿事情,然后一会儿再出来看看包裹到了没。一直反复,直到包裹到达。

内核在没有准备好数据的时候会返回错误码,而调用程序不会休眠,而是不断轮询询问内核数据是否准备好。

I/O 多路复用 ( I/O Multiplexing )

你有一个自动收包裹的机器人,它替你监控是否有包裹被送到。如果有它会通知你让你去拿。

多路复用类似与非阻塞,只不过轮询不是由用户线程去执行,而是由内核去轮询,内核监听程序监听到数据准备好后,调用内核函数复制数据到用户态。

信号驱动 ( Signal Driven I/O (SIGIO) )

你在门口安装了一个门铃,为你运送包裹的快递公司会在到你家时按一下门铃,如果你在,将会把包裹给你。如果碰巧你不在,那你的包裹就被丢弃了。

我们需要允许Socket进行信号驱动,并且安装一个信号处理函数,在系统进行通知时进行处理,但在大量 IO 操作时可能会因为信号队列溢出或其他不可控因素导致没法通知。

异步 I/O ( Asynchronous I/O )

你知道你拿到包裹后就把它放在桌子上,然后你就和快递员商讨好,当包裹到达时让他帮你放到桌子上。你则不需要关心包裹什么时候到达。

异步IO才是真正的非阻塞,主进程只负责做自己的事情,等IO操作完成(数据成功从内核缓存区复制到应用程序缓冲区)时通过回调函数对数据进行处理。

I/O 多路复用

下面我们讲一讲 I/O多路复用,这个最常用在服务器和网络编程中的模型。哦,对了,他还有一个别名,叫做事件驱动模型

继续我们的故事

你搬到新的小区。小区有一个收包裹的驿站,但包裹上没有名字,只有编号,你每次要把编号告诉工作人员,当包裹到达时他们会通知你拿快递。

SELECT / POLL

当有快递到达时,由于驿站工作人员不知道是谁告诉他的,它只能在你们小区挨家挨户的问,直到找到可以接收的人,或全部问完。

简单的概括,SELECT和 POLL 的机制都差不多,只是具体实现和一些小方面不同,所以我们放在一起讲述。

这种模型存在一个致命问题就是他需要轮询所有的流,来查询是谁的事件,由于查询的时间复杂度是O( n ),所以在多连接的情况下性能会很低。

EPOLL

当你告诉工作人员去帮你收包裹的时候他会把你的联系方式记录下来,这样在你的快递到了的时候工作人员可以直接联系你。

EPOLL 是对SELECT 和 POLL 的改进,避免了其需要轮训的问题。

但EPOLL只能在Liunx 系统下工作。

总结

这次我们了解了一下Unix 的5种网络 I/O 模型。并没有讲的太深入,只为让大家理解这方面的知识,如果大家感兴趣的话可以深入研究一下。日后有机会我也会面向操作系统深入讲解一下。

下周我们继续回归分布式方面的话题。