我个人对于网络编程有着强烈的兴趣,最近打算开始看 muduo 的网络库,了解到其构建了基于 Reactor 的事件处理机制,本质涉及 I/O 多路复用的知识。为了后续的理解,首先搞清楚一个有无数讲解却又令人费解的概念:阻塞和非阻塞、同步和异步。
首先需要搞清楚的一件事,就是对于 Linux 系统, I/O 操作不是一步完成的。此处的 I/O 操作是一个通用型的概念,对于 socket 通信,也可以看作一个 I/O 操作过程,只不过操作的是网络对象。
I/O 操作一般分为两个部分:
了解了这个大前提,我们再来看上述四个概念
首先明确一点:阻塞和非阻塞发生在请求处,关注的是程序在等待调用结果时的状态。
上面的概念非常重要,很明确地指出了阻塞的要点,应用程序进程(线程)进行 I/O 请求被阻塞了。很容易理解下面的结论:
理解上面概念的一个要点是请求的结果是否立即返回,同时需要注意的是,结果立即返回,不代表 I/O 操作完成,阻塞和非阻塞只关注请求是否立即获得结果。
默认的 socket 是阻塞的,用户进程发起 system call 之后,内核等待数据从 socket 接收,用户进程等待内核拷贝数据回用户缓冲区,整个过程中,进程调用函数是阻塞在调用处的,一直到数据拷贝到用户进程的缓冲区,返回数据的长度,调用完成,继续执行。
当使用 I/O 多路复用的时候,用户的 I/O 操作会立即返回,但会利用 select 和 epoll 等方法对所监视的 I/O 操作描述符进行遍历轮询(此操作是为了检查数据是否准备完毕,也就是 I/O 操作的第一部分,同时此操作是阻塞,进程或者线程需要等待轮询结果的返回),查看可用的句柄并返回。然后用户进程再对其进行操作。
I/O 操作是否阻塞,可以通过其文件描述符的状态来设定。
同样需要明确一点:同步和异步关注的是消息通信机制,具体来说就是调用者是否等待调用结果的返回,对于 I/O 操作而言,就是应用程序是否等待 I/O 操作完成。
注意:此处的 I/O 操作一般是指上文中 I/O 操作中的两部分的第二部分。
这一点该怎么理解呢?还是得结合上述的 I/O 操作的两步来理解。同步和异步其实就是指 I/O 操作的第二部分,也就是进行具体 I/O 操作过程中,用户进程是否等待 I/O 操作结果返回。
结合前面的概念,可以看出,其实这两者存在本质的区别,它们的修饰对象是不同的。
网上有一个银行办业务的例子说的很明白:
你去银行办业务,前面有人(数据未就绪),你可以自己排队(阻塞),也可以拿号然后干别的(非阻塞),然后隔一会询问一下是否到你了(轮询)。你可以自己去银行办这些事(同步),如果你工作比较忙,也可以委托银行的经理帮你办理这些事,然后告诉你结果(异步)。所以说只有同步的时候,才会有是否阻塞之说。
根据上述概念的组合,我们可以将常见的 I/O 操作归属不同的 I/O 模型:
- 阻塞 I/O (bloking I/O ) - 默认socket的 I/O 操作是阻塞的- 非阻塞 I/O (non-blocking I/O ) - 当 I/O 请求时,如果数据没有准备好,则返回一个错误,然后进程可以干其他事,隔段时间,重新请求,直到数据准备好,进入 I/O 操作,等待操作结果返回。- 多路复用 I/O (multiplexing I/O ) - 利用select和epoll等函数同时监视多个socket,本质上是非阻塞 I/O ,但这些监视函数在轮询时是阻塞的,因此将 I/O 请求阻塞在这些监视函数上了,用户进程不需要轮询数据是否准备好了。- 信号驱动式 I/O (signal-driven I/O ) - 系统会为监视的数据准备设置信号,用户进程进行 I/O 请求之后,不会阻塞,当内核数据准备完毕,会通过信号通知用户进程,随后进行 I/O 操作。- 异步 I/O - 向内核中传递 I/O 操作参数,并立即返回。内核 I/O 操作完毕后,通过回调函数的方式通知用户进程。 我正在参与2023腾讯技术创作特训营第三期有奖征文,组队打卡瓜分大奖!原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。
如有侵权,请联系 cloudcommunity@tencent.com 删除。