Java网络编程(8)NIO - Selector详解

前言

大概的了解了NIO的运行与三个组件
Java网络编程(4)NIO的理解与NIO的三个组件
并详细学习了:Buffer和Channel
Java网络编程(5)NIO - Buffer详解
Java网络编程(6)NIO - Channel详解

接下来完成Selector的学习

目录

  1. Selector作用
  2. Selector类继承关系
  3. 常用方法
  4. SelectionKey
  5. 一个聊天系统案例
  6. 总结

Selector作用

NIO编程的结构:
Java网络编程(8)NIO - Selector详解

Java网络编程(8)NIO - Selector详解
选择器让一个线程能够处理多个通道,选择器轮询注册在其上的通道,Selector只能管理非阻塞的通道,文件通道(FileChannel等等)是阻塞的,无法管理

所以Selector是网络通信通道的选择器,最常见的是对ServerSocketChannel和SocketChannel的操作

Selector类继承关系

Java网络编程(8)NIO - Selector详解
Selector是一个抽象类,有多个子类,其中实现类是WindowsSelectorImpl
Java网络编程(8)NIO - Selector详解

常用方法

Selector自身有这些方法:
Java网络编程(8)NIO - Selector详解

  1. 实例化
    因为Selector是抽象类,即不能new 实例化,要通open()方法获得一个Selector对象
Selector selector = Selector.open();
  1. select()
    select()方法是监控所有注册在选择器上的通道,当有IO操作可以进行时,将该通道对应的SelectionKey加入集合并返回(SelectionKey是一个重要的对象)
    Java网络编程(8)NIO - Selector详解
  2. select(long timeout)
    因为select方法是阻塞的,即一定要等到一个通道有事件发生,可以设置TimeOut时间解除阻塞
  3. selectNow()
    非阻塞的select方法
  4. selectedKeys()
    获得存在事件的SelectionKey
  5. keys()
    保存了所有已注册的通道的SelectionKey
  6. isOpen()
    判断该选择器是否打开
  7. wakeup()
    唤醒选择器
    selector.select()是阻塞的,通常情况下,只有注册在selector上的channel有事件就绪时,select()才会从阻塞中被唤醒,处理就绪事件,当selector上的channel无就绪事件时,如果想要唤醒阻塞在select()操作上的线程去处理一些别的工作就可以通过wakeup方法

SelectionKey

Java网络编程(8)NIO - Selector详解
SelectionKey是一个抽象类,实现类是SelectionKeyImpl类

SelectionKey表示SelectableChannel在Selector中注册的标识.每个Channel向Selector注册时,都将会创建一个selectionKey

SelectableChannel是各种网络通道的父类,可以向下转型为我们想要的Channel

Java网络编程(8)NIO - Selector详解

使用

SelectionKey对象。这个对象包含了一些属性:

  • interest集合:所选择的感兴趣的事件集合
  • ready集合:通道已经准备就绪的操作的集合
  • Channel:SelectionKey可以得到标记的Channel对象
  • Selector:SelectionKey可以得到使用的Selector对象
  • 附加的对象(可选):可以将一个对象或者更多信息附着到SelectionKey上,这样就能方便的识别某个给定的通道,例如将Buffer附加上去

Java网络编程(8)NIO - Selector详解

  1. 操作集

通道注册到选择器时,可以选择监听的事件
例如:serverSocketChannel就需要监听请求连接事件

//注册,服务器监听请求连接事件
serverSocketChannel.register(selector, SelectionKey.OP_ACCEPT);

SelectionKey.OP_ACCEPT就是一个操作集

SelectionKey有四个操作集,是位运算值:

Java网络编程(8)NIO - Selector详解OP_READ值为1
OP_WRITE值为4
OP_CONNECT值为8
OP_ACCEPT值为16

interesOps():获得此SelectionKey的interes集合.
interestOps(int ops):将此SelectionKey的interst设置为指定值(前面是在注册时设置)

  1. Channel()
    返回此选择键所关联的通道.即使此key已经被取消,仍然会返回
    在读写操作中,一般都是用这个方法通过SelectionKey获得Channel
  2. Selector()
    返回此选择键所关联的Selector
  3. 附加对象:attachment()
    在注册时可以选择设置一个缓冲区
socketChannel.register(selector, SelectionKey.OP_READ, ByteBuffer.allocate(1024));

