NIO之SocketChannel 和 Selector

1,802 阅读3分钟

概述

在文件IO的时候我们看到了,读取一个文件的时候,可以创建出一个 Channel 然后往 Channel里面读取 ByteBuffer ByteBuffer 分为直接内存和堆内存,涉及到拷贝次数的问题.但是实际测试中,BIO和NIO的性能差别并不是很大,那么JDK为什么还要费尽周折搞NIO呢? 原因就是网络IO层面.在IO模型推导里面讲述了 非阻塞IO的优势,这里就来细节的看JAVA是如何实现的。

Channel

还是先从Channel入手,网络的Channel分为服务端Channel ServerSocketChannel 和 客户端 SocketChannel

ServerSocketChannel

image.png 首先服务端的Channel实现了 NetworkChannel, 可以绑定一个端口

public interface NetworkChannel
    extends Channel
{
    NetworkChannel bind(SocketAddress local) throws IOException;
}

再来看另一边的实现 SelectableChannel

public abstract class SelectableChannel
    extends AbstractInterruptibleChannel
    implements Channel
{

    # 注册一个 Selector
    public abstract SelectionKey register(Selector sel, int ops, Object att)
        throws ClosedChannelException;
   
    # 设置阻塞模式
    public abstract SelectableChannel configureBlocking(boolean block)
        throws IOException;
}

什么是 Selector先不谈,看过 IO模型的应该知道就是一个多路复用器,这里先略过,还有一个是设置这个 channel的阻塞模式。接下里看看 ServerSocketChannel

public abstract class ServerSocketChannel
    extends AbstractSelectableChannel
    implements NetworkChannel
{

    # 创建 ServerSocketChannel的实现类
    public static ServerSocketChannel open() throws IOException {
        return SelectorProvider.provider().openServerSocketChannel();
    }
    # 绑定端口
    public abstract ServerSocketChannel bind(SocketAddress local, int backlog)
        throws IOException;
    # 接受客户端
    public abstract SocketChannel accept() throws IOException;
}

服务端的 ServerSocketChannel 总结起来就是有一个

  1. 绑定端口 bind-> NetworkChannel
  2. 注册多路复用器和设置阻塞模型 register,configureBlocking -> SelectableChannel
  3. 接受客户端的连接 accept-> ServerSocketChannel 并不像文件IO的Channel通过ByteBuffer读写数据.

SocketChannel

接下来看看客户端的Channel

image.png 其他和服务端的一样,多出来了一些 ReadableByteChannel, WritableByteChannel 就知道肯定跟ByteBuffer读写相关了。

public interface ReadableByteChannel extends Channel {
    public int read(ByteBuffer dst) throws IOException;
}
public interface WritableByteChannel extends Channel
{
    public int write(ByteBuffer src) throws IOException;
}

看看 SocketChannel 源码

public abstract class SocketChannel
    extends AbstractSelectableChannel
    implements ByteChannel, ScatteringByteChannel, GatheringByteChannel, NetworkChannel
{
    # 创建一个socketchannel
    public static SocketChannel open() throws IOException {
        return SelectorProvider.provider().openSocketChannel();
    }
    # 连接远程服务器
    public abstract boolean connect(SocketAddress remote) throws IOException;

    # 读写
    public abstract int write(ByteBuffer src) throws IOException;

    public abstract int read(ByteBuffer dst) throws IOException;
}

客户端的SocketChannel总结起来就是

  1. 连接服务端 connect -> SocketChannel
  2. 读写 bytebuffer -> ReadableByteChannel,WritableByteChannel

Selector

看完了大体的客户端和服务单的 SocketChannel,我们来看看 服务端需要注册的 Selector是一个什么东西.

public abstract class Selector implements Closeable {

    public static Selector open() throws IOException {
        return SelectorProvider.provider().openSelector();
    }
    
    # 返回有IO事件的 selectionKey
    public abstract Set<SelectionKey> selectedKeys();
    # 阻塞的等待一组 SelectionKey,直到其中有一个或多个有IO事件
    public abstract int select() throws IOException;

}

沒有继承什么类,就是一个等待返回一组有数据的对象,其中又涉及到其他的类 SelectionKey. SelectionKey 的设计思想就是把 Channel和Selector绑定在一起

public abstract class SelectionKey {
    
    public abstract SelectableChannel channel();

    public abstract Selector selector();

    public abstract int interestOps();
    
    private volatile Object attachment = null;

    public static final int OP_READ = 1 << 0;

    public static final int OP_WRITE = 1 << 2;

    public static final int OP_CONNECT = 1 << 3;

    public static final int OP_ACCEPT = 1 << 4;
}

register流程

register的本质就是创建一个 SelectionKey,然后 selector和Channel各自都保存一份. image.png 回顾一下register是 SelectableChannel 的一个抽象方法.当一个 Channel调用register后

public final SelectionKey register(Selector sel, int ops,
                                   Object att)
    throws ClosedChannelException
{
    synchronized (regLock) {
        if (!isOpen())
            throw new ClosedChannelException();
        if ((ops & ~validOps()) != 0)
            throw new IllegalArgumentException();
        if (blocking)
            throw new IllegalBlockingModeException();
        # 判断有没有注册过
        SelectionKey k = findKey(sel);
        if (k != null) {
            k.interestOps(ops);
            k.attach(att);
        }
        if (k == null) {
            // New registration
            synchronized (keyLock) {
                if (!isOpen())
                    throw new ClosedChannelException();
                    # 如果没有SelectionKey 调用 Selector的register,注册并且返回一个key
                k = ((AbstractSelector)sel).register(this, ops, att);
                # 自己在添加一个selectionKey
                addKey(k);
            }
        }
        return k;
    }
}

我们来看看selector中的register的代码

protected final SelectionKey register(AbstractSelectableChannel var1, int var2, Object var3) {
    if (!(var1 instanceof SelChImpl)) {
        throw new IllegalSelectorException();
    } else {
           # 创建一个 selectionKey,绑定channel和自身
        SelectionKeyImpl var4 = new SelectionKeyImpl((SelChImpl)var1, this);
        var4.attach(var3);
        # 调用系统调用相关,根据操作系统不同而不同
        synchronized(this.publicKeys) {
            this.implRegister(var4);
        }

        var4.interestOps(var2);
        return var4;
    }
}