Java NIO 通道概念选择器使用示例详解

 更新时间:2023年10月27日 10:50:29   作者:lane  
这篇文章主要为大家介绍了Java NIO 通道概念选择器使用示例详解,有需要的朋友可以借鉴参考下,希望能够有所帮助,祝大家多多进步,早日升职加薪

Java NIO通道

通道相当于一个传递物品的管子,两边都可以往对面传递东西。

有哪些通道?

对应文件IO和网络IO,通道也分为一个FileChannel和三个socket通道(SocketChannel、ServerSocketChannel和DatagramChannel)

基础一般情况下,一个通道必然关联着一个文件描述符或者是文件句柄。

通道可以是单向的,也可以是双向的(读写)。

socket通道可以是阻塞的或非阻塞的,FileChannel只支持阻塞模式。

Scatter和Gather 发散和汇聚

从字面理解,通道支持多个缓冲区同时读写。这样能够充分利用现代操作系统多核CPU功能,同时填充或排干多个缓冲区。

Scatter/Gather是一个简单却强大的概念,它是指在多个缓冲区上实现一个简单的 I/O 操作。对于一个 write 操作而言,数据是从几个缓冲区按顺序抽取(称为 gather)并沿着通道发送的。

缓冲区本身并不需要具备这种gather 的能力(通常它们也没有此能力)。该 gather 过程的效果就好比全部缓冲区的内容被连结起来,并在发送数据前存放到一个大的缓冲区中。对于 read 操作而言,从
通道读取的数据会按顺序被散布(称为 scatter)到多个缓冲区,将每个缓冲区填满直至通道中的数据或者缓冲区的最大空间被消耗完。

大多数现代操作系统都支持本地矢量 I/O(native vectored I/O)。当您在一个通道上请求一个Scatter/Gather 操作时,该请求会被翻译为适当的本地调用来直接填充或抽取缓冲区。这是一个很大
的进步,因为减少或避免了缓冲区拷贝和系统调用。Scatter/Gather 应该使用直接的 ByteBuffers 以从本地 I/O 获取最大性能优势。

Java NIO 选择器

从最基础的层面上来看,选择器提供了问询通道是否就绪操作I/O的能力,选择器可以监控注册在上面的多个通道,通道注册时会返回选择键(记录通道与选择器之间的关联关系),选择器管理者这些注册的键、和就绪状态键的集合

SelectableChannel

所有继承SelectableChannel的通道都可以在选择器中注册,FileChannel没有继承这个类,所以无法使用选择器

选择键(SelectionKey)

选择键是选择器的重点内容,选择器就绪的通道通过返回选择键集合来通知

public abstract class SelectionKey {
    public static final int OP_READ
    public static final int OP_WRITE
    public static final int OP_CONNECT
    public static final int OP_ACCEPT
    public abstract SelectableChannel channel();
    public abstract Selector selector();
    public abstract void cancel();
    public abstract boolean isValid();
    public abstract int interestOps();
    public abstract void interestOps(int ops);
    public abstract int readyOps();
    public final boolean isReadable()
    public final boolean isWritable()
    public final boolean isConnectable()
    public final boolean isAcceptable()
    public final Object attach(Object ob)
    public final Object attachment()
}

选择键维护了通道和选择器之间的关联,可以通过选择键获取Channel或Selector,键对象表示一种特殊的关联关系,当这种关系需要终止时,可以调用cancel()方法取消,调用这个方法时,不会立即被取消,而是将这个键放到被取消的集合里,当Selector下次调用select()方法时会真正被清理掉。当通道关闭时,选择键会自动被取消,当选择器关闭时,所有键都会被清理掉。

一个选择器键包含有两个准备好的操作集合,包括感兴趣的事件集合instrest和就绪的操作集合ready,通过掩码保存

感兴趣的事件集合interestOps()

通常一个键的instrest注册时就已经确认,但是可以在注册后通过interestOps(newOps)传入一个新的ops来改变这个值

channel.register(this.selector, SelectionKey.OP_READ);

上面的代码注册的键interest包含read事件,可以在对通道IO异步处理时,改变这个ops来临时取消对read事件的关注,以防止重复处理未处理完的通道

就绪的操作集合readyOps()

通过这个方法返回就绪的操作,isReadable( ),isWritable( ),isConnectable( ), 和isAcceptable( )用来判断这些操作是否就绪,进行下一步的处理

示范选择器的使用

下面列举两个示例来示范选择器的使用

单选择器单线程

