Netty学习(1)——IO多路复用



Linux IO模型介绍

网络IO实际上就是通过socket对数据进行读取,对于一次socket中的IO操作(例如read),数据首先会被复制到操作系统内核的缓存区,然后再从内核缓存区复制到应用程序所在的用户空间。所以一个read操作,需要经过两个步骤:

1.等待数据被复制到内核缓存区
2.操作系统将缓存区中的数据拷贝到用户空间

Linux中的IO模型一般分为5种:

阻塞IO(Blocking IO), 非阻塞IO(Non-blocking IO), 多路复用IO(Mutiplexing IO), 信号驱动IO(Signal-driven IO), 异步IO(Asynchronous IO)

接下来简单地介绍一下这5种IO模型:

阻塞IO(Blocking IO)

假如锅里正在烧水,同时你正想要洗衣服。因为你不知道锅里的水什么时候烧开,那么你就要先等水烧开,再去洗衣服,这就是阻塞IO,只能等一件事情完成了才能再去做第二件...

阻塞IO是最常用且最简单的一个IO模型,在这个IO模型中,用户空间的应用程序执行一个系统调用之后,这会导致应用程序阻塞,什么也不干,直到数据准备好,并且由操作系统将数据从内核复制到用户进程之后,进程才从阻塞状态恢复,从而处理数据。

大致的流程如图:

阻塞IO

非阻塞IO(Non-blocking IO)

同样锅里正在烧水,你也正想要洗衣服,但是你不想等到水烧开了再去洗。于是想出一个主意,我洗一会儿衣服就过来看一眼水烧开了没有,这个动作就叫做轮询,这种模式就叫做非阻塞IO

非阻塞的应用程序调用非阻塞IO之后,内核会立刻返回给调用进程,因此并没有被阻塞,内核马上返回给进程,如果数据还没准备好,此时会返回一个error。进程在返回之后,可以干点别的事情,然后再发起系统调用。重复上面的过程,循环往复的进行系统调用。这个过程通常被称之为轮询。轮询检查内核数据,直到数据准备好,再拷贝数据到进程,进行数据处理。

大致的流程如图:

非阻塞IO

IO多路复用(IO Mutiplexing)

这一次使用锅来烧水,使用洗衣机来洗衣服,你还有工作要忙就去工作了,但是你请了一位小管家来帮你照看,不过这个管家只负责看并不会主动告知你。于是你可以主动地问管家,有事情已经完成了吗?管家可以告诉你有或没有,如果有的话,你就可以主动去看看哪件事完成了,而继续下一步的操作。

对于非阻塞IO而言,需要通过轮询的方式不断询问,会占用大量的CPU时间,对于请求众多的情况,对性能的影响就更加明显。于是就有人想到,对于那么多请求,可以只用一个单一的线程去查询各任务的状态,这就是IO多路复用了。Linux系统中的select/poll和epoll就是用来做这个工作的,本文主要也就是介绍三种IO多路复用的机制。

IO多路复用的流程如图:

IO多路复用

信号驱动IO(Signal-driven IO)

如果使用热水壶来烧水,自动洗衣机来洗衣服,我们就可以安心地做自己的工作。因为热水烧开时,会发出尖锐的声音,洗衣机洗完衣服之后也会发出提示,我们可以在收到提示之后去继续下一步的操作。

如果我们允许Socket进行信号驱动IO,并安装一个信号处理函数,进程继续运行并不阻塞。当数据准备好时,进程会收到一个SIGIO信号,可以在信号处理函数中调用I/O操作函数处理数据。

大致流程如图:

信号驱动IO

异步IO(Asynchronous IO)

这次请了一位能干一点的管家,你只需要跟他说,帮我烧壶水,顺便把衣服洗了,然后你就可以继续自己的工作了,剩下的事情他会处理好,并在处理好之后告诉你。

相对于同步IO,异步IO不是顺序执行。用户进程进行系统调用之后,无论内核数据是否准备好,都会直接返回给用户进程,然后用户态进程可以去做别的事情。等到socket数据准备好了,内核直接复制数据给进程,然后从内核向进程发送通知。

大致流程如图:

异步IO

IO多路复用的3种机制

IO多路复用是指内核一旦发现进程指定的一个或者多个IO条件已经准备就绪,它就通知该进程。IO多路复用适用如下场合:

  (1)当客户处理多个描述字时(一般是交互式输入和网络套接口),必须使用I/O复用。

  (2)当一个客户同时处理多个套接口时,而这种情况是可能的,但很少出现。

  (3)如果一个TCP服务器既要处理监听套接口,又要处理已连接套接口,一般也要用到I/O复用。

  (4)如果一个服务器即要处理TCP,又要处理UDP,一般要使用I/O复用。

  (5)如果一个服务器要处理多个服务或多个协议,一般要使用I/O复用。

  与多进程和多线程技术相比,I/O多路复用技术的最大优势是系统开销小,系统不必创建进程/线程,也不必维护这些进程/线程,从而大大减小了系统的开销。

select

该函数准许进程指示内核等待多个事件中的任何一个发送,并只在有一个或多个事件发生或经历一段指定的时间后才唤醒。

select的调用过程如下图所示:
select调用过程

首先由进程调用read、write等IO函数,然后通过select函数查询其文件描述符是否就绪,此时进程可以选择阻塞等待或超时等待,一旦文件描述符就绪,就立刻返回,供进程执行下一步操作。

select函数有以下缺点:

1.每次调用select,都需要把fd集合从用户态拷贝到内核态,这个开销在fd很多时会很大

2.同时每次调用select都需要在内核遍历传递进来的所有fd,这个开销在fd很多时也很大

3.select支持的文件描述符数量太小了,默认是1024

poll

poll的机制与select类似,与select在本质上没有多大差别,管理多个描述符也是进行轮询,根据描述符的状态进行处理,但是poll没有最大文件描述符数量的限制。

相对于select而言,poll最大的优势就是没有文件描述符的数量限制,其他的都差不多。

epoll

epoll是在2.6内核中提出的,是之前的select和poll的增强版本。相对于select和poll来说,epoll更加灵活,没有描述符限制。epoll使用一个文件描述符管理多个描述符,将用户关系的文件描述符的事件存放到内核的一个事件表中,这样在用户空间和内核空间的copy只需一次。

epoll函数解决了select函数的3个缺点

首先epoll是通过epoll_create创建句柄的,而不像select函数是在每次调用时才将所有担心拷贝到内核的,因此epoll在创建之后,才通过epoll_ctl将一个个事件拷贝到内核,只拷贝了一次,而select是在每次调用的时候都要将所有的文件描述符拷贝到内核。

对于第二个缺点,epoll是通过给每个fd添加一个回调函数,当这个fd就绪之后就调用这个回调函数将就绪的fd加入到一个列表中来实现的;而select则通过一次次轮询来检查是否就绪

对于第三个缺点,epoll一般没有限制,epoll所能支持的上限一般由机器决定

关于三种多路复用机制,更加深入的讲解可以参考:

selectpollepoll


-------------本文结束感谢您的阅读-------------
0%