Netty(八) - SelectionKey

1. Analysis of NIO non-blocking network programming principle

NIO non-blocking network programming related relationship (Selector, SelectionKey, ServerSocketChannel and SocketChannel) combing diagram:
NIO non-blocking network programming principle

1) When the client connects, it will get the SocketChannel through the ServerSocketChannel
2), the Selector will monitor through the select() method, and return the number of channels where the event occurred
3), register the SocketChannel to the Selector, public final SelectionKey register( Selector sel, int ops), multiple SocketChannels can be registered on a Selector
4), after registration, return a SelectionKey, which will be associated with the Selector (collection)
5), and further obtain the SelectionKey with an event
6), and then reverse through SelectionKey Get SocketChannel, when the method: public abstract SelectableChannel channel()
7), you can complete the corresponding business processing through the obtained Channel

2. NIO application case

2.1 NIO non-blocking network programming quick start

Case requirements

  1. Write a NIO entry case to realize simple data communication between server and client (non-blocking)
  2. Purpose: Understand the NIO non-blocking network programming mechanism
  3. Code demo
/**
 * @desc 服务端
 * @author yxs
 * @date 2021-02-05 9:43
 */
public class NIOServer {

    public static void main(String[] args) throws IOException {

        // 创建 ServerSocketChannel 类似于 ServerSocket
        ServerSocketChannel serverSocketChannel = ServerSocketChannel.open();

        // 得到一个 Selector 对象
        Selector selector = Selector.open();

        // 绑定端口6666,在服务器端监听
        serverSocketChannel.socket().bind(new InetSocketAddress(6666));
        // 设置为非阻塞
        serverSocketChannel.configureBlocking(false);

        // 把 ServerSocketChannel 注册到 Selector,关心的事件为:OP_ACCEPT
        serverSocketChannel.register(selector, SelectionKey.OP_ACCEPT);
        System.out.println("注册的 selectionKey 数量 = " + selector.keys().size()); // 返回 1

        // 循环等待客户端连接
        while (true) {

            // 这里我们等待1秒(可以设置为selectNow),如果没有事件发生,就继续
            if(selector.select(1000) == 0){
                System.out.println("服务器等待了1秒,无连接");
                continue;
            }

            // 如果返回的大于0,就获取到相关的 SelectionKey 集合
            // 1. 如果返回的大于0,表示已经获取到关注的事件
            // 2. selector.selectedKeys() 返回关注事件的集合
            //    通过 selectionKeys 反向获取通道
            Set<SelectionKey> selectionKeys = selector.selectedKeys();
            System.out.println("selectionKeys 数量 = " + selectionKeys.size());

            // 遍历 Set<SelectionKey>,使用迭代器遍历
            Iterator<SelectionKey> keyIterator = selectionKeys.iterator();
            while (keyIterator.hasNext()) {
                // 获取到 SelectionKey
                SelectionKey key = keyIterator.next();
                // 根据 key 对应的通道发生的事件做相应的处理
                if(key.isAcceptable()){ // 如果发生的事件为:OP_ACCEPT,有新的客户端连接
                    // 给该客户端生成一个 SocketChannel
                    SocketChannel socketChannel = serverSocketChannel.accept();
                    System.out.println("客户端连接成功 生成了一个 socketChannel " + socketChannel.hashCode());
                    // 将 SocketChannel 设置为非阻塞
                    socketChannel.configureBlocking(false);
                    // 将 SocketChannel 注册到 Selector,关心的事件为:OP_READ,同时给 SocketChannel
                    // 关联一个 Buffer
                    socketChannel.register(selector,SelectionKey.OP_READ, ByteBuffer.allocate(1024));
                    System.out.println("客户端连接后,注册的 selectionKey 数量 = " + selector.keys().size());// 2,3,4...
                }
                if(key.isReadable()){ // 如果是发生的事件为:OP_READ
                    // 通过 key 反向获取到对应的 channel
                    SocketChannel channel = (SocketChannel)key.channel();
                    // 获取到该channel关联的buffer
                    ByteBuffer buffer = (ByteBuffer) key.attachment();
                    channel.read(buffer);
                    System.out.println("from client:"+new String(buffer.array()));
                }

                // 手动从集合中移除当前的SelectionKey,防止重复操作
                keyIterator.remove();
            }

        }
    }

}
/**
 * @desc 客户端
 * @auther yxs
 * @date 2021/2/5 22:09
 */
public class NIOClient {

    public static void main(String[] args) throws IOException {

        // 得到一个网络通道
        SocketChannel socketChannel = SocketChannel.open();
        // 设置非阻塞
        socketChannel.configureBlocking(false);
        // 提供服务器端的 ip 和 端口
        InetSocketAddress inetSocketAddress = new InetSocketAddress("127.0.0.1", 6666);
        // 连接服务器
        if(!socketChannel.connect(inetSocketAddress)){
            while (!socketChannel.finishConnect()){
                System.out.println("因为连接需要时间,客户端不会阻塞,可以做其它工作");
            }
        }

        // 如果连接成功,就发送数据
        String str = "Hello,World!";
        // Wraps a byte array into a buffer.
        ByteBuffer buffer = ByteBuffer.wrap(str.getBytes());
        // 发送数据,将 buffer 数据写入 channel
        socketChannel.write(buffer);
        System.in.read();
    }

}

、 SelectionKey API

1), SelectionKey, represents the registration relationship between the Selector and the network channel, there are four types:
int OP_ACCEPT: there is a new network connection, the value is 16
int OP_CONNECT: the connection is already connected, the value is 8
int OP_READ: the read operation, the value is 1
int OP_WRITE: represents a write operation, the value is 4

In the source code: (bit operation)

public static final int OP_ACCEPT = 1 << 4;
public static final int OP_CONNECT = 1 << 3;
public static final int OP_READ = 1 << 0;
public static final int OP_WRITE = 1 << 2;

2), SelectionKey related methods

method description
public abstract Selector selector() Get the Selector object associated with it
public abstract SelectableChannel channel() Get the associated channel
public final Object attachment() Get the shared data associated with it
public abstract SelectionKey interestOps(int ops) Set or change the listening event
public final boolean isAcceptable () Can accept
public final boolean isReadable() Can read
public final boolean isWritable() Can write

SelectionKey related methods

Guess you like

Origin blog.csdn.net/yangxshn/article/details/113665701