public abstract class AbstractNioServer {
    protected final static String CHARSET = "utf-8";
 protected String ip;
 protected Integer port;
 protected Selector selector;
 public AbstractNioServer(String ip, Integer port) {
        this.ip = ip;
 this.port = port;
 }
    /**
 * 客户端连接请求
 *
 * @param key
 */
 protected abstract void accept(SelectionKey key) throws IOException;
 /**
 * 读取数据
 *
 * @param key
 */
 protected abstract void read(SelectionKey key) throws IOException;
 /**
 * 初始化服务器
 *
 * @throws IOException
 */ public void init() throws IOException {
        //设置服务器地址端口
 SocketAddress address = new InetSocketAddress(this.ip, this.port);
 //创建服务端通道
 ServerSocketChannel serverSocketChannel = ServerSocketChannel.open();
 //绑定服务器地址
 serverSocketChannel.bind(address);
 //设置为非阻塞模式
 serverSocketChannel.configureBlocking(false);
 //创建一个选择器
 this.selector = Selector.open();
 //将服务器通道注册到选择器中,ServerSocketChannel只支持accept事件注册,validOps返回16
 serverSocketChannel.register(this.selector, SelectionKey.OP_ACCEPT);
 }
    public void start() throws IOException {
        this.init();
 while (true) {
            int count = this.selector.select();
 if (count == 0) {
                //没有就绪的选择键
 continue;
 }
            Iterator<SelectionKey> iterator = this.selector.selectedKeys().iterator();
 while (iterator.hasNext()) {
                SelectionKey key = iterator.next();
 if (!key.isValid()) {
                    continue;
 }
                if (key.isAcceptable()) {
                    //连接请求
 accept(key);
 } else if (key.isReadable()) {
                    //修改键的感兴趣事件,防止被 select 重复调用,处理完事件后及时恢复
 key.interestOps(key.interestOps() & (~SelectionKey.OP_READ));
 //读取消息
 read(key);
 }
                iterator.remove();
 }
        }
    }
    /**
 * 恢复键的感兴趣事件
 * @param key
 */
 protected void resumeInterOpsRead(SelectionKey key) {
        //还原key的感兴趣事件
 key.interestOps(key.interestOps() | SelectionKey.OP_READ);
 //唤醒selector的select事件
 key.selector().wakeup();
 }
}
public class SingleNioServer extends AbstractNioServer {
    public static void main(String[] args) {
        SingleNioServer server = new SingleNioServer("127.0.0.1", 1008);
 try {
            server.start();
 } catch (IOException e) {
            e.printStackTrace();
 }
    }
    public SingleNioServer(String ip, Integer port) {
        super(ip, port);
 }
    @Override
 protected void accept(SelectionKey key) throws IOException {
        ServerSocketChannel serverChannel = (ServerSocketChannel) key.channel();
 //SocketChannel支持 read、write、connect 事件注册,validOps返回13=1+4+8
 SocketChannel channel = serverChannel.accept();
 if (channel == null) {
            return;
 }
        System.out.println("新的连接请求");
 channel.configureBlocking(false);
 //如果是阻塞通道进行注册,会抛出 IllegalBlockingModeException 异常
 channel.register(this.selector, SelectionKey.OP_READ);
 }
    @Override
 protected void read(SelectionKey key) throws IOException {
        SocketChannel channel = (SocketChannel) key.channel();
 try {
            ByteBuffer buffer = ByteBuffer.allocate(1024);
 int len = channel.read(buffer);
 buffer.flip();
 if (len > 0) {
                String str = Charset.forName(CHARSET).decode(buffer).toString();
 System.out.println("客户端消息:" + str);
 String msg = "消息已收到";
 byte[] sendData = msg.getBytes(CHARSET);
 ByteBuffer sendBuffer = ByteBuffer.wrap(sendData);
 channel.write(sendBuffer);
 super.resumeInterOpsRead(key);
 } else if (len == -1) {
                System.out.println("socket client close");
 key.cancel();
 channel.close();
 }
        } catch (IOException ex) {
            key.cancel();
 channel.close();
 }
    }
}

单选择器多线程

