Java 网络编程之NIO(selector)

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

 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()方法调用之间,
只有一个通道就绪了。

 【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中注册了!");
            }
        }
    }
}

 结果分析:

     

 解决方案

【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();
                }


            }
        }
    }
}

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

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

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

 新的问题:

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

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

解决方案:

客户端强制关机,服务器报异常,使用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();
                    }

                }


            }
        }
    }
}

结果:

客户端正常结束

 客户端强制结束

 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();
        }
    }
}

猜你喜欢

转载自blog.csdn.net/qq_57533658/article/details/130004236