深入的聊聊Java NIO
1. 原始Ractor模式
相关组件的解释
-
Handle(句柄或是描述符):本质上表示一种资源,是操作系统提供的;该资源用于表示一个个
事件
,比如文件描述符,或者是针对于网络编程中的Socket
描述符。事件既可以来自于外部,也可以来自内部;外部事件比如说客户端的连接请求,客户端发送过来数据等;内部事件比如说操纵系统产生的定时器事件等。它本质上就是一个文件描述符。Handle
是事件产生的发源地。 -
Synchronous Event Demultiplexer(同步事件分离器):它本身是一个系统调用,用于等待事件的发生(事件可能是一个,也可能是多个)。调用方在调用它的时候会被阻塞,一直阻塞到同步事件分离器上有事件产生为止。对于
Linux
来说,同步事件分离器指的就是常用的I/O
多路复用机制,比如说select
、poll
、epoll
等。在Java NIO
中,同步事件分离器对应的组件就是Selector
;对应的阻塞方法就是select
方法。 -
Event Handler(事件处理器) 本身由多个回调方法构成,这些回调构成了与应用相关的对于某个事件的反馈机制。
Netty
相比于Java NIO
来说,在事件处理器这个角色上进行一个升级,它为我们开发者提供了大量的回调方法,供我们在待定事件产生时实现相应的回调方法进行业务逻辑的处理。 -
Concrete Event Handler(具体事件处理器):它本身实现了事件处理所提供的各个回调方法,从而实现了特定于业务的逻辑。它本质上就是我们所编写的一个个的处理器实现。
-
Initiation Dispatcher(初始分发器):实际上就是
Reactor
角色。它本身定义了一些规范,这些规范用于控制事件的调度方式,同时又提供了应用进行事件处理器的注册、删除等。Initiation Dispatcher
会通过同步事件分离器来等待事件的发生,一旦事件发生,Initiation Dispatcher
首先会分离出每一个事件,然后调用事件处理器,最后调用相关的回调方法来处理事件。
执行流程分析
-
当应用像
Initiation Dispatcher
注册具体的事件处理器时,应用会标识出事件处理器希望Initiation Dispatcher
在某个事件发生时向其通知该事件,该事件与Handle
关联。 -
Initiation Dispatcher
会要求每个事件向其传递内部的Handle
。该Handle
向操作系统标识了事件处理器。 -
当所有事件处理器注册完毕后,应用会调用
handle_events
方法来启动Initiation Dispatcher
的事件循环。这时,Initiation Dispatcher
会将每个注册的事件管理器的Handle
合并起来,并使用同步事件分离器等待这些事件的发生。比如说,TCP
协议层使用select
同步事件分离器操作来等待客户端发送的数据到达连接的socker handle
上。 -
当与某个事件源对应的
Handle
变为ready
状态时(比如说,TCP socker
变为等待读状态时),同步事件分离器就会通知Initiation Dispatcher
。 -
Initiation Dispatcher
会触发事件处理器的回调方法,从而响应这个处于ready
状态的Handle
。Initiation Dispatcher
会回调事件处理器的handle_events
回调方法来执行特定于应用的功能(开发者自己所编写的功能),从而响应这个事件。所发生的事件类型可以作为该方法参数并被该方法内部使用来执行额外的特定于服务的功能。
以上描述的内容似乎和本文的标题不大,其实不然,它正是下面介绍的内容的开端。
2. 通过一个例子拉近与Java NIO的距离
/**
* @Author CoderJiA
* @Description NIOServer
* @Date 13/2/19 下午4:59
**/
public class NIOServer {
public static void main(String[] args) throws Exception{
// 1.创建ServerSocketChannel
ServerSocketChannel serverSocketChannel = ServerSocketChannel.open();
serverSocketChannel.configureBlocking(false);
ServerSocket serverSocket = serverSocketChannel.socket();
serverSocket.bind(new InetSocketAddress(8899));
// 2.创建Selector,并ServerSocketChannel注册OP_ACCEPT事件,接收连接。
Selector selector = Selector.open();
serverSocketChannel.register(selector, SelectionKey.OP_ACCEPT);
// 3.开启轮询
while (selector.select() > 0) {
// 从selector所有事件就绪的key,并遍历处理。
Set<SelectionKey> selectionKeys = selector.selectedKeys();
selectionKeys.forEach(selectionKey -> {
SocketChannel client;
try {
if (selectionKey.isAcceptable()) { // 接受事件就绪
// 获取serverSocketChannel
ServerSocketChannel server = (ServerSocketChannel)selectionKey.channel();
// 接收连接
client = server.accept();
client.configureBlocking(false);
client.register(selector, SelectionKey.OP_READ);
} else if (selectionKey.isReadable()) { // 读事件就绪
// 获取socketChannel
client = (SocketChannel) selectionKey.channel();
// 创建buffer,并将获取socketChannel中的数据读入到buffer中
ByteBuffer readBuf = ByteBuffer.allocate(1024);
int readCount = client.read(readBuf);
if (readCount <= 0) {
return;
}
Charset charset = Charset.forName(StandardCharsets.UTF_8.name());
readBuf.flip();
System.out.println(String.valueOf(charset.decode(readBuf).array()));
}
} catch (IOException e) {
e.printStackTrace();
}
selectionKeys.remove(selectionKey);
});
}
}
通过这个例子,与原始
Reactor
模式相对应的理解,比如同步事件分离器对应着Selector
的select()
方法,再比如ServerSocketChannel
注册给Selector
的OP_ACCEPT
,还有SocketChannel
的OP_READ
与OP_WRITE
,这些事件保存在操作系统上,其实就是原始Reactor
中的Handle
。
四个重要api
-
Channel
:Connections to files,sockets etc that support non-blocking reads. -
Buffer
:Array-like objects that can be directly read or written by Channels. -
Selector
:Tell which of a set of Channels have IO events. -
SelectionKeys
:Maintain IO event status and bingdings.
3.用Java NIO对Reactor模式的应用。
3.1 Single threaded version
/**
* @Author CoderJiA
* @Description Reactor
* @Date 5/4/19 下午2:25
**/
public abstract class Reactor implements Runnable{
protected final Selector selector;
protected final ServerSocketChannel serverSocket;
protected final long port;
protected final long timeout;
public Reactor(int port, long timeout) throws IOException {
this.port = port;
this.timeout = timeout;
selector = Selector.open();
serverSocket = ServerSocketChannel.open();
serverSocket
.socket()
.bind(new InetSocketAddress(port));
serverSocket.configureBlocking(false);
SelectionKey sk = serverSocket.register(selector, SelectionKey.OP_ACCEPT);
sk.attach(newAcceptor(selector));
}
@Override
public void run() {
try {
while (!Thread.interrupted()) {
if (selector.select(timeout) > 0) {
Set<SelectionKey> selected = selector.selectedKeys();
selected.forEach(sk -> {
dispatch(sk);
selected.remove(sk);
});
}
}
} catch (IOException e) {
e.printStackTrace();
}
}
private void dispatch(SelectionKey sk) {
Runnable r = (Runnable)(sk.attachment());
if (Objects.nonNull(r)) {
r.run();
}
}
public abstract Acceptor newAcceptor(Selector selector);
}
/**
* @Author CoderJiA
* @Description Acceptor
* @Date 5/4/19 下午2:58
**/
public class Acceptor implements Runnable {
private final Selector selector;
private final ServerSocketChannel serverSocket;
public Acceptor(Selector selector, ServerSocketChannel serverSocket) {
this.selector = selector;
this.serverSocket = serverSocket;
}
@Override
public void run() {
try {
SocketChannel socket = serverSocket.accept();
if (Objects.nonNull(socket)) {
new Handler(selector,