NIO源码初探

文章目录

  • 一. Selector.open();
  • 二. ServerSocketChannel.open()
  • 三. serverChannel.register()
  • 四. selector.select()

先回顾一下创建server与selector的过程

 * 相当于开启一个server端,并绑定为一个端口,用于客户端向此发起请求
 * 其中这个server端,是非阻塞的,selector帮助server端监听事件,并当事件到来时处理事件
public NIOServerDemo(int port) {

    try {
        this.port = port;
        selector = Selector.open();
        //创建 可选择通道并配置为非阻塞式
        //通过open方法创建 服务器套接字 通道
        ServerSocketChannel server = ServerSocketChannel.open();
		server.configureBlocking(false);
		
        server.register(selector, SelectionKey.OP_ACCEPT);  //注册感兴趣的事件
        server.bind(new InetSocketAddress(this.port)); //绑定指定端口
    } catch (Exception e) {
        e.printStackTrace();
    }
}

 
先从Selector的open方法开始看,我们看java.nio.channels.Selector类的源码。看看open做了哪些事情:

一. Selector.open();

open来创建Selector

selector = Selector.open();


public static Selector open() throws IOException {
    return SelectorProvider.provider().openSelector();
}

 
SelectorProvider.provider() 提供默认的selector

public static SelectorProvider provider() {
    synchronized (lock) {
    //保证了整个Server程序中只有一个KQueueSelectorProvider对象
        if (provider != null)
            return provider;
        return AccessController.doPrivileged(
            new PrivilegedAction<SelectorProvider>() {
                public SelectorProvider run() {
                        if (loadProviderFromProperty())
                            return provider;
                        if (loadProviderAsService())
                            return provider;
   //会根据操作系统来返回不同的实现类,mac平台返回KQueueSelectorProvider
                        provider = sun.nio.ch.DefaultSelectorProvider.create();
                        return provider;
                    }
                });
    }
}

DefaultSelectorProvider.create默认会创建KQueueSelectorProvider(mac环境下)

public static SelectorProvider create() {
    return new KQueueSelectorProvider();
}

看看 new KQueueSelectorProvider(); 做了哪些事情

KQueueSelectorImpl(SelectorProvider var1) {
    super(var1);
    //关键方法
    long var2 = IOUtil.makePipe(false);
    //移位处理。
    this.fd0 = (int)(var2 >>> 32);
    this.fd1 = (int)var2;

    try {
        this.kqueueWrapper = new KQueueArrayWrapper();
        this.kqueueWrapper.initInterrupt(this.fd0, this.fd1);
        this.fdMap = new HashMap();
        this.totalChannels = 1;
    } catch (Throwable var8) {
        try {
            FileDispatcherImpl.closeIntFD(this.fd0);
        } catch (IOException var7) {
            var8.addSuppressed(var7);
        }

        try {
            FileDispatcherImpl.closeIntFD(this.fd1);
        } catch (IOException var6) {
            var8.addSuppressed(var6);
        }

        throw var8;
    }
}

IOUtil.makePipe(true)是一个本地方法。它返回两个文件描述符:高位存放的是通道read端的文件描述符,低32位存放的是write端的文件描述符。

 long var2 = IOUtil.makePipe(false);
 this.fd0 = (int)(var2 >>> 32);
 this.fd1 = (int)var2;

 
把返回的pipe的write端的FD、read端的FD放在this.kqueueWrapper中(后面会发现这么做是为了实现Selector的wakeup())。

this.kqueueWrapper = new KQueueArrayWrapper();
this.kqueueWrapper.initInterrupt(this.fd0, this.fd1);

 

二. ServerSocketChannel.open()

ServerSocketChannel的实现:

public static ServerSocketChannel open() throws IOException {
    return SelectorProvider.provider().openServerSocketChannel();
}


public ServerSocketChannel openServerSocketChannel() throws IOException {
    return new ServerSocketChannelImpl(this);
}


