网络IO模型总结
网络IO流程
网络IO中的一次请求和响应的流程基本如下:
- 内核通过网卡读取客户端的请求数据,将数据读取到内核缓冲区。数据从网卡到内核空间;
- 从内核缓冲区读取数据到应用进程缓冲区。数据从内核空间到用户空间;
- 服务端进程在自己的用户空间中,处理客户端的请求。数据在用户空间中被处理;
- 处理完数据并构建好的响应之后,将数据从用户缓冲区写入内核缓冲区。
- 内核将内核缓冲区中的响应写入网卡,网卡通过底层的通讯协议,会将数据发送给目标客户端。
两组概念:
- 同步:请求被逐个地处理,无法并发执行。
- 异步:多个请求可以并发执行,内核IO操作完成后会通知用户线程,或者调用用户进程的回调函数。
- 阻塞:请求发出后,由于该请求操作需要的条件不满足,请求操作一直阻塞,不会返回,直到条件满足。
- 非阻塞:请求发出后,若该请求需要的条件不满足,则立即返回一个标志信息告知条件不满足,而不会一直等待。一般需要通过循环判断请求条件是否满足来获取请求结果。
在《UNIX网络编程》中,将UNIX的IO模型分为了以下五种:
- 阻塞式IO
- 非阻塞式IO
- IO多路复用
- 信号驱动式IO
- 异步IO
阻塞式IO
该模型中,用户空间的应用程序通过执行read调用(底层是recvfrom系统调用)来从socket中读取数据,在应用程序发起read调用后,会一直阻塞,直到数据包到达网卡上并复制到内核空间中,随后从内核空间拷贝到用户空间之后才会返回。
BIO在实现异步操作时,只能使用多线程进行处理,一个请求对应一个线程,该模型对于高并发环境,开销十分巨大,需要考虑其他的IO处理模型。
非阻塞式IO
应用程序发起系统调用,如果内核数据暂未准备好,进程可以做其他事,然后再次轮询内核获取请求结果。
简单的NIO需要不断的重复发起IO系统调用,这种不断地询问内核的操作,这将占用大量的 CPU 时间,并导致上下文切换,系统资源利用率较低。
IO多路复用
IO多路复用模型通过一个监听线程发起另一种形式的系统调用,由一个线程监听多个文件描述符(fd,linux系统把所有网络请求以一个fd来标识),一旦某个fd的操作就绪(一般是内核缓冲区可读/可写),该系统调用就会返回,随后监听线程可以通知程序对准备好了的fd进行对应的IO系统调用,比如通过recvfrom读取数据。
在Linux中select、poll、epoll函数就是IO多路复用的具体实现。Java4新增的NIO包中引入的选择器Selector,使用的就是IO多路复用模型,通过它,只需要一个线程便可以管理多个客户端连接。
信号驱动IO
应用程序通过sigaction系统调用安装一个信号处理函数,应用程序继续工作,当数据准备好后,内核给进程发送一个SIGIO
信号,应用程序开始执行系统调用执行IO操作。
这种模型的优势在于等待数据达到期间,进程不被阻塞。只需要等待信号处理函数的通知。
异步IO
AIO的基本流程是:用户线程通过系统调用,告知内核启动某个IO操作,用户线程随即返回。内核在整个IO操作(包括数据准备、数据复制)完成后,会通知用户程序,用户执行后续的业务操作。
与信号驱动式IO的区别:信号驱动模型是由内核通知我们何时可以启动一个IO操作,而异步IO模型由内核通知我们IO操作何时完成。信号驱动IO更像半异步IO。
参考
《UNIX网络编程》