Java-第十七部分-NIO和Netty-Selector

284 阅读3分钟

NIO和Netty全文

Selector

  • Netty的IO线程NioEventLoop聚合了Selector选择器(多路复用器)
  • 选择器,可以实现单线程管理多个通道/网络连接,用于检查多个通道是否有事件发生, image.png
  • 用更少的线程管理通道,避免线程上下文切换带来的开销,将非阻塞IO的空闲时间用于在其他通道上执行IO操作
  • SelectableChannel,可选择通道,只用继承了SelectableChannel才可以被复用

并不是所有通道都可以被selector复用,FileChannel不可以

  • 一个通道可以被注册到多个选择器上,但是一个通道对于一个选择器只能被注册一次
  • selector关注的是通道做好注册行为准备之后,进行行为之前的状态,采用主动轮询的方式,监听各个通道所处的状态,监听到感兴趣的就绪状态后,将该通道放入选择键集合中
  • Channel必须在非阻塞模式下
  • 四个模式的触发情况
  1. accept,接收到连接,适用ServerSocketChannel
  2. read,该通道被写入数据或信号
  3. write,该通道可写
  4. connect,进行连接操作,可以获取连接的信息

方法示例

//创建
Selector selector = Selector.open();
//创建通道
ServerSocketChannel ssc = ServerSocketChannel.open();
//非阻塞模式
ssc.configureBlocking(false);
//绑定连接
ssc.bind(new InetSocketAddress(8888));
//注册通道,关注的是接收的行为
//获取支持的操作,16进制的表示
int i = ssc.validOps();
//16,只有接受操作
System.out.println(i);
ssc.register(selector, SelectionKey.OP_ACCEPT);
//查询已经就绪的通道操作
Set<SelectionKey> sk = selector.selectedKeys();
Iterator<SelectionKey> it = sk.iterator();
while (it.hasNext()) {
    SelectionKey key = it.next();
    //判断就绪状态
    if (key.isAcceptable()) {
    } else if (key.isConnectable()) {
    } else if (key.isValid()) {
    } else if (key.isReadable()) {
    } else if (key.isWritable()) {
    }
}
it.remove();

select

  • select(),阻塞到至少有一个通道就绪
sel.select()
  • selectNow() 只要有通道就绪立刻返回,没有就直接跳过
sel.selectNow()
  • select(long timeout),最长阻塞时间为timeout毫秒
sel.select(10)

客户端

//获取通道,绑定服务器的主机和端口号
SocketChannel sc = SocketChannel.open(new InetSocketAddress("localhost", 9999));
//切换到非阻塞模式
sc.configureBlocking(false);
System.out.println(sc.validOps());
//创建buffer并写入
ByteBuffer bb = ByteBuffer.allocate(1024);
Scanner scanner = new Scanner(System.in);
while (scanner.hasNext()) {
    String str = scanner.next();
    bb.put((new Date().toString() + " - " + str).getBytes());
    //模式切换,还原指针
    bb.flip();
    sc.write(bb);
    bb.clear();
}
//如果客户端使用close关闭,服务端会持续收到这个关闭信号,导致一直存在sk
//当关闭时,读取sc为-1
sc.close();

服务端

ServerSocketChannel ssc = ServerSocketChannel.open();
ssc.configureBlocking(false);
ByteBuffer bb = ByteBuffer.allocate(1024);
//绑定端口号
ssc.bind(new InetSocketAddress(9999));
//注册通道
Selector sel = Selector.open();
ssc.register(sel, SelectionKey.OP_ACCEPT);
//如果有就绪状态,就是>=1
while (sel.select() > 0) {
    Set<SelectionKey> sk = sel.selectedKeys();
    Iterator<SelectionKey> it = sk.iterator();
    while (it.hasNext()) {
        SelectionKey key = it.next();
        //是否有效
        if (key.isValid() && key.isAcceptable()) {
            //获取连接
            SocketChannel sc = ssc.accept();
            sc.configureBlocking(false);
            //将这个链接注册到sel上,进行轮询监听
            sc.register(sel, SelectionKey.OP_READ);
        } else if (key.isValid() && key.isReadable()) {
            SocketChannel sc = (SocketChannel) key.channel();
            int len = 0;
            //从SocketChannel读取到会是0
            while ((len = sc.read(bb)) > 0) {
                bb.flip();
                System.out.println("receive - " + new String(bb.array(), 0, len));
                bb.clear();
            }
        }
        //做完当前key的处理,并清除
        it.remove();
    }
}

其他api

  • 注册时绑定buffer
//为这个通道绑定一个buffer
sc.register(sel, SelectionKey.OP_READ, ByteBuffer.allocate(1024));
  • 获取绑定的buffer
ByteBuffer bb = (ByteBuffer) sk.attachment();
  • 改变监听事件
sk.interestOps(SelectionKey.OP_WRITE);

Selector细节

  • mac中Selector的实际类型为KQueueSelectorImpl
  • 所有注册的通道 sel.keys()

NIO编程步骤

  • 创建ServerCocketChannel通道,绑定监听端口
  • 设置通道为非阻塞模式
  • 创建Selector选择器
  • Channel注册到选择器上,监听连接事件
  • 调用Selectorselect方法,监测通道的就绪状态
  • 调用selectedKeys获取到选择键集合
  • 遍历就绪channel集合,获取其事件类型,实现具体业务
  • 删除处理完的事件