7. Tong brother said Selector core component of the Java NIO netty Series

- Arch day a soldier, unexpected!

nio

Hello, I am a brother Tong herein, this series is netty VII.

Brief introduction

The last chapter we learned together a core component of Buffer Java NIO, it is usually used in conjunction with the Channel, but how should they be used in network IO in it, and today we will learn together another NIO core components - Selector , it can not He said it does not dry up network IO.

concept

Let's look at two Selector notes, see category java.nio.channels.Selector.

Comment I

A multiplexor of {@link SelectableChannel} objects.

It is SelectableChannelan object of the multiplexer , from here we can actually know Java NIO multiplexing IO.

SelectableChannelThere are several sub-categories, you will be very familiar:

  • DatagramChannel, UDP protocol connection
  • SocketChannel, TCP connection protocol
  • ServerSocketChannel, to deal specifically with the TCP protocol Accept event

We need to review the process of multiplexing IO :

multiplexing-io

By polling the first stage to select no connection check data ready, data is copied from the second stage of the kernel space to user space.

In Java, it is through Selectorthe multiplexer to implement the first stage.

Notes II

A selector may be created by invoking the {@link #open open} method of this class, which will use the system's default {@link java.nio.channels.spi.SelectorProvider selector provider} to create a new selector. A selector may also be created by invoking the {@link java.nio.channels.spi.SelectorProvider#openSelector openSelector} method of a custom selector provider. A selector remains open until it is closed via its {@link #close close} method.

Selector可以通过它自己的open()方法创建,它将通过默认的java.nio.channels.spi.SelectorProvider类创建一个新的Selector。也可以通过实现java.nio.channels.spi.SelectorProvider类的抽象方法openSelector()来自定义实现一个Selector。Selector一旦创建将会一直处于open状态直到调用了close()方法为止。

那么,默认使用的Selector究竟是哪个呢?

通过跟踪源码:

> java.nio.channels.Selector#open()
  1> java.nio.channels.spi.SelectorProvider#provider()
    1.1> sun.nio.ch.DefaultSelectorProvider#create() // 返回WindowsSelectorProvider
  2> sun.nio.ch.WindowsSelectorProvider#openSelector() // 返回WindowsSelectorImpl

可以看到,在Windows平台下,默认实现的Provider是WindowsSelectorProvider,它的openSelector()方法返回的是WindowsSelectorImpl,它就是Windows平台默认的Selector实现。

为什么要提到在Windows平台呢,难道在Linux下面实现不一样?

是滴,因为网络IO是跟操作系统息息相关的,不同的操作系统的实现可能都不一样,Linux下面JDK的实现完全不一样,那么我们为什么没有感知到呢?我的代码在Windows下面写的,拿到Linux下面不是一样运行?那是Java虚拟机(或者说Java运行时环境)帮我们把这个事干了,它屏蔽了跟操作系统相关的细节,这也是Java代码可以“Write Once, Run Anywhere”的精髓所在。

Selector与Channel的关系

上面我们说了selector是多路复用器,它是在网络IO的第一阶段用来轮询检查有没有连接准备好数据的,那么它和Channel是什么关系呢?

nio

Selector通过不断轮询的方式同时监听多个Channel的事件,注意,这里是同时监听,一旦有Channel准备好了,它就会返回这些准备好了的Channel,交给处理线程去处理。

所以,在NIO编程中,通过Selector我们就实现了一个线程同时处理多个连接请求的目标,也可以一定程序降低服务器资源的消耗。

基本用法

创建Selector

通过调用Selector.open()方法是我们常用的方式:

Selector selector = Selector.open();

当然,也可以通过实现java.nio.channels.spi.SelectorProvider.openSelector()抽象方法自定义一个Selector。

将Channel注册到Selector上

为了将Channel跟Selector绑定在一起,需要将Channel注册到Selector上,调用Channel的register()方法即可:

channel.configureBlocking(false);

SelectionKey key = channel.register(selector, SelectionKey.OP_READ);

Channel必须是非阻塞模式才能注册到Selector上,所以,无法将一个FileChannel注册到Selector,因为FileChannel没有所谓的阻塞还是非阻塞模式,本文来源于工从号彤哥读源码。

注册的时候第二个参数传入的是监听的事件,一共有四种事件:

  • Connect
  • Accept
  • Read
  • Write

当Channel触发了某个事件,通常也叫作那个事件就绪了。比如,数据准备好可以读取了就叫作读就绪了,同样地,还有写就绪、连接就绪、接受就绪,当然后面两个不常听到。

在Java中,这四种监听事件是定义在SelectionKey中的:

  • SelectionKey.OP_READ,值为 1 << 0 = 0000 0001
  • SelectionKey.OP_WRITE,值 为 1 << 2 = 0000 0100
  • SelectionKey.OP_CONNECT,值为 1 << 3 = 0000 1000
  • SelectionKey.OP_ACCEPT,值为 1 << 4 = 0001 0000

所以,也可以通过位或命令监听多个感兴趣的事件:

int interestSet = SelectionKey.OP_READ | SelectionKey.OP_WRITE;

SelectionKey

正如上面所看到的,Channel注册到Selector后返回的是一个SelectionKey,所以SelectionKey又可以看作是Channel和Selector之间的一座桥梁,把两者绑定在了一起。

SelectionKey具有以下几个重要属性:

  • interest set,感兴趣的事件集
  • ready set,就绪的事件集
  • 保存着的Channel
  • 保存着的Selector
  • attached object,附件

interest set

里面保存了注册Channel到Selector时传入的第二个参数,即感兴趣的事件集。

int interestSet = selectionKey.interestOps();

boolean isInterestedInAccept  = interestSet & SelectionKey.OP_ACCEPT;
boolean isInterestedInConnect = interestSet & SelectionKey.OP_CONNECT;
boolean isInterestedInRead    = interestSet & SelectionKey.OP_READ;
boolean isInterestedInWrite   = interestSet & SelectionKey.OP_WRITE;    

可以通过位与运算查看是否注册了相应的事件。

ready set

里面保存了就绪了的事件集。

int readySet = selectionKey.readyOps();
selectionKey.isAcceptable();
selectionKey.isConnectable();
selectionKey.isReadable();
selectionKey.isWritable();

可以通过readyOps()方法获取所有就绪了的事件,也可以通过isXxxable()方法检查某个事件是否就绪。

保存的Channel和Selector

Channel  channel  = selectionKey.channel();

Selector selector = selectionKey.selector();    

通过channel()selector()方法可以获取绑定的Channel和Selector。

attachment

可以调用attach(obj)方法绑定一个对象到SelectionKey上,并在后面需要用到的时候通过attachment()方法取出绑定的对象,也可以翻译为附件,它可以看作是数据传递的一种媒介,跟ThreadLocal有点类似,在前面绑定数据,在后面使用。

selectionKey.attach(theObject);

Object attachedObj = selectionKey.attachment();

当然,也可以在注册Channel到Selector的时候就绑定附件:

SelectionKey key = channel.register(selector, SelectionKey.OP_READ, theObject);

Selector.select()

一旦将一个或多个Channel注册到Selector上了,我们就可以调用它的select()方法了,它会返回注册时感兴趣的事件中就绪的事件,本文来源于工从号彤哥读源码。

select()方法有三种变体:

  • select(),无参数,阻塞直到某个Channel有就绪的事件了才返回(当然是我们注册的感兴趣的事件)
  • select(timeout),带超时,阻塞直到某个Channel有就绪的事件了,或者超时了才返回
  • selectNow(),立即返回,不会阻塞,不管有没有就绪的Channel都立即返回

select()的返回值为int类型,表示两次select()之间就绪的Channel,即使上一次调用select()时返回的就绪Channel没有被处理,下一次调用select()也不会再返回上一次就绪的Channel。比如,第一次调用select()返回了一个就绪的Channel,但是没有处理它,第二次调用select()时又有一个Channel就绪了,那也只会返回1,而不是2。

Selector.selectedKeys()

一旦调用select()方法返回了有就绪的Channel,我们就可以使用selectedKeys()方法来获取就绪的Channel了。

Set<SelectionKey> selectedKeys = selector.selectedKeys();    

然后,就可以遍历这些SelectionKey来查看感兴趣的事件是否就绪了:

Set<SelectionKey> selectedKeys = selector.selectedKeys();

Iterator<SelectionKey> keyIterator = selectedKeys.iterator();

while(keyIterator.hasNext()) {
    
    SelectionKey key = keyIterator.next();

    if(key.isAcceptable()) {
        // a connection was accepted by a ServerSocketChannel.

    } else if (key.isConnectable()) {
        // a connection was established with a remote server.

    } else if (key.isReadable()) {
        // a channel is ready for reading

    } else if (key.isWritable()) {
        // a channel is ready for writing
    }

    keyIterator.remove();
}

最后,一定要记得调用keyIterator.remove();移除已经处理的SelectionKey。

Selector.wakeup()

When we said earlier call to select () method, the caller's thread enters the blocking state until ready Channel will not return. In fact, not necessarily, wakeup () is used to break the rules, you can call a thread in another wakeup () method to force the wake blocked thread, so select () method will return immediately.

If you call wakeup () and there are no threads blocked on select (), then the next call to select () will return immediately, not block. This with LockSupport.unpark () method is relatively similar.

Selector.close()

Call the close () method will close Selector, but also the associated SelectionKey failure, but does not close the Channel.

For chestnuts

nio

public class EchoServer {
    public static void main(String[] args) throws IOException {
        // 创建一个Selector
        Selector selector = Selector.open();
        // 创建ServerSocketChannel
        ServerSocketChannel serverSocketChannel = ServerSocketChannel.open();
        // 绑定8080端口
        serverSocketChannel.bind(new InetSocketAddress(8080));
        // 设置为非阻塞模式,本文来源于工从号彤哥读源码
        serverSocketChannel.configureBlocking(false);
        // 将Channel注册到selector上,并注册Accept事件
        serverSocketChannel.register(selector, SelectionKey.OP_ACCEPT);

        while (true) {
            // 阻塞在select上
            selector.select();

            // 如果使用的是select(timeout)或selectNow()需要判断返回值是否大于0

            // 有就绪的Channel
            Set<SelectionKey> selectionKeys = selector.selectedKeys();
            // 遍历selectKeys
            Iterator<SelectionKey> iterator = selectionKeys.iterator();
            while (iterator.hasNext()) {
                SelectionKey selectionKey = iterator.next();
                // 如果是accept事件
                if (selectionKey.isAcceptable()) {
                    // 强制转换为ServerSocketChannel
                    ServerSocketChannel ssc = (ServerSocketChannel) selectionKey.channel();
                    SocketChannel socketChannel = ssc.accept();
                    System.out.println("accept new conn: " + socketChannel.getRemoteAddress());
                    socketChannel.configureBlocking(false);
                    // 将SocketChannel注册到Selector上,并注册读事件
                    socketChannel.register(selector, SelectionKey.OP_READ);
                } else if (selectionKey.isReadable()) {
                    // 如果是读取事件
                    // 强制转换为SocketChannel
                    SocketChannel socketChannel = (SocketChannel) selectionKey.channel();
                    // 创建Buffer用于读取数据
                    ByteBuffer buffer = ByteBuffer.allocate(1024);
                    // 将数据读入到buffer中
                    int length = socketChannel.read(buffer);
                    if (length > 0) {
                        buffer.flip();
                        byte[] bytes = new byte[buffer.remaining()];
                        // 将数据读入到byte数组中
                        buffer.get(bytes);

                        // 换行符会跟着消息一起传过来
                        String content = new String(bytes, "UTF-8").replace("\r\n", "");
                        if (content.equalsIgnoreCase("quit")) {
                            selectionKey.cancel();
                            socketChannel.close();
                        } else {
                            System.out.println("receive msg: " + content);
                        }
                    }
                }
                iterator.remove();
            }
        }
    }
}

to sum up

Today we learned a core component of the Java NIO Selector, here, NIO three most important core components we have completed the study, to be honest, this is the most important issue NIO thinking, always remember a thread in the NIO It can handle multiple connections.

Watching the native Java NIO for network programming seems to have no difficulty in it? So why have Netty it? The next chapter will officially enter into Netty learning, where we will find the answer.

Finally, I also welcome to work from No. Tong brother read the source code systematic study of the source code & architecture knowledge.

nio

Guess you like

Origin www.cnblogs.com/tong-yuan/p/11992860.html