接着上一章的那个比喻,如果我们把Buffer缓冲区看做码头,Channel通道看做航道的话,那么Selector选择器可以看做海上的一个调度中心,一个调度中心可以监听多个航道的事件,当然,这需要航道归属在这个调度中心的编制下。
创建一个Selector
Selector selector = Selector.open();
将Channel注册在某个Selector的管辖范围之下
Selector是一个对象,它可以被注册到很多个Channel上,监听各个Channel上发生的事件,并且能够根据事件情况决定Channel读写。这样,通过一个线程管理多个Channel,就可以处理大量网络连接了。
channel.register(selector, SelectionKey.OP_ACCEPT);
上面这行代码是有返回值的,这个方法会返回一个SelectionKey类型的结构。这个SelectionKey是NIO中事务处理的一个关键中间点,通过SelectionKey我们可以获取很多信息,可以说SelectionKey是整个NIO的业务大脑,关于SelectionKey,我将会放在下一章进行说明,毕竟自己目前的理解还不够深。
Selector如何选择就绪的通道
//这个方法可能会阻塞,直到至少有一个已注册的事件发生,或者当一个或者更多的事件发生时
selector.select();
NIO明明是非阻塞的IO,为何会阻塞
这是第一章Java NIO学习(一)NIO相关概念中说的内容,作为非阻塞的IO操作,NIO为何会有这么一个可能会造成阻塞的方法呢,别急,selector对象有很多方法可以解决阻塞的问题!
//阻塞在select()方法上的线程也可以立刻返回,不阻塞
selector.selectNow();
//可以设置超时时间,防止进程阻塞
selector.select(long timeout);
//可以唤醒阻塞状态下的selector
selector.wakeup();
选择器-多路复用器使用示例
具体详细使用示例请看:多路复用器-selector单线程版本
/**
* 初始化服务器端信息
*/
private void initServerSocket(){
try {
serverSocketChannel = ServerSocketChannel.open();
serverSocketChannel.bind(new InetSocketAddress(serverPort));//绑定端口号
System.out.println("step1 : new ServerSocket(" + serverPort+ ") ");
serverSocketChannel.configureBlocking(false);//设置服务端非阻塞模式
/**
* //打开多路复用器
*/
selector = Selector.open();
/**
* 把服务端注册到多路复用器当中,并监听一个网络行为---》OP_ACCEPT
*
* Interest Set
* 监听的Channel通道触发了一个事件意思是该事件已经就绪。
* 一个channel成功连接到另一个服务器称为”连接就绪“。
* 一个server socket channel准备号接收新进入的连接称为”接收就绪“。
* 一个有数据可读的通道可以说是”读就绪“。
* 一个等待写数据的通道可以说是”写就绪“。
* 这四种事件用SelectionKey的四个常量来表示:
* SelectionKey.OP_CONNECT
* SelectionKey.OP_ACCEPT
* SelectionKey.OP_READ
* SelectionKey.OP_WRITE
*/
serverSocketChannel.register(selector, SelectionKey.OP_ACCEPT);
} catch (IOException e) {
e.printStackTrace();
}
}
/**
* 客户端连接建立处理类
* @param selectionKey
*/
private void acceptHandler(SelectionKey selectionKey){
try {
/**
* 由于在之前已经把ServerSocketChannel
* <p>serverSocketChannel.register(selector, SelectionKey.OP_ACCEPT);</p>
* 已经注册到多路复用器(selector)中,因此现阶段可以直接把服务端通道直接取出
*/
ServerSocketChannel serverSocketChannel = (ServerSocketChannel)selectionKey.channel();
/**
* 调用accept接口从服务端通道中获取客户端连接
*/
SocketChannel clientSocketChannel = serverSocketChannel.accept();
/**
* 设置客户端连接也为非阻塞状态
*/
clientSocketChannel.configureBlocking(false);
/**
* 创建一个8字节的数据缓冲区
* 暂且不论该缓冲区大小是否足够
* 该缓冲区的作用为:******一个客户端通道对应一个数据缓冲区,防止缓冲区公用
*/
ByteBuffer byteBuffer = ByteBuffer.allocateDirect(8192);
/**
* 把新的客户端连接也注册到多路复用器当中,并且注册监听事件为读取就绪
* 这样在新的<p>SelectionKey</p> 中就即可以拿到自己的客户端,也可以拿到与客户端绑定的缓冲区
*
* 附加选项---byteBuffer
*/
SelectionKey clientSelectionKey = clientSocketChannel.register(selector,SelectionKey.OP_READ,byteBuffer);
System.out.println("--------------------------------------------------");
System.out.println("------新客户端进入------:" + clientSocketChannel.getRemoteAddress());
System.out.println("--------------------------------------------------");
} catch (IOException e) {
e.printStackTrace();
}
}