Selector
selector就是管理多个channel的,并且能够管理监测这些channel上有没有事件发生,如果有事件发生,selector就能获得这些事件,我们的线程再去处理这些事件。没有事件发生的时候线程可以阻塞。
事件有四种:
- accept 客户端发起了连接请求时触发该类型的事件
- connect 客户端与服务器连接建立成功就会触发该事件
- read 可读事件
- write 可写事件
使用Selector步骤
- 创建Selector
- 创建ServerSocketChannel
- 将ServerSocketChannel注册进selector
- 得到一个SelectedKey,并指定该channel要关注的事件
- 写一个死循环,然后调用select()方法阻塞线程
- 当有事件发生是就会解除线程的阻塞,获取所有有事件发生的SelectedKeys集合,遍历,获取key
- 通过key进行判断事件的类型
- 通过key获取channel,在进行各个事件相应的处理
处理accept事件
package com.hs.netty.network;
import java.io.IOException;
import java.net.InetSocketAddress;
import java.nio.channels.*;
import java.util.Iterator;
import java.util.Set;
/**
* 使用select机制,服务器端的代码
* @author hs
* @date 2021/07/12
*/
public class ServerSocketChannelTest3 {
public static void main(String[] args) throws IOException {
// 创建一个select
Selector selector = Selector.open();
ServerSocketChannel serverSocketChannel = ServerSocketChannel.open();
serverSocketChannel.configureBlocking(false);
serverSocketChannel.bind(new InetSocketAddress(8080));
// 建立select和channel之间的联系(channel注册进select中)
// selectKey 就是将来事件发生后可以通过它知道是哪一个channel发生的事件
SelectionKey sscKey = serverSocketChannel.register(selector, 0, null);
// 服务器ServerSocketChannel这个selectKey只需要关注accept事件
sscKey.interestOps(SelectionKey.OP_ACCEPT);
while(true){
// 调用Selector类的select()方法,该方法的作用就是如果没有事件发生 就阻塞,如果有事件发生了 才会让线程继续运行
selector.select();
// 处理发生的事件
// 首先要拿到所有发生了的事件,结果是一个set集合,在之后的操作中还会在集合中进行删除操作,
// 所以这里不能使用增强for循环,需要使用到迭代器
Iterator<SelectionKey> iterator = selector.selectedKeys().iterator();
// 遍历
while(iterator.hasNext()){
// 我们可以通过这个key知道是哪个channel发生了事件,发生了什么事件。
SelectionKey key = iterator.next();
// 得到发生事件了的key,调用channel()方法返回的是SelectableChannel类型,
// 还可以进行强转变为ServerSocketChannel
ServerSocketChannel channel = (ServerSocketChannel) key.channel();
// 执行这个accept事件,
SocketChannel socketChannel = channel.accept();
System.out.println("客户端成功连接到服务器---->" + socketChannel);
}
}
}
}
如果我们得到了channel,却不进行事件处理的话,就又会进入一个非阻塞状态不停的死循环。这是因为如果我们处理了这个事件,执行完一次while循环后,再执行到 selector.select()时,它就会认为这个事件已经处理了,我应该给你新事件。如果不处理,就会一直拿到这个事件,一直死循环。
如果拿到了SelectionKey,处于某些原因,不想执行这个事件了 可以调用key.cancel()
方法取消该事件
总结:select()方法在事件未处理时不会阻塞,事件已处理或者是取消了,并且当前没有新事件了就会阻塞,即如果有事件发生,要么处理要么取消,不能置之不理。
处理accept+read事件
现在有个问题,就是事件有多种,我们需要在while()循环中添加事件的判断,然后执行各个事件相应的业务逻辑
while(iterator.hasNext()){
// 我们可以通过这个key知道是哪个channel发生了事件,发生了什么事件。
SelectionKey key = iterator.next();
// 有以下几个方法来判断事件的类型,判断事件的类型,然后进行相应的逻辑
key.isAcceptable();
key.isConnectable();
key.isReadable();
key.isWritable();
}
现在服务器端的代码如下:
package com.hs.netty.network;
import com.google.common.base.Charsets;
import java.io.IOException;
import java.net.InetSocketAddress;
import java.nio.ByteBuffer;
import java.nio.channels.*;
import java.util.Iterator;
/**
* 使用select机制,处理accept + read 事件 服务器端的代码
* @author hs
* @date 2021/07/12
*/
public class ServerSocketChannelTest4 {
public static void main(String[] args) throws IOException {
// 创建一个select
Selector selector = Selector.open();
ServerSocketChannel serverSocketChannel = ServerSocketChannel.open();
serverSocketChannel.configureBlocking(false);
serverSocketChannel.bind(new InetSocketAddress(8080));
// 创建一个ByteBuffer
ByteBuffer byteBuffer = ByteBuffer.allocate(16);
// 建立select和channel之间的联系(channel注册进select中)
// selectKey 就是将来事件发生后可以通过它知道是哪一个channel发生的事件
SelectionKey sscKey = serverSocketChannel.register(selector, 0, null);
// 服务器ServerSocketChannel这个selectKey只需要关注accept事件
sscKey.interestOps(SelectionKey.OP_ACCEPT);
while(true){
// 调用Selector类的select()方法,该方法的作用就是如果没有事件发生 就阻塞,如果有事件发生了 才会让线程继续运行
selector.select();
// 处理发生的事件
// 首先要拿到所有发生了的事件,结果是一个set集合,在之后的操作中还会在集合中进行删除操作,
// 所以这里不能使用增强for循环,需要使用到迭代器
Iterator<SelectionKey> iterator = selector.selectedKeys().iterator();
// 遍历
while(iterator.hasNext()){
// 我们可以通过这个key知道是哪个channel发生了事件,发生了什么事件。
SelectionKey key = iterator.next();
// 如果是accept事件就执行下面的业务逻辑,将客户端SocketChannel也注册进Selector中
if (key.isAcceptable()) {
// 得到发生事件了的key,调用channel()方法返回的是SelectableChannel类型,
// 还可以进行强转变为ServerSocketChannel
ServerSocketChannel channel =