如何理解网络编程中的阻塞、非阻塞I/O和同步、异步I/O

本文章只单纯讨论网络编程中的阻塞、非阻塞 I/O和同步、异步I/O,并不是广义上定义的对比。因为这两种说法在某些方面领域其实意思很接近,甚至可以混用,很难总结出一套说法适用于所有情况。但在用的较多的网络编程方面,这两种说法的区别还是很明确的。

我们只要把握住核心的两点就可以很容易区分出到底属于哪种I/O操作:

以网络编程中的recv数据过程为例,该过程可以简单理解为将内核空间收到的网络数据拷贝到用户空间。

1. 是用户进程负责将内核空间数据拷贝到用户空间,还是内核进程负责这个拷贝过程。

  • 如果是用户进程负责拷贝,则是同步I/O;
  • 如果是内核进程负责拷贝,则是异步I/O;

2. 如果内核空间的数据还没有准备好,那么recv是等待数据准备好然后拷贝;还是立刻返回错误,告诉用户进程内核数据还没准备好。(拷贝过程实际是由用户进程负责)

  • 如果recv一直等待数据准备好,完成拷贝后才返回,则是阻塞I/O;
  • 如果内核数据没有准备好,recv立刻返回错误(如EAGAIN错误),则是非阻塞I/O;

可以看到,无论是阻塞recv还是非阻塞recv,它从内核空间向用户空间拷贝数据的过程其实都是由用户进程负责的,也就是我们的应用程序进程,所以说无论阻塞还是非阻塞,其实都是同步操作。

非阻塞recv相比非阻塞只是多了一种处理方式:如果数据未准备好,立刻返回错误,而这时候内核空间数据并没有向用户空间进行拷贝。等到数据准备好,非阻塞recv和阻塞式recv一样,都需要一段时间将数据从内核空间拷贝到用户空间,这个过程用户进程只能负责这一件事,就是拷贝数据。所以说阻塞、非阻塞I/O 都是同步操作。

异步I/O则完全不同,因为它其实将这个拷贝过程交给了内核进程去做,用户进程并不参与这个拷贝过程,它只需要等内核进程拷贝完毕给我们发个完成信号或事件就行了。在数据拷贝的这段时间,用户进程可以进行其他工作。

linux虽然已支持异步I/O,不过当前网络编程中大多还是采用的 select/poll/epoll + send/recv 的 多路I/O复用+同步I/O 模式,异步I/O暂时用的还很少。不过显然异步操作是趋势,也是提高服务器性能的关键。linux下的异步I/O还是需要大型工程项目背书才能扩展开来。

以下图片截取自《UNIX网络编程 卷一 第三版》

阻塞I/O模型

如何理解网络编程中的阻塞、非阻塞I/O和同步、异步I/O

 

非阻塞式I/O模型

如何理解网络编程中的阻塞、非阻塞I/O和同步、异步I/O

 

异步I/O模型

如何理解网络编程中的阻塞、非阻塞I/O和同步、异步I/O