四Netty组件类--2Selector多路选择器
四Netty组件类--2Selector多路选择器
16.4 Selector源码与机制
选择器selector是NIO的关键,是实现多路复用的核心类。Nio是由事件驱动的,selectableChannel.register方法,将当前channel注册到指定selector上,通过询问操作系统,selector.select可以返回已经注册channel就绪的数量。然后获取selectionkeys,处理就绪的channel。
16.4.1 Selector
selector中包括三个主要的selectionkey集合:
Set<SelectionKey> keys(); //注册到当前selector上的channel的selectionkey集合------selector.key()
Set<SelectionKey> selectedKeys(); //就绪的channelkey集合-----selector.selectedKeys()
Set<SelectionKey> cancelledKeys(); //取消的channelkey集合
select()方法,会选择当前就绪的channelkey集合
/**……select返回注册在当前selector上就绪的channel数量,将就绪的selectionKey加入selectiokeys中,然后返回关系且就绪的channel数量…………*/
//非阻塞式select()---可以返回0
public abstract int selectNow() throws IOException;
//阻塞式select---阻塞直到有一个就绪channel时返回,或者会在wakeup、当前线程中断时、超时,从当前方法返回
public abstract int select(long timeout) throws IOException;
//阻塞式select---阻塞直到有一个就绪channel时返回,或者会在wakeup、当前线程中断时,从当前方法返回
public abstract int select() throws IOException;
public abstract class Selector implements Closeable {
//初始化当前selector
protected Selector() { }
//创建并打开selector
public static Selector open() throws IOException {
return SelectorProvider.provider().openSelector();
}
public abstract boolean isOpen();
//返回创建当前selector的SelectorProvider
public abstract SelectorProvider provider();
//返回当前selector的set<selectionKey>:set是非线程安全的(set集合是注册在当前selector上的selectionkey,就是注册在其上的channel集合
/** selector的Set<SelectionKey>不能直接修改,只能在selectionkey.cancel或者当前channel关闭/deregistered之后,会在下一个selection周期内,会从集合set中移除*/
public abstract Set<SelectionKey> keys();
//返回当前selector上就绪的SelectionKey集合(当前集合只能在处理完就绪selectionkey时,可以在iterator上删除;但是不能向selectedKeys中添加add(selectedKeys也是非线程安全的集合)
public abstract Set<SelectionKey> selectedKeys();
/**……select返回注册在当前selector上就绪的channel数量,将就绪的selectionKey加入selectiokeys中,然后返回关系且就绪的channel数量…………*/
//非阻塞式select()---可以返回0
public abstract int selectNow() throws IOException;
//阻塞式select---阻塞直到有一个就绪channel时返回,或者会在wakeup、当前线程中断时、超时,从当前方法返回
public abstract int select(long timeout) throws IOException;
//阻塞式select---阻塞直到有一个就绪channel时返回,或者会在wakeup、当前线程中断时,从当前方法返回
public abstract int select() throws IOException;
//唤醒阻塞的selector.select操作
public abstract Selector wakeup();
public abstract void close() throws IOException;
}
Netty中selector实现类是KQueueSelectorImpl。
16.4.2 SelectionKey
selectionkey用于表征selectableChannel.register(selector)这个注册的关系。可以通过selectionkey获取当前channel。
SelectionKey的关键逻辑:
当一个channel注册到selector上的时候会创建一个selectionkey出来,除非调用selectionkey的cancel方法,或者关闭channel或者关闭selector,selectionkey始终有效。
取消一个selectionkey的时候不会立即的从其关联的selector上面移除掉,其会立即的添加到selector关联的cancelled-key集合里面去,下次selection操作期间,会从selector关联的三个集合里面都移除掉。
public abstract class SelectionKey {
protected SelectionKey() { }
/** ……………………………………………………………………SelectionKey负责绑定当前channel与selector的关系…………………………………………………………………………………………………………*/
public abstract SelectableChannel channel();
public abstract Selector selector();
//当前key是否有效(在selectionkey.cancel或者当前channel关闭后,当前selectionkey会变成无效的)
public abstract boolean isValid();
//请求将当前key上的channel取消在selector上的注册
/**当取消时,会将当前key加入selector上的cancelset集合中,但是不会从selector的set<key>中移除,会在下一次select周期过程中才会移除,因此cancel的移除,会存在延迟 */
public abstract void cancel();
//当前key关注的事件
public abstract int interestOps();
//设置关注事件为给定的ops
public abstract SelectionKey interestOps(int ops);
//当前ready的集合
public abstract int readyOps();
public static final int OP_READ = 1 << 0;
public static final int OP_WRITE = 1 << 2;
public static final int OP_CONNECT = 1 << 3;
public static final int OP_ACCEPT = 1 << 4;
/**……………………………………………………………………………………………………位运算,计算当前key的关注事件类型………………………………………………………………………………………………………… */
public final boolean isReadable() {
return (readyOps() & OP_READ) != 0;
}
public final boolean isWritable() {
return (readyOps() & OP_WRITE) != 0;
}
public final boolean isConnectable() {
return (readyOps() & OP_CONNECT) != 0;
}
public final boolean isAcceptable() {
return (readyOps() & OP_ACCEPT) != 0;
}
//增加附加信息
private volatile Object attachment = null;
private static final AtomicReferenceFieldUpdater<SelectionKey,Object>
attachmentUpdater = AtomicReferenceFieldUpdater.newUpdater(
SelectionKey.class, Object.class, "attachment"
);
public final Object attach(Object ob) {
return attachmentUpdater.getAndSet(this, ob);
}
public final Object attachment() {
return attachment;
}
}
16.4.3 selector工作机制
server端:
ServerSocketChannel serverSocketChannel = ServerSocketChannel.open();
serverSocketChannel.configureBlocking(false);
serverSocketChannel.bind(new InetSocketAddress(999));
Selector selector = Selector.open(); // 1
serverSocketChannel.register(selector, SelectionKey.OP_ACCEPT); // 2
while (true) { // 3
if (selector.select(1000) == 0) { // 4
continue;
}
Set<SelectionKey> eventKeys = selector.selectedKeys(); // 5
Iterator<SelectionKey> iterator = eventKeys.iterator();
while (iterator.hasNext()) {
SelectionKey selectionKey = iterator.next();
SelectableChannel channel = selectionKey.channel(); // 6
// 如果是 连接已就绪事件
if (selectionKey.isAcceptable()) { // 7
ServerSocketChannel server = ((ServerSocketChannel) channel);
SocketChannel clientChannel = server.accept();
clientChannel.configureBlocking(false);
// 再将 client 注册到 selector
clientChannel.register(selector, SelectionKey.OP_READ, ByteBuffer.allocate(1024)); // 8
// 如果是可读事件 说明是客户端的连接channel
} else if (selectionKey.isReadable()) { // 9
// 可将此处代码放入先程序处理,不占用 主线程循环监听cpu时间片, 类比: netty 中的 EventLoop Work线程池
SocketChannel client = (SocketChannel) channel;
ByteBuffer buffer = (ByteBuffer) selectionKey.attachment();
int len = client.read(buffer);
buffer.flip();
byte[] data = new byte[buffer.remaining()];
int index = 0;
while (len != index) {
data[index++] = buffer.get();
}
String clientMsg = new String(data, StandardCharsets.UTF_8);
System.out.println("client: " + clientMsg);
buffer.clear();
client.write(ByteBuffer.wrap(("收到请求:" + clientMsg).getBytes(StandardCharsets.UTF_8)));
} else if (selectionKey.isWritable()) {
// System.out.println(selectionKey.readyOps());
} else {
System.out.println(selectionKey.readyOps());
}
iterator.remove(); // 10
}
}
将ServerSocketChannel绑定到本地端口,获取Selector
将ServerSocketChannel注册到Selector并且注册事件是 accept
开始循环使用Selector,询问操作系统
询问操作系统,是否有注册的事件发生
返回第4步中的有效的SelectionKey
从5中的key获取对应的Channel
判断事件类型,如若是accept事件 代表新的连接进来
获取新的连接SocketChannel并将改Channel再次注册到Selector,注册事件是 READ
因为代表客户端的SocketChannel也注册到了该Selector,所以该事件也可能是 READ 代表字节池现在可读(read可直接读取),随后向改channel写入数据表示响应
每次事件读取完成后,需要把改事件剔除,否则下次会重复读取到该事件
client端:
SocketChannel client = SocketChannel.open(); // 1
client.configureBlocking(false);
if (!client.connect(new InetSocketAddress("localhost", 999))) { // 2
if (!client.finishConnect()) { // 3
System.out.println("连接失败,不占用cpu资源,do other things.");
}
}
System.out.println("连接成功。.");
ByteBuffer buffer = ByteBuffer.allocate(1024);
while (true) {
int len = client.read(buffer); // 4
buffer.flip();
byte[] data = new byte[buffer.remaining()];
int index = 0;
while (len != index) {
data[index++] = buffer.get();
}
System.out.println("server: " + new String(data, StandardCharsets.UTF_8));
buffer.clear();
client.write(ByteBuffer.wrap(("你好,我是客户端:" + client.getLocalAddress() + "[" + client.hashCode() + "]" +
new Date()).getBytes(StandardCharsets.UTF_8)));
TimeUnit.SECONDS.sleep(2);
}
创建channel
绑定到服务端地址,因为开启了异步,所以可能连接尚在建立返回false
建立稳定连接
读取数据,发送数据