四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;

}
image-20221205163630237

Netty中selector实现类是KQueueSelectorImpl。

16.4.2 SelectionKey

image-20221205162140828

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
建立稳定连接
读取数据,发送数据
posted @ 2023-03-10 17:22  LeasonXue  阅读(96)  评论(0编辑  收藏  举报