Java 网络编程之NIO(selector)

这篇具有很好参考价值的文章主要介绍了Java 网络编程之NIO(selector)。希望对大家有所帮助。如果存在错误或未考虑完全的地方,请大家不吝赐教,您也可以点击"举报违法"按钮提交疑问。

                    本编文章意在循环渐进,可看最后一个就可以了

 

 Selector简介

Java 网络编程之NIO(selector)

  【1】创建Selector

             Selector selector = Selector.open();

  【2】channel注册到Selector

               首先channel必须是非阻塞的情况下

                channel.register(选择器,操作的类型,绑定的组件);返回的是选择键             

1)Channel 注册到后,并且一旦通道处于某种就绪的状态,就可以被选择器查询到。
这个工作,使用选择器 Selector 的 select()方法完成。select 方法的作用,对感兴
趣的通道操作,进行就绪状态的查询。
2)Selector 可以不断的查询 Channel 中发生的操作的就绪状态。并且挑选感兴趣
的操作就绪状态。一旦通道有操作的就绪状态达成,并且是 Selector 感兴趣的操作,
就会被 Selector 选中,放入 选择键集合 中。
3)一个选择键,首先是包含了注册在 Selector 的通道操作的类型,比方说
SelectionKey.OP_READ。也包含了特定的通道与特定的选择器之间的注册关系。
开发应用程序是,选择键是编程的关键。NIO 的编程,就是根据对应的选择键,进行
不同的业务逻辑处理。
4)选择键的概念,和事件的概念比较相似。一个选择键类似监听器模式里边的一个
事件。由于 Selector 不是事件触发的模式,而是主动去查询的模式,所以不叫事件
Event,而是叫 SelectionKey 选择键。

【3】轮询查询就绪操作

1 )通过 Selector select ()方法,可以查询出已经就绪的通道操作,这些就绪的
状态集合,保存在一个元素是 SelectionKey 对象的 Set 集合中。
2 )下面是 Selector 几个重载的查询 select() 方法:
           - select(): 阻塞到至少有一个通道在你注册的事件上就绪了。
           - select(long timeout) :和 select() 一样,但最长阻塞事件为 timeout 毫秒。
           - selectNow(): 非阻塞,只要有通道就绪就立刻返回。
select() 方法返回的 int 值,表示有多少通道已经就绪,更准确的说,是自前一次 select
方法以来到这一次 select 方法之间的时间段上,有多少通道变成就绪状态。
例如:首次调用 select()方法,如果有一个通道变成就绪状态,返回了 1,若再次调用
select()方法,如果另一个通道就绪了,它会再次返回 1。如果对第一个就绪的
channel 没有做任何操作,现在就有两个就绪的通道,但在每次 select()方法调用之间,
只有一个通道就绪了。
Java 网络编程之NIO(selector)
Java 网络编程之NIO(selector)

 【4】停止选择的方法

选择器执行选择的过程, 系统底层会依次询问每个通道是否已经就绪 ,这个过程可能会造成调用线程进入阻塞状态,那么我们有以下方式可以唤醒在 select()方法中阻塞的线程。
        wakeup()方法 :通过调用 Selector 对象的 wakeup()方法让处在阻塞状态的 select()方法立刻返回该方法使得选择器上的第一个还没有返回的选择操作立即返回。如果当前没有进行中 的选择操作,那么下一次对 select()方法的一次调用将立即返回。
        close()方法 :通过 close()方法关闭 Selector, 该方法使得任何一个在选择操作中阻塞的线程都被唤醒(类似 wakeup()),同时 使得注册到该 Selector 的所有 Channel 被注销,所有的键将被取消, 但是 Channel 本身并不会关闭

NIO 编程步骤

第一步:创建 Selector 选择器
第二步:创建 ServerSocketChannel 通道,并绑定监听端口
第三步:设置 Channel 通道是非阻塞模式
第四步:把 Channel 注册到 Socketor 选择器上,监听连接事件
第五步:调用 Selector 的 select 方法(循环调用),监测通道的就绪状况
第六步:调用 selectKeys 方法获取就绪 channel 集合
第七步:遍历就绪 channel 集合,判断就绪事件类型,实现具体的业务操作
第八步:根据业务,决定是否需要再次注册监听事件,重复执行第三步操作

代码1.0

