Skip to content

Latest commit

 

History

History
205 lines (149 loc) · 6.03 KB

34.彻底解密:SelectionKey (选择键) 核心原理.md

File metadata and controls

205 lines (149 loc) · 6.03 KB

34 彻底解密:SelectionKey (选择键) 核心原理

SelectionKey 和 IO 事件紧密相关。

一个SelectionKey表示一个Channel对象和一个selector之间注册绑定的关系。

通常意义上来讲,一个SelectionKey对象代表着一个channel和它注册的selector之间的关系。

SelectionKey#channel() : 返回与该键相关的SelectableChannel对象。

SelectionKey#selector() : 返回相关的selector对象。

此外,SelectionKey还有两个比较重要的属性,两个以整数形式进行编码的比特掩码:

interestOps

代表channel所感兴趣的事件的集合。interest集合是使用注册通道时给定的值初始化的,可以通过调用键对象的interestOps(int ops)方法进行修改。

同时可以调用键对象的interestOps()方法获取所有感兴趣的事件。当相关的selector上的select操作正在进行时改变键的interest集合,不会影响那个正在进行的select的操作,

所有更改将会在下一个select中作用出来。

readyOps

代表,interest集合在上次调用了select之后,所有准备就绪的事件。他是interestOps的自己,注册通道时,初始值为0,只有在选择器选择期间可能被更新。

可以调用键对象的readyOps()方法获取所有准备就绪的事件。需要注意的是,ready集合返回的就绪状态仅仅是一个提示,不是保证。底层的通道在任何时候都可以改变,

其他的线程可能在通道上执行,从而影响它的就绪状态。

SelectionKey的继承关系

AbstractSelectionKey

AbstractSelectionKey是一个抽象类,相对功能比较简单,就是判断当前key是否有效。

public abstract class AbstractSelectionKey
    extends SelectionKey
{

    /**
     * Initializes a new instance of this class.
     */
    protected AbstractSelectionKey() { }

    private volatile boolean valid = true;

    public final boolean isValid() {
        return valid;
    }

    void invalidate() {                                 // package-private
        valid = false;
    }

    // 注意这个方法的含义。
    /**
     * Cancels this key.
     *
     * <p> If this key has not yet been cancelled then it is added to its
     * selector's cancelled-key set while synchronized on that set.  </p>
     */
    public final void cancel() {
        // Synchronizing "this" to prevent this key from getting canceled
        // multiple times by different threads, which might cause race
        // condition between selector's select() and channel's close().
        synchronized (this) {
            if (valid) {
                valid = false;
                ((AbstractSelector)selector()).cancel(this);
            }
        }
    }
}

向选择器注册事件

serverChannel.register(selctor, SelectionKey:key)

也可以分开注册:

sk = serverChannel.register(selctor, 0)
sk.interestOps(SelectionKey:key)

然后通过SelectionKey实例就可以获取内部的selector和channel了:

 public void run(){   
    server.register(selector, SelectionKey.OP_ACCEPT);
    
    SelectionKey sk = server.register(selector, 0);
    sk.interestOps(SelectionKey.OP_ACCEPT);

    SelectableChannel channel = sk.channel();
    Selector selector = sk.selector();
}

interestOps 和 readyOps

前面提到了SelectionKey的两个重要属性 interestOps 和 readyOps ,它的具体实现是在 SelectionKeyImpl 中 ,以下是源码:

public class SelectionKeyImpl extends AbstractSelectionKey {
    final SelChImpl channel;
    public final SelectorImpl selector;
    private int index;
    private volatile int interestOps;
    private int readyOps;
    ...
}

SelectionKey可以通过interestOps()方法返回所感兴趣的事件,然后再和不同事件的值进行与运算计算所对应的类型是否就绪。

SelectionKey通过readyOps获取就绪事件集合,

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

public final boolean isAcceptable() {
    return (readyOps() & OP_ACCEPT) != 0;
}

ready事件是通道实际发生的事件

就绪事件的处理

Set<SelectionKey> selectionKeys = selector.selectedKeys();
Iterator<SelectionKey> iter = selectionKeys.iterator();
while (iter.hasNext()) {
    SelectionKey key = iter.next();
    if (key.isAcceptable()) {
        acceptHandle(key);
    } else if (key.isReadable()) {
        readHandle(key);
    }
}

首先循环判断selectionKey中是否有就绪事件,如果有就绪事件,则判断就绪是是什么类型的事件,然后进行处理。

public void acceptHandle(SelectionKey key) throws IOException {
    ServerSocketChannel ssc = (ServerSocketChannel) key.channel();
    SocketChannel client = ssc.accept();
    client.configureBlocking(false);
    ByteBuffer byteBuffer = ByteBuffer.allocateDirect(1024);
    client.register(selector, SelectionKey.OP_READ, byteBuffer);
    System.err.println("client arrived " + client.getRemoteAddress());
}
public void readHandle(SelectionKey key) throws IOException {
    SocketChannel client = (SocketChannel) key.channel();
    ByteBuffer buffer = (ByteBuffer) key.attachment();
    buffer.clear();
    int read = 0;
    while (true) {
        read = client.read(buffer);
        if (read > 0) {
            // 服务端读到的数据,再写一遍给到客户端
            buffer.flip();
            while (buffer.hasRemaining()) {
                client.write(buffer);
            }
            buffer.clear();
        } else if (read == 0) {
            break;
        } else {
            // client 发生错误 或者断开 read == -1
            // 导致空转 最终CPU达到100%
            client.close();
            break;
        }
    }
}