我对网络IO的理解

Unix/linux系统下IO主要分为磁盘IO,网络IO,我今天主要说一下对网络IO的理解,网络IO主要是socket套接字的读(read)、写(write),socket在Linux系统被抽象为流(stream) 。
网络IO模型在Unix/Linux系统下,IO分为两个不同阶段:

  • 等待数据准备好
  • 从内核向进程复制数据
阻塞式I/O
阻塞式I/O(blocking I/O)是最简单的一种,默认情况下,socket 套接字的系统调用都是阻塞的,我以recv/recvfrom 理解一下网络IO的模型 。当应用层的系统调用recv/recvfrom时,开启Linux的系统调用,开始准备数据,然后将数据从内核态复制到用户态,然后通知应用程序获取数据,整个过程都是阻塞的 。两个阶段都会被阻塞 。
我对网络IO的理解

文章插图
阻塞I/O模型
图片来源于《Unix网络编程卷1》
阻塞I/O下开发的后台服务,一般都是通过多进程或者线程取出来请求,但是开辟进程或者线程是非常消耗系统资源的,当大量请求时,因为需要开辟更多的进程或者线程有可能将系统资源耗尽,因此这种模式不适合高并发的系统 。
非阻塞式I/O
非阻塞IO(non-blocking I/O)在调用后,内核马上返回给进程,如果数据没有准备好,就返回一个error,进程可以先去干其他事情,一会再次调用,直到数据准备好为止,循环往返的系统调用的过程称为轮询(pool),然后在从内核态将数据拷贝到用户态,但是这个拷贝的过程还是阻塞的 。
我还是以recv/recvfrom为例说一下,首选需要将socket套接字设置成为非阻塞,进程开始调用recv/recvfrom,如果内核没有准备好数据时,立即返回给进程一个error码(在Linux下是EAGINE的错误码),进程接到error返回后,先去干其他的事情,进入了轮询,只等到数据准备好,然后将数据拷贝到用户态 。
需要通过ioctl 函数将socket套接字设置成为非阻塞
ioctl(fd, FIONBIO, &nb);
我对网络IO的理解

文章插图
非阻塞I/O模型
图片来源于《Unix网络编程卷1》
非阻塞I/O的第一阶段不会阻塞,但是第二个阶段将数据从内核态拷贝到用户态时会有阻塞 。在开发后台服务,由于非阻塞I/O需要通过轮询的方式去知道是否数据准备好,轮询的方式特别耗CPU的资源 。
I/O多路复用
在Linux下提供一种I/O多路复用(I/O multiplexing)的机制,这个机制允许同时监听多个socket套接字描述符fd,一旦某个fd就绪(就绪一般是有数据可读或者可写)时,能够通知进程进行相应的读写操作 。
在Linux下有三个I/O多路复用的函数Select、Poll、Epoll,但是它们都是同步IO,因为它们都需要在数据准备好后,读写数据是阻塞的 。
我对网络IO的理解

文章插图
I/O多路复用模型
图片来源于《Unix网络编程卷1》
I/O多路复用是Linux处理高并发的技术,Epoll比Select、Poll性能更优越,后面会讲到它们的区别 。优秀的高并发服务例如Nginx、redis都是采用Epoll+Non-Blocking I/O的模式 。
信号驱动式I/O
信号驱动式I/O是通过信号的方式通知数据准备好,然后再讲数据拷贝到应用层,拷贝阶段也是阻塞的 。
我对网络IO的理解

文章插图
信号驱动式I/O
图片来源于《Unix网络编程卷1》
异步I/O
 
异步I/O(asynchronous I/O或者AIO),数据准备通知和数据拷贝两个阶段都在内核态完成,两个阶段都不会阻塞,真正的异步I/O 。
进程调用read/readfrom时,内核立刻返回,进程不会阻塞,进程可以去干其他的事情,当内核通知进程数据已经完成后,进程直接可以处理数据,不需要再拷贝数据,因为内核已经将数据从内核态拷贝到用户态,进程可以直接处理数据 。
我对网络IO的理解

文章插图
异步I/O模型
图片来源于《Unix网络编程卷1》
Linux对AIO支持不好,因此使用的不是太广泛 。
同步和异步区别、阻塞和非阻塞的区别同步和异步区别
对于这两个东西,POSIX其实是有官方定义的 。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;
一个同步I/O操作会引起请求进程阻塞,只到这个I/O请求结束 。


推荐阅读