网络编程--NIO--Selector

参考:https://www.cnblogs.com/crazymakercircle/p/9826906.html
https://www.cnblogs.com/ostenant/p/9695187.html

Selector的和Channel的关系

Java NIO的核心组件包括:

(1)Channel(通道)

(2)Buffer(缓冲区)

(3)Selector(选择器)

其中Channel和Buffer比较好理解 ,联系也比较密切,他们的关系简单来说就是:数据总是从通道中读到buffer缓冲区内,或者从buffer写入到通道中。

选择器和他们的关系又是什么?

选择器(Selector) 是 Channel(通道)的多路复用器,Selector 可以同时监控多个 通道的 IO(输入输出) 状况。

Selector的作用是什么?

选择器提供选择执行已经就绪的任务的能力。从底层来看,Selector提供了询问通道是否已经准备好执行每个I/O操作的能力。Selector 允许单线程处理多个Channel。仅用单个线程来处理多个Channels的好处是,只需要更少的线程来处理通道。事实上,可以只用一个线程处理所有的通道,这样会大量的减少线程之间上下文切换的开销。

要学会selector首先要知道SelectableChannel和SelectionKey

可选择通道(SelectableChannel)
**并不是所有的Channel,都是可以被Selector 复用的。比方说,FileChannel就不能被选择器复用。**为什么呢?

判断一个Channel 能被Selector 复用,有一个前提:判断他是否继承了一个抽象类SelectableChannel。如果继承了SelectableChannel,则可以被复用,否则不能。

SelectableChannel类是何方神圣?

**SelectableChannel类提供了实现通道的可选择性所需要的公共方法。**它是所有支持就绪检查的通道类的父类。所有socket通道,都继承了SelectableChannel类都是可选择的,包括从管道(Pipe)对象的中获得的通道。而FileChannel类,没有继承SelectableChannel,因此是不是可选通道。

通道和选择器注册之后,他们是绑定的关系吗?

答案是不是。不是一对一的关系。一个通道可以被注册到多个选择器上,但对每个选择器而言只能被注册一次。

通道和选择器之间的关系,使用注册的方式完成。SelectableChannel可以被注册到Selector对象上,在注册的时候,需要指定通道的哪些操作,是Selector感兴趣的。

在这里插入图片描述

选择键(SelectionKey)

可能大家最困惑的就是选择键了,这是个什么东东!
选择键从它的名字来说不太好理解,其实他就是保存了即将要执行的所有的被选择出来的事件的集合。就这样理解:选择器怎么知道要处理那个注册的通道的事件呢,有一个监听器,将监听(其实是查询)到的(比如连接事件来临),他就把他的相关的注册到选择键里面去,然后我们遍历选择器,就可以操作了,这就是异步操作,事件发生的时候再来操作!!!

Channel和Selector的关系确定好后,并且一旦通道处于某种就绪的状态,就可以被选择器查询到。这个工作,使用选择器Selector的select()方法完成。select方法的作用,对感兴趣的通道操作,进行就绪状态的查询。

Selector可以不断的查询Channel中发生的操作的就绪状态。并且挑选感兴趣的操作就绪状态。一旦通道有操作的就绪状态达成,并且是Selector感兴趣的操作,就会被Selector选中,放入选择键集合中。

一个选择键,首先是包含了注册在Selector的通道操作的类型,比方说SelectionKey.OP_READ。也包含了特定的通道与特定的选择器之间的注册关系。

开发应用程序是,选择键是编程的关键。NIO的编程,就是根据对应的选择键,进行不同的业务逻辑处理。

选择键的概念,有点儿像事件的概念。

选择键和事件的关系是什么?

一个选择键有点儿像监听器模式里边的一个事件,但是又不是。由于Selector不是事件触发的模式,而是主动去查询的模式,所以不叫事件Event,而是叫SelectionKey选择键。

Channel注册到Selector

使用Channel.register(Selector sel,int ops)方法,将一个通道注册到一个选择器时。第一个参数,指定通道要注册的选择器是谁。第二个参数指定选择器需要查询的通道操作。

可以供选择器查询的通道操作,从类型来分,包括以下四种:

(1)可读 : SelectionKey.OP_READ

(2)可写 : SelectionKey.OP_WRITE

(3)连接 : SelectionKey.OP_CONNECT

(4)接收 : SelectionKey.OP_ACCEPT

如果Selector对通道的多操作类型感兴趣,可以用“位或”操作符来实现:int key = SelectionKey.OP_READ | SelectionKey.OP_WRITE ;