public class SelectorServer {
    public static void main(String[] args) throws Exception {
        ServerSocketChannel  ssc = ServerSocketChannel.open();
        ssc.bind(new InetSocketAddress(8080));
        ssc.configureBlocking(false);
        //创建selector选择器
        Selector selector = Selector.open();
        //将ssc注册到选器器(建立两者的联系)
        SelectionKey sscKey = ssc.register(selector, 0, null);
        //选择哪种监听的事件
        sscKey.interestOps(SelectionKey.OP_ACCEPT);

        while (true){
            selector.select();//阻塞方法,如果没有事件发生,线程将在此处停止
            Iterator<SelectionKey> iterator = selector.selectedKeys().iterator();//返回所有可能发生事件的key集合(set)
            while (iterator.hasNext()){
                SelectionKey key = iterator.next();
                System.out.println("key::"+key);
                ServerSocketChannel channel = (ServerSocketChannel) key.channel();//获取相对应的channel
                SocketChannel sc = channel.accept();
                sc.configureBlocking(false);
                SelectionKey scKey = sc.register(selector, 0, null);
                System.out.println("scKey---->"+scKey);
                scKey.interestOps(SelectionKey.OP_READ);
                System.out.println("sc已经在selector中注册了!");
            }
        }
    }
}

 结果分析:

Java 网络编程之NIO(selector)

     

Java 网络编程之NIO(selector)

 解决方案

【1】区分触发seletor.select()的事件

【2】处理完一个事件,在对应的注册的keys集合删除。

 代码2.0

public class SelectorServer {
    public static void main(String[] args) throws Exception {
        ServerSocketChannel  ssc = ServerSocketChannel.open();
        ssc.bind(new InetSocketAddress(8080));
        ssc.configureBlocking(false);
        Selector selector = Selector.open();
        SelectionKey sscKey = ssc.register(selector, 0, null);
        sscKey.interestOps(SelectionKey.OP_ACCEPT);

        while (true){
            selector.select();//阻塞方法,如果没有事件发生,线程将在此处停止
            Iterator<SelectionKey> iterator = selector.selectedKeys().iterator();//返回所有可能发生事件的key集合(set)
            while (iterator.hasNext()){
                SelectionKey key = iterator.next();
                iterator.remove();//解决处理后事件在set集合中还有的现象
                if (key.isAcceptable()){//区分不同事件触发的结果
                    ServerSocketChannel channel = (ServerSocketChannel) key.channel();//获取相对应的channel
                    SocketChannel sc = channel.accept();
                    sc.configureBlocking(false);
                    SelectionKey scKey = sc.register(selector, 0, null);
                    System.out.println("scKey---->"+scKey);
                    scKey.interestOps(SelectionKey.OP_READ);
                    System.out.println("sc已经在selector中注册了!");
                }else if (key.isReadable()){
                    SocketChannel channel = (SocketChannel) key.channel();
                    ByteBuffer buffer = ByteBuffer.allocate(32);
                    int len = channel.read(buffer);
                    buffer.flip();
                    System.out.println(StandardCharsets.UTF_8.decode(buffer).toString());
                    buffer.clear();
                }


            }
        }
    }
}

Java 网络编程之NIO(selector)

 新的问题 如果ByteBuffer分配的空间不够用会出现什么结果(分析)

       当一次没有读完后就会触发下一次的读取,

Java 网络编程之NIO(selector)

 解决方案:为每一个连接客户端的channel的ByteBuffer的空间动态扩容,(可以避免黏包半包的情况!)

Java 网络编程之NIO(selector)

 新的问题:

当客户端强制关机,服务器停止

Java 网络编程之NIO(selector)

 当客户端正常结束,服务器进入无限循环

Java 网络编程之NIO(selector)

解决方案:

客户端强制关机,服务器报异常,使用try--catch语句  key.cancel()对此不做任何事情

客户端正常结束,客户端向服务器发送数据,read = -1;

代码3.0(最终篇)

public class SelectorServer {
    