这样可以减少缓冲区的开销,然后通过attachment方法获得该缓冲区

ByteBuffer byteBuffer = (ByteBuffer) key.attachment();

当然可以在SelectionKey上也可以设置
attach(Object ob):将给定的对象作为附件添加到此key上

  1. ready集合:判断事件
    isAcceptable():判断是否是连接事件(服务器通道使用)
    isValid():判断该key是否有效
    isReadable(): 检测此键是否为"read"事件

一个聊天系统案例

服务器

package com.company.Selector.Selector1;



import java.net.InetSocketAddress;
import java.nio.ByteBuffer;
import java.nio.channels.*;
import java.util.Iterator;
import java.util.Set;

public class NIOServer {
    public static void main(String[] args) throws Exception {
        //打开ServerSocketChannel等待连接
        ServerSocketChannel serverSocketChannel = ServerSocketChannel.open();
        //设置非阻塞
        serverSocketChannel.configureBlocking(false);
        //绑定端口号6666
        serverSocketChannel.socket().bind(new InetSocketAddress(6666));
        //打开选择器
        Selector selector = Selector.open();
        //注册,监听连接事件
        serverSocketChannel.register(selector, SelectionKey.OP_ACCEPT);

        while (true){
            //循环监听
            //select是阻塞方法
/*            if ( selector.select(5000)==0){
                System.out.println("没有客户端连接");
                continue;
            }*/
            selector.select();
            //获得存在事件的SelectionKey
            Set<SelectionKey> selectionKeys = selector.selectedKeys();
            //迭代Set集合
            Iterator<SelectionKey> keyIterator = selectionKeys.iterator();
            while (keyIterator.hasNext()){

                SelectionKey key = keyIterator.next();
                if (key.isAcceptable()){
                    System.out.println("得到客户端连接");
                    //accept得到SocketChannel
                    SocketChannel socketChannel = serverSocketChannel.accept();
                    //设置非阻塞
                    socketChannel.configureBlocking(false);
                    //注册,socketChannel监听读事件
                    //关联一个bytebuffer
                    //socketChannel.register(selector, SelectionKey.OP_READ, ByteBuffer.allocate(1024));
                    socketChannel.register(selector, SelectionKey.OP_READ);
                }
                if (key.isReadable()){
                    //SelectionKey的channel方法得到SocketChannel
                    SocketChannel socketChannel = (SocketChannel) key.channel();
                    //获得buffer
                    //ByteBuffer byteBuffer = (ByteBuffer) key.attachment();
                    ByteBuffer byteBuffer = ByteBuffer.allocate(1024);
                    //从通道读出数据
                    socketChannel.read(byteBuffer);
                    byteBuffer.clear();

                    System.out.println(new String(byteBuffer.array()));
                }
                keyIterator.remove();
            }

        }
    }

}

客户端

package com.company.Selector.Selector1;

import java.net.InetSocketAddress;
import java.nio.ByteBuffer;
import java.nio.channels.SocketChannel;
import java.util.Scanner;

public class NIOClient {
    public static void main(String[] args) throws Exception{
        //打开通道
        SocketChannel socketChannel = SocketChannel.open();
        //设置为非阻塞
        socketChannel.configureBlocking(false);
        //绑定
        InetSocketAddress inetSocketAddress = new InetSocketAddress("127.0.0.1", 6666);
        if(!socketChannel.connect(inetSocketAddress)){
            while (!socketChannel.finishConnect()){
                System.out.println("连接失败");
            }
        }
        while (true) {
            Scanner scanner = new Scanner(System.in);
            String str = scanner.nextLine();
            System.out.println(str);

            ByteBuffer byteBuffer = ByteBuffer.wrap(str.getBytes());

            socketChannel.write(byteBuffer);

        }
    }
}

总结

  1. Selector是一个选择器,用于处理非阻塞的通道,即一个线程设置一个选择器,可以处理多个通道
  2. Selector用于处理网络通道,常用的有ServerSocketChannel、SocketChannel,需要这些通道注册在Selector上才可以管理
  3. Selector有许多方法,其中select()方法监控所有通道,当有通道发生事件时,会将该通道的SelectionKey对象保存到集合
  4. SelectionKey是对象是一个标识对象,里面保存着许多属性
上一篇:NIO 与 BIO 的对比


下一篇:Java NIO简介