一、经典的I/O服务设计 ———— BIO模式
1、流程:
(1)、服务器端的Server是一个线程,通过死循环来阻塞的监听客户端的连接请求和通信。
(2)、当客户端向服务器端发起连接请求,服务器端通过ServerSocket.accept()阻塞
返回一个Socket对象,此Socket对象和对应的客户端建立连接。
(3)、构建一个handler,将Socket传入该handler(处理程序),
并创建一个线程来执行这个handler。
(4)、当该客户端和服务器端完成通信关闭连接后,这个线程就会被销毁。
(5)、然后服务器端Server继续执行accept()操作等待新的客户端连接请求。
2、总结:
服务器端有个主线程,通过死循环的方式监听客户端的连接请求,一旦有客户端连接请求,
服务器端的Server就通过ServerSocket.accept()
创建一个服务器端socket对象与客户端的socket对象连接。
然后构建一个handler(处理器),
这个处理器里面包含了处理逻辑和服务器端socket对象,
通过服务器端socket对象读取数据或推送数据,执行相应的逻辑。
一个handler是在一个新的线程里面执行的。
也就是一个连接请求开启一个线程,后续的read/write这些操作也都在这个线程中完成。
缺点:
可能有多数线程处于空闲状态,浪费资源。
线程之间切换,线程的开启销毁都是影响效率的。
二、NIO模式(多路IO复用模型)
1、流程
(1)、服务器端有个主线程,通过死循环的方式监听客户端的连接请求,
一旦有客户端连接请求,服务器端的Server就通过ServerSocket.accept()
创建一个服务器端socket对象与客户端的socket对象连接。
(2)、将socket对象设置为非阻塞,加入到一个集合里面。
(3)、开启一个线程,去循环监听这些非阻塞的socket对象,
当某个socket对象准备好了数据,
就开启一个线程去将系统内存的数据读取到用户线程内存里面。
2、总结
和同步非阻塞IO模型很像,只不过同步非阻塞IO是在用户线程里面去轮询socket
是否准备好数据,而多路复用IO是利用select调用系统内核在轮询。
三、单Reactor单线程模式
1、流程:
(1)、服务器端的Reactor对象实际上是个线程对象,
这个线程循环监听非阻塞的socket对象,实际上是调用了系统内核的select函数。
(2)、注册一个Acceptor事件处理器到Reactor中,用于监听客户端的连接请求
(3)、收到客户端连接请求,Acceptor处理器通过accept()方法得到与客户端相对应的
SocketChannel。
(4)、然后将该SocketChannel连接对应的READ事件处理器注册到Reactor中。
(5)、当Reactor监听到有读或者写事件发生时,将相关的事件派发给对应的处理器
进行处理。
(6)、每当处理完所有就绪的感兴趣的I/O事件后,
Reactor线程会再次执行select()阻塞等待新的事件就绪并将其分派给
对应处理器进行处理。
2、总结
实际上就是对多路IO复用模型的封装,Reactor对象循环监听执行对应的回调函数,
将处理客户端连接的Acceptor注册到Reactor对象里面,
将该SocketChannel连接对应的READ事件处理器注册到Reactor对象里面
当Reactor对象监听到相关事件发生后,
就会执行之前已经注册的处理器的回调函数进行处理。
客户端所有的请求处理,不管是IO还是业务操作都在一个线程里面完成。
四、单Reactor多线程模式
流程:
1、和上面的单Reactor单线程模式差不多,区别就在于
添加了一个工作者线程池,并将非I/O操作从Reactor线程中移出转交给
工作者线程池来执行。这样能够提高Reactor线程的I/O响应,
不至于因为一些耗时的业务逻辑而延迟对后面I/O请求的处理。
总结:
将业务操作方法哦线程池里面去执行,缓解了IO处理的压力,不至于因为一些耗时的
业务逻辑而延迟对后面I/O请求的处理。
五、多Reactor多线程模式
和上面的单Reactor多线程模式差不多,只不过是进一步把客户端连接操作和其它的IO
操作再次拆分了,mainReactor线程处理连接操作,subReactor处理其它业务操作。
流程:
(1)、注册一个Acceptor事件处理器到mainReactor中,
mainReactor去循环监听客户端连接事件。
(2)、mainReactor监听到客户端连接事件后,执行回调函数,生成SocketChannel。
将SocketChannel传递给subReactor。
(3)、subReactor线程池分配线程给这个SocketChannel,这样就实现了subReactor线程
对该SocketChannel IO操作的监听。
(4)、当监听到有I/O事件就绪时,该subReactor线程就会执行对应的read方法。
注意,这里subReactor线程只负责完成I/O的read()操作,
在读取到数据后,将业务逻辑的处理放入到线程池中完成。
若完成业务逻辑后需要返回数据给客户端,
则相关的I/O的write操作还是会被提交回subReactor线程来完成。
(5)、所以的I/O操作(包括,I/O的accept()、read()、write()以及connect()操作)
依旧还是在Reactor线程(mainReactor线程 或 subReactor线程)中完成的。
Thread Pool(线程池)仅用来处理非I/O操作的逻辑。
总结:
分离了客户端连接请求和其它IO请求,
解决了因为read()数据量太大而导致后面的客户端连接请求得不到即时处理的情况。
并且多Reactor线程模式在海量的客户端并发请求的情况下,
还可以通过实现subReactor线程池来将海量的连接分发给多个subReactor线程,
在多核的操作系统中这能大大提升应用的负载和吞吐量。
六、Netty模式
Netty框架实际上就是实现了多Reactor多线程模式
mainReactor ———— bossGroup(NioEventLoopGroup) 中的某个NioEventLoop
subReactor ———— workerGroup(NioEventLoopGroup) 中的某个NioEventLoop
acceptor ———— ServerBootstrapAcceptor
ThreadPool ———— 用户自定义线程池
1、流程:
(1)、服务器端有两个线程池,(事件循环组),bossGroup负责监听客户端连接,
wokerGroup负责监听网络读写。
(2)、每个线程池组里面都含有多个NioEventLoop(事件循环线程),
就是一个死循环,不停的监听。
(3)、每个NioEventLoop(事件循环线程)中有两个对象,
一个是selector,负责监听绑定在其上的socket的IO事件,
一个是taskQueue,负责来存放没来得及处理的IO事件。
(4)、bossGroup里面的NioEventLoop监听到了客户端连接事件,做以下三个操作:
<1>、轮询accpet事件(客户端连接事件)。
<2>、监听到了连接请求,就通过accpet(),生成一个nioSocketChannel
(连接通道),将其注册到workGroup里面的某个NioEventLoop的selector
<3>、继续处理任务队列。
(5)、workGroup里面的NioEventLoop监听到了连接事件,做以下三个操作:
<1>、轮询每个NioEventLoop中的selector是否有read、write事件发生
<2>、监听到了事件就执行,利用selector上绑定的niosocketchannel
对应的pipeline中的channelhandler进行处理。
<3>、继续处理任务队列。
(6)、一个niosocketchannel会对应一个pipeline,
pipeline(管道)里面有多个channelhandler(处理器),
也就是通过一系列channel(通道)里绑定的handler进行IO处理。