张东轩的博客

合抱之木,生于毫末;九层之台,起于累土;千里之行,始于足下。

0%

网络编程中的I/O多路复用 - I/O Models

实现IO多路复用的方法主要是依赖系统提供的selectpoll方法,在对它们进行描述之前,我们需要回过头看一下IO多路复用出现的大背景,先了解一下Unix提供给我们的五种I/O模型:

  • blocking I/O
  • nonblocking I/O
  • I/O multiplexing(select and poll)
  • signal driven I/O(SIGIO)
  • asynchronous I/O(the POSIX aio_functions)

阻塞式IO

要理解IO多路复用,首先要了解普通的IO模型,通常的IO操作都是阻塞式IO,当对socket句柄调用recv时,如果没有收到数据,那么线程就会被挂起,收到数据时被唤醒继续执行。

如果server要处理N个Socket连接,那么就需要开N个线程来处理这N个连接,recv会使大部分线程进入阻塞状态,如果N很大,那么光是创建这些线程就会消耗大量的内存空间;
另外大量的线程间切换也会会导致每个线程能分到的CPU时间较少,使程序的性能变差。
所以阻塞式IO只适用并发量小的网络应用开发。

非阻塞式IO

非阻塞IO顾名思义就是调用IO方法不会阻塞线程,例如在调用recv的时候,如果内核缓冲区有数据就返回数据,没有数据就返回例如EWOULDBLOCK的错误码。设置为非阻塞的方法也比较简单,可以通过fcntl(POSIX)或ioctl(Unix)设为句柄设置为非阻塞。虽然这样不会阻塞了,但是还是需要不断的对众多句柄调用recv方法进行轮训,这样会消耗大量的CPU资源。

非阻塞式IO适用并发量较小、且不需要及时响应的网络应用开发;

I/O多路复用

多路复用是使用一个线程来检查多个socket描述符的ready状态,比如调用select函数,传入多个socket描述符,有任意一个socket描述符对应的IO在内核缓冲区有数据时就返回,否则会阻塞直到超时。对ready的socket描述符进行recv时,可以放到一个线程,也可以新开线程执行。

实现I/O多路复用的三种方案: selectpollepoll;

特点:

  • 专一进程解决多个进程IO的阻塞问题,性能好;
  • 实现、开发应用难度较大;
  • 适用高并发服务应用开发:一个进程(线程)响应多个请求;

信号驱动IO模型

当进程发起一个IO操作,会向内核注册一个信号处理函数,然后进程返回不阻塞;当内核数据就绪时会发送一个信号给进程,进程便在信号处理函数中调用IO读取数据。

特点:回调机制,实现、开发应用难度大;

异步IO模型

当进程发起一个IO操作,进程返回(不阻塞),但也不能返回果结;内核把整个IO处理完后,会通知进程结果。如果IO操作成功则进程直接获取到数据。 异步IO和信号驱动的IO主要的区别是,信号驱动的IO是内核告诉我们这个IO操作可以开始了,但是异步IO是内核告诉我们IO操作已经完成了。

几种IO模型的比较

POSIX defines these two terms as follows:
  • A synchronous I/O operation causes the requesting process to be blocked until that I/O operation completes.
  • An asynchronous I/O operation does not cause the requesting process to be blocked.

Using these definitions, the first four I/O models—blocking, nonblocking, I/O multiplexing, and signal-driven I/O—are all synchronous because the actual I/O operation (recvfrom) blocks the process. Only the asynchronous I/O model matches the asynchronous I/O definition.