Java Netty 学习(五) - NIO基础知识Selector

版权声明:本博客所有的原创文章,转载请注明出处,作者皆保留版权。 https://blog.csdn.net/anLA_/article/details/79672917

Selector选择器,可以用来检测Java NIO 中的channel是否可用(connect,read,write,accept),并能够知晓通道是否为诸如读写事件做好准备的组件。这样,一个单独的线程可以管理多个channel,从而管理多个网络连接。

Selector就相当与一个管家,利用它来知晓,哪个channel是否可用,怎样形式的可用等等。

Why use Selector

毫无疑问,Selector带来了更好的设计,以及更加舒适的开发体验。
另一方面,仅用单个线程来处理多个Channels,一方面可以节省线程资源,另外,
线程之间上下文切换的开销较大,而且每个线程都要占用系统的一些资源(如内存)。

题外:
也不能一味的去追求少线程,因为现在电脑cpu基本都是多核,如果不使用多任务,那是对cpu资源的浪费。

用法

下面看看一个典型用法:

    private static void selector(){
        ServerSocketChannel serverChannel = ServerSocketChannel.open();   //打开一个serverChannel
        ServerSocket serverSocket = serverChannel.socket( );
        Selector selector = Selector.open( );          //打开一个selector
        serverSocket.bind (new InetSocketAddress (8089));
        serverChannel.configureBlocking (false);             //非阻塞模式
        serverChannel.register (selector, SelectionKey.OP_ACCEPT);     //给serverChannel注册一个selector,对OP_ACCEPT感兴趣
        //然后,就可以正对selector做文章了,它会告诉你是否有新socketChannel线程连上来
        while (true) {
            int n = selector.selectNow();      //非阻塞式选择
            if (n == 0) {
                continue; // nothing to do
            }
            Iterator it = selector.selectedKeys().iterator();  //得到所有SelectionKey 
            while (it.hasNext( )) {
                SelectionKey key = (SelectionKey)it.next();  //得到一个SelectionKey
                if (key.isAcceptable()) {
                    //如果接受到了一个新连接
                    ServerSocketChannel server = (ServerSocketChannel) key.channel( );
                    SocketChannel channel = server.accept( );
                    if (channel == null) {
                        ;//handle code, could happen
                    }
                    channel.configureBlocking (false);    //非阻塞式
                    //给收到的这个channel,也注册给这个selector,并且关注它的op_read事件
                    channel.register (selector, SelectionKey.OP_READ); 

                }
                if (key.isReadable()) {  //可以读了
                    readData(key);  //说明是socketChannel的事件,那么就读数据
                }
                it.remove();//处理完这个事件,然后移除
            }
        }
    }

可以总结为以下几个步骤:

  1. 首先,打开一个并获得一个selector,这个selector就像一个管家,你既可以注册socketChannel,也可以注册ServerSocketChannel等等,但是不能为FileChannel,与Selector一起使用时,Channel必须处于非阻塞模式下。
  2. 其次,自旋搜索,使用select或者selectNow去搜索下,看有没有可用的channel(包括四种事件)。
  3. 如果有可用的,循环遍历它的所有SelectionKey。并且用if去判定每个SelectionKey处于那种状态(可以有多个)
  4. 最后操作完,记得要remove这个key。

下面针对方法详细了解:

open

使用Selector时候,需要用open打开:

Selector selector = Selector.open();

register

每像一个selector注册一个channel,就意味着selector可以管辖这个channel了,当channel有活动时,它也能搜查得到。

channel.configureBlocking(false);
SelectionKey key = channel.register(selector,Selectionkey.OP_READ);

注意几点:
1. register的第二个参数,Selectionkey.OP_READ , 说明当这个channel有OP_READ信息时,selector会搜查到,并最后返回,
而这个channel的其他的几个信息,selector并不会收集他们。
2. 另一方面,如果你对多个信息感兴趣,可以这样用“或运算”标识:

int interestSet = SelectionKey.OP_READ | SelectionKey.OP_WRITE;

再把interestSet作为第二个参数添加进去。

  1. register还有个重载方法:public final SelectionKey register(Selector sel, int ops, Object att)
    很明显,多了一个att,这个att主要用来传递数据并且标识channel的,后面获得的selectKey中,可以获得这个att,最终就能
    知道是哪个socket。可以想想做一个简单聊天系统,att是不是能很简单的标识每位用户呢?

select和selectNow

在循环中,使用select方法进行筛选,看是否有就绪的channel,有以下几个select方法:

  • public int selectNow() throws IOException :非阻塞式,立即返回,为0说明
  • public int select(long timeout) throws IOException :阻塞式,有超时的,非阻塞式
  • public int select() throws IOException:阻塞式,有才返回

SelectionKey

当如果select返回有数据,就可以从select里面获得一个SelectionKey的set集合,那么SelectionKey里面又主要有什么呢?
1. interest集合,就是开始注册的那些,可以这样获得:

int interestSet = selectionKey.interestOps();
//通过判定,是否有accpt的属性
boolean isInterestedInAccept  = (interestSet & SelectionKey.OP_ACCEPT) == SelectionKey.OP_ACCEPT;
  1. ready集合
    和前一个相比,这个是channel里面有的集合,就是说明里面已经有了以下事件(connect,accept,read,write等)
    可以这样检测:
int readySet = selectionKey.readyOps();
boolean isInterestedInAccept  = (readySet & SelectionKey.OP_ACCEPT) == SelectionKey.OP_ACCEPT;

也可以这样直接通过方法获得:

selectionKey.isAcceptable();
selectionKey.isConnectable();
selectionKey.isReadable();
selectionKey.isWritable();

附加对象

即上面所讲的在register里面添加一个att,这样获得:

selectionKey.attach(theObject);
Object attachedObj = selectionKey.attachment();

Selector和Channel

当然,也可以获得大管家Selector和通道Channel:

Channel  channel  = selectionKey.channel();
Selector selector = selectionKey.selector();

wakeUp

某个线程A调用select()方法后阻塞了,即使没有通道已经就绪,也有办法让其从select()方法返回。只要让其它线程在第一个线程调用select()方法的那个对象上调用Selector.wakeup()方法即可。阻塞在select()方法上的线程(原来那个线程A)会立马返回。

猜你喜欢

转载自blog.csdn.net/anLA_/article/details/79672917