public class MulitpleNioServer extends AbstractNioServer {
    public static void main(String[] args) throws IOException {
        MulitpleNioServer server = new MulitpleNioServer("127.0.0.1", 1008);
 server.start();
 }
    /**
 * 线程池
 */
 private ExecutorService executorService = Executors.newFixedThreadPool(5);
 public MulitpleNioServer(String ip, Integer port) {
        super(ip, port);
 }
    @Override
 protected void accept(SelectionKey key) throws IOException {
        ServerSocketChannel serverChannel = (ServerSocketChannel) key.channel();
 //SocketChannel支持 read、write、connect 事件注册,validOps返回13=1+4+8
 SocketChannel channel = serverChannel.accept();
 System.out.println("新的连接请求");
 channel.configureBlocking(false);
 channel.register(this.selector, SelectionKey.OP_READ);
 }
    @Override
 protected void read(SelectionKey key) throws IOException {
        executorService.submit(new Runnable() {
            @Override
 public void run() {
                readData(key);
 }
        });
 }
    private void readData(SelectionKey key) {
        SocketChannel channel = (SocketChannel) key.channel();
 try {
            ByteBuffer buffer = ByteBuffer.allocate(1024);
 if (channel.isOpen()) {
                if (channel.isConnected()) {
                    int len = channel.read(buffer);
 buffer.flip();
 if (len > 0) {
                        String str = Charset.forName(CHARSET).decode(buffer).toString();
 System.out.println("客户端消息:" + str);
 String msg = "消息已收到";
 byte[] sendData = msg.getBytes(CHARSET);
 ByteBuffer sendBuffer = ByteBuffer.wrap(sendData);
 channel.write(sendBuffer);
 } else if (len == -1) {
                        System.out.println("socket client close1");
 key.cancel();
 channel.close();
 }
                    super.resumeInterOpsRead(key);
 }
            }
        } catch (IOException ex) {
            System.out.println("client is close2");
 key.cancel();
 try {
                channel.close();
 } catch (IOException e) {
                e.printStackTrace();
 }
        }
    }
}

以上就是Java NIO 通道概念选择器使用示例详解的详细内容,更多关于Java NIO 通道选择器的资料请关注脚本之家其它相关文章!

相关文章

  • Java复制(拷贝)数组的五种方法汇总

    Java复制(拷贝)数组的五种方法汇总

    java基础在Java中我们经常需要复制一个数组,下面这篇文章主要给大家介绍了关于Java复制(拷贝)数组的五种方法,文中通过实例代码介绍的非常详细,需要的朋友可以参考下
    2023-03-03
  • 教你用JAVA写文本编辑器(二)

    教你用JAVA写文本编辑器(二)

    之前介绍了JAVA写文本编辑器的基本思路,下面这篇文章就来给大家介绍了关于用JAVA写文本编辑器的相关资料,文中通过实例代码介绍的非常详细,需要的朋友可以参考下
    2021-11-11
  • 一文弄懂Java中ThreadPoolExecutor

    一文弄懂Java中ThreadPoolExecutor

    ThreadPoolExecutor是Java中的一个线程池实现,它可以管理和控制多个 Worker Threads,本文就详细的介绍一下Java中ThreadPoolExecutor,具有一定的参考价值,感兴趣的可以了解一下
    2023-08-08
  • Springboot如何使用filter对request body参数进行校验

    Springboot如何使用filter对request body参数进行校验

    这篇文章主要介绍了Springboot如何使用filter对request body参数进行校验,具有很好的参考价值,希望对大家有所帮助。如有错误或未考虑完全的地方,望不吝赐教
    2022-03-03
  • 基于Java8实现提高Excel读写效率

    基于Java8实现提高Excel读写效率

    这篇文章主要介绍了基于Java8实现提高Excel读写效率,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友可以参考下
    2020-11-11
  • Java模拟微信来电提醒示例

    Java模拟微信来电提醒示例

    这篇文章主要为大家介绍了Java模拟微信来电提醒示例详解,有需要的朋友可以借鉴参考下,希望能够有所帮助,祝大家多多进步,早日升职加薪
    2022-07-07
  • SpringBoot项目如何将Bean注入到普通类中

    SpringBoot项目如何将Bean注入到普通类中

    这篇文章主要介绍了SpringBoot项目如何将Bean注入到普通类中,具有很好的参考价值,希望对大家有所帮助。如有错误或未考虑完全的地方,望不吝赐教
    2021-11-11
  • Mybatis-Plus自动生成代码的实现示例

    Mybatis-Plus自动生成代码的实现示例

    在工作中,程序员很多时候都是在写类似的代码,可以使用自动生成代码,本文主要介绍了Mybatis-Plus自动生成代码的实现示例,具有一定的参考价值,感兴趣的可以了解一下
    2023-11-11
  • Netty分布式ByteBuf使用subPage级别内存分配剖析

    Netty分布式ByteBuf使用subPage级别内存分配剖析

    这篇文章主要为大家介绍了Netty分布式ByteBuf使用subPage级别内存分配剖析,有需要的朋友可以借鉴参考下,希望能够有所帮助,祝大家多多进步,早日升职加薪
    2022-03-03
  • SpringBoot Redis用注释实现接口限流详解

    SpringBoot Redis用注释实现接口限流详解

    Redis 除了做缓存,还能干很多很多事情:分布式锁、限流、处理请求接口幂等性。。。太多太多了~今天想和小伙伴们聊聊用 Redis 处理接口限流,这也是最近的 项目涉及到这个知识点了,我就拎出来和大家聊聊这个话题
    2022-07-07

最新评论