非阻塞式IO

一、概述

阻塞与非阻塞的概念

在用户进程或者线程进行系统调用进入内核的时候,如果不能马上满足条件,内核对调用者采取不同的行为,分为“阻塞”和”非阻塞“两种。”阻塞“表示如果系统调用不能马上获得满足而返回,就将调用者(进程或线程)挂起,等到满足条件,内核执行完系统调用,重新恢复调用者。”非阻塞“表示如果系统调用不能满足,则立即返回,并设置错误信息以告知调用者,该系统调用因何不能满足。

从以上看,”阻塞“虽然使用比较简单,但是很被动,会出现”永久阻塞“而导致无法处理的问题,设想一下,客户端进程阻塞等待接受服务器的消息,但是这个服务器因为某种原因崩溃了,如此客户端就会永久阻塞。相反,”非阻塞“技术给了程序员更大的灵活性,只要能够合理处理各种情况便能够更好的提高效率。

总之,非阻塞调用相对与阻塞调用有几个好处:

  • 不会出现“永久阻塞”(设置超时也可解决永久阻塞问题)
  • 同时监听多个IO复用的程序模型,如果使用阻塞调用,可能出现顾此失彼的情况。即,系统调用阻塞在某个IO上时,可能有另一个IO已经就绪,而它不能被及时响应。

也因为如此,使用非阻塞技术要体现这些优势。

网络编程背景下的非阻塞式IO

可能阻塞的套接字调用分为以下四类:

  • 输入操作(read、readv、recv、recvfrom、recvmsg五个函数)
  • 输出操作(write、writev、send、sendto、sendmsg五个函数)
  • 接受外来连接,accept函数
  • 发起外出连接,用于TCP的connect函数

二、非阻塞读和写

UNP给出一个数据,echo服务下的客户端不同版本str_cli函数,测试从一个Solaris客户主机想RTT为175毫秒的一个服务器主机复制2000行文本,所花时间的不同:

  • 354.0s,停等版本
  • 12.3s,select+阻塞式IO版本
  • 6.9s, select+非阻塞IO版本
  • 8.7s, fork版本(多进程)
  • 8.5s,多线程版本
    从这个数据可以看出:
  • 一、IO复用模型的使用确实大大加快了IO的处理
  • 二、IO复用模型搭配非阻塞技术可以再提高效率(但是会提高编程的复杂性)

UNP推荐:相对于select+非阻塞IO,最好使用效率差不多、但是编程更简单的多进程模型。

对UNP书上str_cli函数的select+非阻塞式IO的注解

本版本需要做的工作:

  • 维护两个缓冲区;
  • 使用selectIO复用,监听并处理四个IO;
  • 对所有监听的IO都设置成非阻塞IO;
  • 恰当处理终止条件,当服务器终止连接或者客户端标准输入得到EOF;

动态性强,但是编程要考虑的情况多。UNP提供了效率差不多,但是编程简单的fork版本。该版本做的工作较少:

  • fork出子进程,负责阻塞等待套接字可读,并将收到的消息输出到标准输出;
  • 父进程负责阻塞等待标准输入可读,并将收到的消息写入套接字;

三、非阻塞connect