    public static void main(String[] args) throws Exception {
        ServerSocketChannel  ssc = ServerSocketChannel.open();
        ssc.bind(new InetSocketAddress(8080));
        ssc.configureBlocking(false);
        Selector selector = Selector.open();
        SelectionKey sscKey = ssc.register(selector, 0, null);
        sscKey.interestOps(SelectionKey.OP_ACCEPT);

        while (true){
            selector.select();//阻塞方法,如果没有事件发生,线程将在此处停止
            Iterator<SelectionKey> iterator = selector.selectedKeys().iterator();//返回所有可能发生事件的key集合(set)
            while (iterator.hasNext()){
                SelectionKey key = iterator.next();
                iterator.remove();//解决处理后事件在set集合中还有的现象
                if (key.isAcceptable()){//区分不同事件触发的结果
                    ServerSocketChannel channel = (ServerSocketChannel) key.channel();//获取相对应的channel
                    SocketChannel sc = channel.accept();
                    sc.configureBlocking(false);
                    SelectionKey scKey = sc.register(selector, 0, null);
                    System.out.println("scKey---->"+scKey);
                    ByteBuffer buffer = ByteBuffer.allocate(4);
                    scKey.attach(buffer);//为每一个注册到set集合中的channel分配独立的缓冲区
                    scKey.interestOps(SelectionKey.OP_READ);
                    System.out.println("sc已经在selector中注册了!");
                }else if (key.isReadable()){
                    try {
                        SocketChannel channel = (SocketChannel) key.channel();
                        ByteBuffer buffer = (ByteBuffer) key.attachment();
                        int read = channel.read(buffer);
//                    System.out.println("read::"+read);
//                    System.out.println("positon:"+buffer.position()+"limit:"+buffer.limit());
                        System.out.println(buffer);
                        if (read == -1){//客户端正常结束,read的值等于-1
                            key.cancel();
                            continue;
                        }
                        if(read == buffer.capacity()){
                            ByteBuffer newBuffer = ByteBuffer.allocate(buffer.capacity()*2);
                            newBuffer.flip();
                            newBuffer.put(buffer);
                            key.attach(newBuffer);
                        }else{
                            buffer.flip();
                            System.out.println(StandardCharsets.UTF_8.decode(buffer).toString());
                            buffer.clear();
                        }
                    } catch (IOException e) {
                        e.printStackTrace();
                        key.cancel();
                    }

                }


            }
        }
    }
}

结果:

客户端正常结束

Java 网络编程之NIO(selector)

 客户端强制结束Java 网络编程之NIO(selector)

 

 Selector写事件与读事件类似

服务器

public class SelectorWriter {
    public static void main(String[] args) throws Exception {
        ServerSocketChannel ssc = ServerSocketChannel.open();
        ssc.configureBlocking(false);
        ssc.bind(new InetSocketAddress(8080));

        Selector selector = Selector.open();
        ssc.register(selector, SelectionKey.OP_ACCEPT, null);

        while (true){
            selector.select();
            Iterator<SelectionKey> iterator = selector.selectedKeys().iterator();
            while (iterator.hasNext()){
                SelectionKey key = iterator.next();
                iterator.remove();
                if (key.isAcceptable()){
                    SocketChannel sc = ssc.accept();
                    sc.configureBlocking(false);
                    SelectionKey sckey = sc.register(selector, SelectionKey.OP_READ);

                    //发送大量的数据
                    StringBuilder str = new StringBuilder();
                    for (int i = 0; i < 300000; i++) {
                        str.append("a");
                    }
                    ByteBuffer buffer = Charset.defaultCharset().encode(str.toString());
                    int write = sc.write(buffer);
                    System.out.println(write);
                    if (buffer.hasRemaining()){
                        // 4. 关注可写事件
                        sckey.interestOps(sckey.interestOps() + SelectionKey.OP_WRITE);
                       // sckey.interestOps(sckey.interestOps() | SelectionKey.OP_WRITE);
                        // 5. 把未写完的数据挂到 sckey 上
                        sckey.attach(buffer);
                    }

                }else if(key.isWritable()){
                    SocketChannel sc = (SocketChannel) key.channel();
                    ByteBuffer buffer = (ByteBuffer) key.attachment();
                    int write = sc.write(buffer);
                    System.out.println(write);
                    //清理工作
                    if (!buffer.hasRemaining()){
                        key.attach(null);
                        key.interestOps(key.interestOps() - SelectionKey.OP_WRITE);
                    }
                
                }
            }
        }
    }
}

 客户端

public class WriterClient {
    public static void main(String[] args) throws Exception {
        SocketChannel sc = SocketChannel.open();
        sc.connect(new InetSocketAddress("localhost", 8080));

        // 3. 接收数据
        int count = 0;
        while (true) {
            ByteBuffer buffer = ByteBuffer.allocate(1024 * 1024);
            count += sc.read(buffer);
            System.out.println(count);
            buffer.clear();
        }
    }
}