注意,操作一词,是一个是使用非常泛滥,也是一个容易混淆的词。特别提醒的是,选择器查询的不是通道的操作,而是通道的某个操作的一种就绪状态。

什么是操作的就绪状态?

一旦通道具备完成某个操作的条件,表示该通道的某个操作已经就绪,就可以被Selector查询到,程序可以对通道进行对应的操作。比方说,某个SocketChannel通道可以连接到一个服务器,则处于“连接就绪”(OP_CONNECT)。再比方说,一个ServerSocketChannel服务器通道准备好接收新进入的连接,则处于“接收就绪”(OP_ACCEPT)状态。还比方说,一个有数据可读的通道,可以说是“读就绪”(OP_READ)。一个等待写数据的通道可以说是“写就绪”(OP_WRITE)。

与Selector一起使用时,Channel必须处于非阻塞模式下,否则将抛出异常IllegalBlockingModeException。这意味着,FileChannel不能与Selector一起使用,因为FileChannel不能切换到非阻塞模式,而套接字相关的所有的通道都可以。

创建Selector
Selector对象是通过调用静态工厂方法open()来实例化的

  // 1、获取Selector选择器

            Selector selector = Selector.open();

将Channel注册到Selector
要实现Selector管理Channel,需要将channel注册到相应的Selector上

// 2、获取通道

    ServerSocketChannel serverSocketChannel = ServerSocketChannel.open();

    // 3.设置为非阻塞

    serverSocketChannel.configureBlocking(false);

    // 4、绑定连接

    serverSocketChannel.bind(new InetSocketAddress(SystemConfig.SOCKET_SERVER_PORT));

    // 5、将通道注册到选择器上,并制定监听事件为:“接收”事件

    serverSocketChannel.register(selector,SelectionKey.OP_ACCEPT);

上面通过调用通道的register()方法会将它注册到一个选择器上。

首先需要注意的是:

与Selector一起使用时,Channel必须处于非阻塞模式下,否则将抛出异常IllegalBlockingModeException。这意味着,FileChannel不能与Selector一起使用,因为FileChannel不能切换到非阻塞模式,而套接字相关的所有的通道都可以。

另外,还需要注意的是:

一个通道,并没有一定要支持所有的四种操作。比如服务器通道ServerSocketChannel支持Accept 接受操作,而SocketChannel客户端通道则不支持。可以通过通道上的validOps()方法,来获取特定通道下所有支持的操作集合。

轮询查询就绪操作
万事俱备,可以开干。下一步是查询就绪的操作。

通过Selector的select()方法,可以查询出已经就绪的通道操作,这些就绪的状态集合,包存在一个元素是SelectionKey对象的Set集合中。

下面是Selector几个重载的查询select()方法:

(1)select():阻塞到至少有一个通道在你注册的事件上就绪了。

(2)select(long timeout):和select()一样,但最长阻塞事件为timeout毫秒。

(3)selectNow():非阻塞,只要有通道就绪就立刻返回。

select()方法返回的int值,表示有多少通道已经就绪,更准确的说,是自前一次select方法以来到这一次select方法之间的时间段上,有多少通道变成就绪状态。

一旦调用select()方法,并且返回值不为0时,下一步工干啥?

通过调用Selector的selectedKeys()方法来访问已选择键集合,然后迭代集合的每一个选择键元素,根据就绪操作的类型,完成对应的操作:

//select()方法返回的int值,表示有多少通道已经就绪,更准确的说,
// 是自前一次select方法以来到这一次select方法之间的时间段上,有多少通道变成就绪状态。
//                判断现在是否有可以执行的通道
                int number = selector.select();
                if(number == 0){
                    continue;
                }
Set selectedKeys = selector.selectedKeys();

Iterator keyIterator = selectedKeys.iterator();

while(keyIterator.hasNext()) {

    SelectionKey key = keyIterator.next();

    if(key.isAcceptable()) {

        // a connection was accepted by a ServerSocketChannel.

    } else if (key.isConnectable()) {

        // a connection was established with a remote server.

    } else if (key.isReadable()) {

        // a channel is ready for reading

    } else if (key.isWritable()) {

        // a channel is ready for writing

    }

    keyIterator.remove();

}

NIO 完整的代码实例:

package com.example.dtest.nio.selector;

