Non-blocking I/O,同步非阻塞。
- NIO设计初衷:一个线程处理所有客户端连接。
网络编程模型 | 处理单元 | 监听 |
---|---|---|
BIO | 处理单元是一个Socket。比如多线程BIO,来一个Socket创建一个线程,来进行处理 | 主动去监听,是否有客户端来连接我,所以服务端会堵塞在轮询中。 |
NIO | 处理单元是事件,一个事件发生,调用相应的方法,进行处理 | 不主动监听,等有客户端连接时,你来通知我 |
背景
一、多线程BIO对资源要求太高
系统中创建线程是需要比较高的系统资源的,如果连接数太高,系统无法承受,而且,线程的反复创建-销毁也需要代价。
二、用线程池优化的BIO,效果不错,但线程粒度太大
线程池本身可以缓解线程创建-销毁的代价,这样优化确实会好很多,不过还是存在一些问题的,就是线程的粒度太大
- 每一个线程把一次交互的事情全部做了,包括读取和返回(甚至连接,表面上似乎连接不在线程里)
- 但如果连接、读取、返回,这三件事不是连续完成的,之间间隔了很久,那这个线程就会堵塞,浪费资源。
- 如果其他线程都堵在了读取上,而且没有剩余的线程了,新的连接就无法得到处理
线程同步的粒度太大了,限制了吞吐量
三、把一次连接拆成粒度更小的事情
可以把一次连接拆分成更细粒度的事情:连接、读取、接入。
一次(或一个线程)只处理一件事情,那线程就很快能腾出手干别的事情,不会闲着。
四、Reactor的出现
拆解成连接、读取、接入三个事情,这其实就是以事件的视角看待这件事情了,就可以使用事件驱动模型。
在Reactor中,这些被拆分的小线程或者子过程对应的是handler,每一种handler会出处理一种event。
这里会有一个全局的管理者selector,我们需要把channel注册感兴趣的事件,那么这个selector就会不断在channel上检测是否有该类型的事件发生,如果没有,那么主线程就会被阻塞,否则就会调用相应的事件处理函数即handler来处理。
典型的事件有连接,读取和写入,当然我们就需要为这些事件分别提供处理器,每一个处理器可以采用线程的方式实现。一个连接来了,显示被读取线程或者handler处理了,然后再执行写入,那么之前的读取就可以被后面的请求复用,吞吐量就提高了。