Java 网络编程之NIO(selector)文章来源地址https://www.toymoban.com/news/detail-410041.html

到了这里,关于Java 网络编程之NIO(selector)的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处: 如若内容造成侵权/违法违规/事实不符,请点击违法举报进行投诉反馈,一经查实,立即删除!

领支付宝红包 赞助服务器费用

相关文章

  • BIO、NIO、IO多路复用模型详细介绍&Java NIO 网络编程

    上文介绍了网络编程的基础知识,并基于 Java 编写了 BIO 的网络编程。我们知道 BIO 模型是存在巨大问题的,比如 C10K 问题,其本质就是因其阻塞原因,导致如果想要承受更多的请求就必须有足够多的线程,但是足够多的线程会带来内存占用问题、CPU上下文切换带来的性能问题

    2024年02月14日
    浏览(32)
  • Java网络编程----通过实现简易聊天工具来聊聊NIO

    前文我们说过了BIO,今天我们聊聊NIO。 NIO 是什么?NIO官方解释它为 New lO ,由于其特性我们也称之为,Non-Blocking IO。这是jdk1.4之后新增的一套IO标准。 为什么要用NIO呢? 我们再简单回顾下BIO: 阻塞式IO,原理很简单,其实就是多个端点与服务端进行通信时,每个客户端有一个

    2024年02月05日
    浏览(48)
  • NIO基础 - 网络编程

    non-blocking io 非阻塞 IO 1.1 Channel Buffer channel 有一点类似于 stream,它就是读写数据的 双向通道 ,可以从 channel 将数据读入 buffer,也可以将 buffer 的数据写入 channel,而之前的 stream 要么是输入,要么是输出,channel 比 stream 更为底层 常见的 Channel 有 FileChannel DatagramChannel SocketCh

    2024年02月02日
    浏览(27)
  • 10.NIO 网络编程应用实例-群聊系统

    需求:进一步理解 NIO 非阻塞网络编程机制,实现多人群聊 编写一个 NIO 群聊系统,实现客户端与客户端的通信需求(非阻塞) 服务器端:可以监测用户上线,离线,并实现消息转发功能 客户端:通过 channel 可以无阻塞发送消息给其它所有客户端用户,同时可以接受其它客户端用

    2024年02月15日
    浏览(28)
  • 由浅入深Netty基础知识NIO网络编程

    阻塞模式下,相关方法都会导致线程暂停 ServerSocketChannel.accept 会在没有连接建立时让线程暂停 SocketChannel.read 会在没有数据可读时让线程暂停 阻塞的表现其实就是线程暂停了,暂停期间不会占用 cpu,但线程相当于闲置 单线程下,阻塞方法之间相互影响,几乎不能正常工作,

    2024年02月05日
    浏览(43)
  • 【Netty专题】【网络编程】从OSI、TCP/IP网络模型开始到BIO、NIO(Netty前置知识)

    我是有点怕网络编程的,总有点【谈网色变】的感觉。为了让自己不再【谈网色变】,所以我想过系统学习一下,然后再做个笔记这样,加深一下理解。但是真要系统学习,其实还是要花费不少时间的,所以这里也只是简单的,尽可能地覆盖一下,梳理一些我认为比较迫切需

    2024年02月06日
    浏览(48)
  • Java NIO Selector选择器源码分析

    Java NIO(New I/O)的Selector选择器是一个用于多路复用(Multiplexing)的I/O操作的关键组件。它允许一个单独的线程监视多个通道(Channel)的可读性和可写性,从而有效地管理大量并发连接。 AbstractSelector主要实现了Selector的打开关闭的状态维护,支持异步关闭和中断的begin和end方

    2024年04月10日
    浏览(27)
  • Java-NIO篇章(4)——Selector选择器详解

    选择器(Selector)是什么呢?选择器和通道的关系又是什么?这里详细说明,假设不用选择器,那么一个客户端请求数据传输那就需要建立一个连接,为了避免线程阻塞,那么每个客户端开辟一个线程。而学过JVM的都知道,默认每开一个线程需要栈空间内存1MB大小。如果这时

    2024年01月21日
    浏览(25)

觉得文章有用就打赏一下文章作者

支付宝扫一扫打赏

博客赞助

微信扫一扫打赏

请作者喝杯咖啡吧~博客赞助

支付宝扫一扫领取红包,优惠每天领

二维码1

领取红包

二维码2

领红包