Java NIO 学习笔记(三) 选择器Selector

上节使用通道channel模拟了客户端给服务器端发送消息。代码是基于同步阻塞式IO。这样效率很低,而且一个服务端进程只能处理一个客户端连接。这节学习Selector选择器。可以让一个服务端处理多个客户端的连接,性能有了较大的提升。

一、Selector选择器简介

Selector是NIO中另外一个重要的实现。选择器是Java对五种IO模型之一的多路复用模型的一中实现。可以同时监控多个非阻塞套接字通道。

在这里插入图片描述

1、选择键SelectKey

选择键包含四种事件,通过选择键,可以选择器发现我们要选择的事件,分别是:

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;

事件之间也可以通过或运算进行组合。
通过以下几个方法可以判断事件是否就绪。我们通过Selector进行轮询,查看我们需要的事件是否准备好。

selectionKey.isAcceptable();

selectionKey.isConnectable();

selectionKey.isReadable();

selectionKey.isWritable();

2、通道注册

将我们感兴趣的事情告知Selector,待事件发生时,Seletor就可返回就绪的事件,然后执行后续的事情。

       //  切换成非阻塞模式
        serverSocketChannel.configureBlocking(false);

        // 将通道注册到选择器上
        serverSocketChannel.register(selector, SelectionKey.OP_ACCEPT);

3、选择过程

Selector包含3中不同功能的选择方法

  • int select() 这是一个阻塞方法,至少有一个通道处于就绪状态时才返回
  • int select(long timeout) 同样也是一个阻塞方法,不过可对该方法设置超时时间,使得线程不会一致被阻塞
  • int selectNow() 非阻塞方法,调用后立刻返回

二、实例代理以及演示

服务端的代码比较固定,参考下面注释,可以很好的理解。这里需要注意当每次结束内循环后,需要将本次轮询获得到的所有选择键移除掉,以便重新开始新的轮询。

/**
 * @author Time
 * @created 2019/8/28
 */
public class TcpSelectServer {
    public static void main(String[] args) throws IOException {
        // 1. 获取通道
        ServerSocketChannel serverSocketChannel = ServerSocketChannel.open();
        // 2. 切换成非阻塞模式
        serverSocketChannel.configureBlocking(false);
        // 3.绑定连接
        serverSocketChannel.bind(new InetSocketAddress(8080));
        // 4.获取选择器
        Selector selector = Selector.open();
        // 5.将通道注册到选择器上
        serverSocketChannel.register(selector, SelectionKey.OP_ACCEPT);
        // 6. 轮询式获取选择器上已经准备就绪的通道
        while(selector.select() > 0 ){
            // 7.获取当前选择器中所有的注册事件
            Iterator<SelectionKey> iterator = selector.selectedKeys().iterator();
            while (iterator.hasNext()){
                // 获取准备就绪的事件
                SelectionKey sk = iterator.next();
                // 判断具体是什么事件
                if(sk.isAcceptable()){
                    // 获取客户端非阻塞
                    SocketChannel s = serverSocketChannel.accept();
                    // 切换非阻塞模式
                    s.configureBlocking(false);
                    // 将该通道注册到选择器上
                    s.register(selector,SelectionKey.OP_READ);
                }else  if (sk.isReadable()){
                    // 获取当前选择器上读就绪通道
                    SocketChannel socketChannel = (SocketChannel) sk.channel();
                    // 读取数据
                    ByteBuffer buffer = ByteBuffer.allocate(128);
                    int len = 0;
                    while((len = socketChannel.read(buffer)) > 0){
                        buffer.flip();
                        System.out.println(new String(buffer.array(),0,len));
                        buffer.clear();
                    }
                }
                // 取消选择键
                iterator.remove();
            }
        }
    }
}

客户端代码

/**
 * @author Time
 * @created 2019/8/28
 * 非阻塞式客户端
 */
public class TcpNonBlockingClient01 {
    public static void main(String[] args) throws IOException {
        // 1. 获取通道
        SocketChannel socketChannel = SocketChannel.open(new InetSocketAddress("localhost",8080));
        // 2.切换成非阻塞模式
        socketChannel.configureBlocking(false);
        // 3.分配指定大小的缓冲区
        ByteBuffer buffer = ByteBuffer.allocate(128);
        // 4.发送数据给服务器
        Scanner scanner = new Scanner(System.in);
        while(scanner.hasNext()){
            String str = scanner.nextLine();
            buffer.put((new Date().toString() + "\n" + str).getBytes());
            buffer.flip();
            socketChannel.write(buffer);
            buffer.clear();
        }

        // 5. 关闭通道
        socketChannel.close();
    }
}

效果演示:可以发现现在一个服务端进程可以处理多个连接
在这里插入图片描述

发布了66 篇原创文章 · 获赞 26 · 访问量 1万+

猜你喜欢

转载自blog.csdn.net/Time__Lc/article/details/100121502