import java.io.IOException;
import java.net.InetSocketAddress;
import java.nio.ByteBuffer;
import java.nio.channels.SelectionKey;
import java.nio.channels.Selector;
import java.nio.channels.ServerSocketChannel;
import java.nio.channels.SocketChannel;
import java.util.Iterator;
import java.util.Set;
import java.util.concurrent.TimeUnit;

public class SelectorTest {

    public static void main(String[] args) {


        try {
            ServerSocketChannel serverSocketChannel = ServerSocketChannel.open();
            serverSocketChannel.configureBlocking(false);

            serverSocketChannel.socket().bind(new InetSocketAddress(25000));

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

            while (true){
                System.out.println("1111111111111");
//select()方法返回的int值,表示有多少通道已经就绪,更准确的说,
// 是自前一次select方法以来到这一次select方法之间的时间段上,有多少通道变成就绪状态。
//                判断现在是否有可以执行的通道
                int number = selector.select();
                if(number == 0){
                    continue;
                }

                Set<SelectionKey> selectionKeys = selector.selectedKeys();
                Iterator<SelectionKey> iterator = selectionKeys.iterator();
                while (iterator.hasNext()){

                    SelectionKey selectionKey = iterator.next();

                    // 9、判断key是具体的什么事件
                    if(selectionKey.isAcceptable()){
                        System.out.println("acceptable");
                        try {
                            TimeUnit.MICROSECONDS.sleep(10);
                        } catch (InterruptedException e) {
                            e.printStackTrace();
                        }
//                        获取客户通道,若接受的事件是“接收就绪” 操作,就获取客户端连接
//                        SocketChannel socketChannel = (SocketChannel) selectionKey.channel();
                        SocketChannel socketChannel = serverSocketChannel.accept();
//                        切换为非阻塞模式
                        socketChannel.configureBlocking(false);
//                        将客户通道注册到选择器上
                        socketChannel.register(selector, SelectionKey.OP_READ);
                    }

                    if(selectionKey.isReadable()){
                        System.out.println("read");
                        try {
                            TimeUnit.MICROSECONDS.sleep(10);
                        } catch (InterruptedException e) {
                            e.printStackTrace();
                        }
                        // 13、获取该选择器上的“读就绪”状态的通道
                        SocketChannel socketChannel = (SocketChannel) selectionKey.channel();

                        // 14、读取数据
                        ByteBuffer byteBuffer = ByteBuffer.allocate(1024);
                        int length = 0;
                        while ((length = socketChannel.read(byteBuffer)) != -1)
                        {
                            byteBuffer.flip();
                            if(! byteBuffer.hasRemaining() ){
                                break;
                            }
                            System.out.println(new String(byteBuffer.array(), 0, length));
                            byteBuffer.clear();
                            try {
                                TimeUnit.MICROSECONDS.sleep(10);
                            } catch (InterruptedException e) {
                                e.printStackTrace();
                            }
                        }
//                        下面有继续的判断操作(Writ、Connect)就不能关掉socketChannel
//                        socketChannel.close();

                    }

                    if(selectionKey.isWritable()){
                        System.out.println("write");
                    }

                    if(selectionKey.isConnectable()){
                        System.out.println("connectable");
                    }

                    //                  移除选择键
                    iterator.remove();

                }

//                关闭连接
                serverSocketChannel.close();


            }


        } catch (IOException e) {
            e.printStackTrace();
        }


    }

}

客户访问端:

package com.example.dtest.nio.channel.socketchannel.block;

import java.io.IOException;
import java.net.InetSocketAddress;
import java.nio.ByteBuffer;
import java.nio.channels.SocketChannel;
import java.util.concurrent.TimeUnit;

public class SocketChannelBlockTest {

    public static void main(String[] args) {

        try {
            blockingWrite();
        } catch (Exception e) {
            e.printStackTrace();
        }


    }

    public static  void blockingWrite() throws Exception{

        try {
            SocketChannel socketChannel = SocketChannel.open();
            socketChannel.connect(new InetSocketAddress("127.0.0.1",25000));

            ByteBuffer byteBuffer = ByteBuffer.allocate(1024);
            byteBuffer.put("Client Blocking SocketChannel".getBytes());
            byteBuffer.flip();

            while (byteBuffer.hasRemaining()){
                socketChannel.write(byteBuffer);
            }

            TimeUnit.SECONDS.sleep(2000);
            socketChannel.close();

        } catch (IOException e) {
            e.printStackTrace();
        }

    }

}

结果:
在这里插入图片描述

在这里插入图片描述

在这里插入图片描述

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值