NIO的非阻塞模式

44 阅读1分钟

开启掘金成长之旅!这是我参与「掘金日新计划 · 12 月更文挑战」的第2天,点击查看活动详情

非阻塞模式

使用configureBlocking(true)方法可以将 ServerSocketChannelaccept()方法和SocketChannelread()方法从阻塞模式变为非阻塞模式。

  • accept()方法用于建立连接获得 SocketChannel,在非阻塞模式下如果没有建立连接该方法返回 null
  • read()方法用于从 SocketChannel 中读数据,在非阻塞模式下如果没有读到数据返回值为0

根据返回值即可在非阻塞的模式下处理网络IO,但这种处理方式会使得CPU资源的浪费

public class Server {

  public static void main(String[] args) throws IOException {
    ByteBuffer buffer = ByteBuffer.allocate(16);
    // 创建Socket服务器,绑定8080端口
    ServerSocketChannel ssc = ServerSocketChannel.open();
    ssc.bind(new InetSocketAddress(8080));
    // 设置为非阻塞模式,这种情况accept是非阻塞的,可能返回null
    ssc.configureBlocking(false);
    // 创建channel集合
    List<SocketChannel> channels = new ArrayList<>();
    while (true) {
      // accept方法建立连接,获得channel与客户端通信
      SocketChannel socketChannel = ssc.accept();
      // 非阻塞情况下,accept方法可能返回null,这种情况会线程空转
      if (Objects.isNull(socketChannel)) {
        continue;
      }
      socketChannel.configureBlocking(false);
      channels.add(socketChannel);
      // 处理channel集合中的读写响应
      for (SocketChannel channel : channels) {
        int read = channel.read(buffer);
        if (read == 0) {
          continue;
        }
        buffer.flip();
        BufferUtils.read(buffer);
        buffer.compact();
      }
    }
  }

}

使用Selector的改进

image.png

Selector 用于管理多个 Channel 并监测是否有事件发生,其实现方式是使用了观察者模式,Selector 作为观察者被注册到某个 Channel 主题上,当 Channel 中出现了事件就会通知到 Selector

Selector 是由事件驱动的,可以感知四种事件

  • connect事件:客户端侧连接建立后触发
  • accept事件:有连接请求时触发
  • read事件:可读事件
  • write:可写事件

因此使用 Selector 来改进NIO的方式为将其注册到 ServerSocketChannelSocketChannel 中,对于 accept 事件监听 channel 的读事件,对于 readwrite 事件进行读写处理

public class NIOServer {

  public static void main(String[] args) throws IOException {
    // 创建selector,用于管理channel
    Selector selector = Selector.open();

    ByteBuffer byteBuffer = ByteBuffer.allocate(16);
    
    ServerSocketChannel socketChannel = ServerSocketChannel.open();
    socketChannel.bind(new InetSocketAddress(8080));
    socketChannel.configureBlocking(false);

    // 注册channel到selector上,设定只关注accept事件
    socketChannel.register(selector, SelectionKey.OP_ACCEPT, null);
    
    while (true) {
      // 阻塞方法,在没有事件发生时会阻塞,避免线程浪费
      selector.select();
      // selector作为观察者收到了主题发出的通知,开始处理所有发生的事件
      Iterator<SelectionKey> iterator = selector.selectedKeys().iterator();
      while (iterator.hasNext()) {
        SelectionKey key = iterator.next();
        // 从集合中移除待处理的事件
        iterator.remove();
        // 根据事件类型进行不同的处理
        if (key.isAcceptable()) {
          ServerSocketChannel channel = (ServerSocketChannel) key.channel();
          SocketChannel sc = channel.accept();
          sc.configureBlocking(false);
          // 注册到selector上,监听read事件
          sc.register(selector, SelectionKey.OP_READ, null);
        } else if (key.isReadable()) {
          try {
            SocketChannel channel = (SocketChannel) key.channel();
            int read = channel.read(byteBuffer);
            byteBuffer.flip();
            BufferUtils.read(byteBuffer);
            byteBuffer.clear();
            // 如果返回-1说明客户端正常断开
            if (read == -1) {
              key.cancel();
            }
          } catch (IOException e) {
            // 如果客户端异常断开,在获得channel时出现异常,在这里cancel()来处理掉事件
            key.cancel();
          }
        }
      }
    }
  }
}