一、I/O 模型对比

图片描述

二、理解阻塞I/O 和 非阻塞I/O

  socket 在创建的时候默认是阻塞的,也可以通过命令将其设置为非阻塞的。阻塞和非阻塞的概念能应用于所有文件描述符,而不仅仅是 socket。我们称阻塞的文件描述符为阻塞 I/O,称非阻塞的文件描述符为非阻塞I/O。

1. 阻塞 I/O

  针对阻塞 I/O 执行的系统调用可能因为无法立即完成而被操作系统挂起,直到等待的事件发生为止。比如,tcp 客户端通过 connect 向服务器发起连接时, connect 将首先发送同步报文段给服务器,然后等待服务器返回确认报文段。如果服务器的确认报文段没有立即到达客户端,则 connect 调用将被挂起,直到客户端收到确认报文段并唤醒 connect 调用。socket 的基础 API 中,可能被阻塞的系统调用包括 accept、send、recv 和 connect。

2. 非阻塞 I/O

  针对非阻塞 I/O执行的系统调用则总是立即返回,而不管事件是否已经发生。如果事件没有立即发生,这么系统调用就返回 -1,和出错的情况一样。此时我们必须根据 errno 来区分这两种情况。对 acccept、send 和 recv 而言,事件未发生时 errno 通常被设置成 EAGAIN (意为“再来一次”) 或者 EWOULDBLOCK (意为“期望阻塞”);对 connect 而言,errno 则被设置成 EINROGRESS (意为“在处理中”)。
  很显然,只有在事件已经发生的情况下操作非阻塞 I/O (读、写等),才能提高程序的效率。因此,非阻塞 I/O 通常要和其他 I/O 通知机制一起使用,比如 I/O 复用和 SIGIO 信号。

三、理解 I/O 复用机制和 SIGIO 信号机制

1. I/O 复用

  I/O 复用是最常用的 I/O 通知机制。它指的是,应用程序通过 I/O 复用函数向内核注册一组事件,内核通过 I/O 复用函数把其中就绪的事件通知给应用程序。Linux 上常用的 I/O 复用函数是 select、poll 和 epoll_wait。需要指出的是,I/O 复用函数本身是阻塞的,它们能提高程序效率的原因在于它们具有同时监听多个 I/O 事件的能力。

2. SIGIO 信号

  SIGIO 信号也可以用来报告 I/O 事件。我们可以为一个目标文件描述符指定宿主进程,那么被指定的宿主进程将捕获到 SIGIO 信号 。这样,当目标文件描述符上有事件发生时,SIGIO 信号的信号处理函数将被触发,我们也就可以在该信号处理函数中对目标文件描述符执行非阻塞 I/O 操作了。

四、 理解同步I/O 和异步 I/O

  阻塞 I/O、I/O复用 和 信号驱动 I/O 都是同步 I/O 模型。因为在这三种 I/O 模型中,I/O 的读写操作,都是在 I/O 事件发生之后,由应用程序来完成的。而 POSIX 规范所定义的异步模型不同。对异步I/O 而言,用户可以直接对 I/O 执行读写操作,这些操作告诉内核用户读写缓存区的位置,以及 I/O 操作完成之后内核通知应用程序的方式。也就是说,同步 I/O 模型要求用户代码自行执行 I/O 操作(将数据从内核缓冲区读入用户缓冲区,或将数据从用户缓冲区写入内核缓冲区),而异步 I/O 机制则由内核来执行 I/O 操作(数据在内核缓冲区和用户缓冲区之间的移动是由内核在“后台”完成的)。你可以这样认为,同步 I/O 向应用程序通知的是 I/O 就绪事件,而异步 I/O 向应用程序通知的是 I/O 完成事件。


waterandair
1.3k 声望78 粉丝

落棋不悔