ServerSocketChannelImpl(SelectorProvider var1) throws IOException {
    super(var1);
    this.fd = Net.serverSocket(true);
    this.fdVal = IOUtil.fdVal(this.fd);
    this.state = 0;
}

super(var1); 中var1代表SelectorProvider,而默认的SelectorProvider是KQueueSelectorProvider。 ing

 

三. serverChannel.register()

接着通过serverChannel.register(selector,SelectionKey.OP_ACCEPT);把Selector和Channel绑定在一起,也就是把新建ServerSocketChannel时创建的FD与Selector绑定在一起

 
到此,Server端已启动完成,主要创建了以下对象。

  1. KQueueSelector:为单例对象,实际上是调用操作系统的API。
  2. KQueueSelectorImpl中包含如下内容。
    a. IOUtil.makePipe:其实就是创建两个FD,一个是read端的,一个是write端的)。
    b. initInterrupt(this.fd0, this.fd1):保存Selector上注册的FD,包括pipe的write端FD和ServerSocketChannel所用的FD。

 

四. selector.select()

selector.select()主要调用了KQueueSelectorImpl的doSelect

protected int doSelect(long var1) throws IOException {
    boolean var3 = false;
    if (this.closed) {
        throw new ClosedSelectorException();
    } else {
        this.processDeregisterQueue();

        int var7;
        try {
            this.begin();
            var7 = this.kqueueWrapper.poll(var1);
        } finally {
            this.end();
        }

        this.processDeregisterQueue();
        return this.updateSelectedKeys(var7);
    }
}

this.kqueueWrapper.poll()是核心,也就是轮询kqueueWrapper中保存的FD;具体实现是调用native方法kevent0()。

 var7 = this.kqueueWrapper.poll(var1);
 
 
 int poll(long var1) {
    this.updateRegistrations();
    int var3 = this.kevent0(this.kq, this.keventArrayAddress, 128, var1);
    return var3;
}

private native int kevent0(int var1, long var2, int var4, long var5);

顺便看看findNative的逻辑

// Invoked in the VM class linking code.
static long findNative(ClassLoader loader, String name) {
    Vector<NativeLibrary> libs =
        loader != null ? loader.nativeLibraries : systemNativeLibraries;
    synchronized (libs) {
        int size = libs.size();
        for (int i = 0; i < size; i++) {
            NativeLibrary lib = libs.elementAt(i);
            long entry = lib.find(name);
            if (entry != 0)
                return entry;
        }
    }
    return 0;
}

kevent0.()会监听kqueueWrapper中的FD有没有数据进出,这会造成I/O阻塞,直到有数据读写事件发生。

比如,由于kqueueWrapper中保存的也有ServerSocketChannel的FD,所以只要ClientSocket发一份数据到ServerSocket,那么kevent0()就会返回;
又由于kqueueWrapper中保存的也有pipe的write端的FD,所以只要pipe的write端向FD发一份数据,也会造成poll0()返回;
 
如果这两种情况都没有发生,那么kevent0()就会一直阻塞,也就是selector.select()会一直阻塞;
如果有任何一种情况发生,那么selector.select()就会返回,所以在OperationServer的run()里要用while(true),这样可以保证在Selector接收数据并处理完后继续监听poll()。

 
 
KQueueSelectorImpl.wakeup()

public Selector wakeup() {
    synchronized(this.interruptLock) {
        if (!this.interruptTriggered) {
            this.kqueueWrapper.interrupt();
            this.interruptTriggered = true;
        }

        return this;
    }
}

wakeup()通过pipe的write端send(scoutFd,&byte,1,0)发送一个字节1来唤醒poll()的,所以在需要的时候(when ing )就可以调用selector.wakeup()来唤醒Selector。

 
 

参考:
《Netty4核心原理与手写RPC框架》

你可能感兴趣的:(网络编程,nio,java,服务器)