先回顾一下创建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做了哪些事情:
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的实现:
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(selector,SelectionKey.OP_ACCEPT);
把Selector和Channel绑定在一起,也就是把新建ServerSocketChannel时创建的FD与Selector绑定在一起。
到此,Server端已启动完成,主要创建了以下对象。
- KQueueSelector:为单例对象,实际上是调用操作系统的API。
- KQueueSelectorImpl中包含如下内容。
a. IOUtil.makePipe:其实就是创建两个FD,一个是read端的,一个是write端的)。
b. initInterrupt(this.fd0, this.fd1):保存Selector上注册的FD,包括pipe的write端FD和ServerSocketChannel所用的FD。
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框架》