SpringBoot Tomcat(2) nio的应用(上)——选择器与通道

2,231 阅读2分钟

NIO主要有三大核心部分:Buffer(缓冲区),Channel(通道), Selector(选择器),本篇主要介绍后面两个。

nio常见方法

  • ServerSocketChannelImpl是ServerSocketChannel的子类,ServerSocketChannel是AbstractSelectableChannel的子类,详细如下
  • SelectableChannel两个参数的register()方法默认调用AbstractSelectableChannel三个参数的register()方法,第一个参数是注册进Selector,第二个参数是感兴趣的事件,第三个参数附带一个对象,返回SelectionKey
    • 一个Selector可以被多个ServerSocketChannel注册
    • 一个SelectableChannel可以注进不同的Selector
    • 一个channel与一个selector对应一个selectionKey,所以可以用channel.keyfor(selector)方法返回唯一的SelectionKey
  • ServerSocketChannelaccept()方法监听新加来的连接,若是非阻塞模式立即返回,阻塞模式下直到有连接时返回SocketChannel
  • SelectionKeychannel()方法返回ServerSocketChannel或SocketChannel
  • SelectionKeyselector()方法返回Selector
  • SelectionKeyattachment()方法返回附带的对象
  • SelectionKeyinterestOps()方法,注册时是什么,此处就返回什么,当然可以用isReadableisWritableisConnectableisAcceptable来看看它具体对那些事件感兴趣
  • SelectionKeyreadyOps()方法来获取相关channel已经就绪的事件,它是interest集合的子集
  • SocketChannelImpl是SocketChannel的子类,SocketChannel是AbstractSelectableChannel的子类
  • Selectorselect()方法返回已就绪的SelectionKey数量,selectNow()是立即返回,如果不设时间,将会阻塞直到有SelectionKey就绪,可以用selectedKeys()返回已就绪的SelectionKey的Set集合
  • Selectorwakeup()方法可以唤醒一次因为select()带来的阻塞,如果正在阻塞则立即解除,否则解除下一次的阻塞

启动阶段

NioEndpoint->bind():
    initServerSocket();
    ......
    // 开启NioBlockingSelector.BlockPoller线程
    selectorPool.open();
NioEndpoint->initServerSocket():
    // 将会返回一个ServerSocketChannelImpl类
    serverSock = ServerSocketChannel.open();
    socketProperties.setProperties(serverSock.socket());
    InetSocketAddress addr = new InetSocketAddress(getAddress(), getPortWithOffset());
    // 这个channel绑定的端口,可以通过server.port设置,默认8080端口
    serverSock.socket().bind(addr,getAcceptCount());
    // 设置为阻塞模式
    serverSock.configureBlocking(true); 

NioEndpoint内部封装NioSelectorPoolNioSelectorPool内部封装NioBlockingSelectorNioBlockingSelector内部有BlockPoller线程,该类以及线程的作用在这里

SelectableChannel的注册

Poller在拉取事件时,调用了events方法,如果有事件的话,会将channel注入selector,所以才有后面run()方法中的select()大于0、遍历selectedKeys()不为空、以及processKey()isReadable()返回为true的情况

NioEndpoint->Poller->events():
    PollerEvent pe = null;
    // 这里的pe是前面调用register()方法添加进来的
    for (int i = 0, size = events.size(); i < size && (pe = events.poll()) != null; i++ ) {
        result = true;
        try {
            pe.run();
            // 调用reset方法之后,interestOps变为0
            pe.reset();
    ......
NioEndpoint->PollerEvent->run(): 
    // 前面register方法中,将interestOps值设为256,走if方法
    if (interestOps == OP_REGISTER) {
        // 这里调用完register方法之后,selector的keys数量增加1
        socket.getIOChannel().register(
            socket.getPoller().getSelector(), SelectionKey.OP_READ, socketWrapper);
    } else {
        final SelectionKey key = socket.getIOChannel().keyFor(socket.getPoller().getSelector());
        final NioSocketWrapper socketWrapper = (NioSocketWrapper) key.attachment();
        if (socketWrapper != null) {
            // 如果key不为空的话,追加当前设置的感兴趣的事件(此处仍为读)
            int ops = key.interestOps() | interestOps;
            socketWrapper.interestOps(ops);
            key.interestOps(ops);
        }