谈谈java selector的机制(二)

2014-11-24 07:40:06 · 作者: · 浏览: 2
locking
while ((count = socketChannel.read(buffer)) > 0) {
buffer.flip(); // Make buffer readable
// Send the data; don't assume it goes all at once
while (buffer.hasRemaining()) {
socketChannel.write(buffer);
}
// WARNING: the above loop is evil. Because
// it's writing back to the same nonblocking
// channel it read the data from, this code can
// potentially spin in a busy loop. In real life
// you'd do something more useful than this.
buffer.clear(); // Empty buffer

}
if (count < 0) {
// Close channel on EOF, invalidates the key
socketChannel.close();
}
}

private void sayHello(SocketChannel channel) throws Exception {
buffer.clear();
buffer.put("Hi there!\r\n".getBytes());
buffer.flip();
channel.write(buffer);
}
}


这段代码摘自JAVA NIO这本书,代码做了很简单的几件事
起一个server socket监听子1234端口
起一个selector
将server socket注册到epoll,感兴趣的事件为SelectionKey.OP_ACCEPT,即来了新的连接
开始一个轮询过程,不断的通过selector来探测到底有没有新的网络事件
如果有监听事件,那么取出连接socket,然后给将这个连接socket注册到epoll,感兴趣的事件为 SelectionKey.OP_READ
如果有读事件,那么就把这个读的内容写回 www.2cto.com
注意出于演示的目的,没有注册写事件,这样的话会导致一个问题就如上面注释中提到的邪恶代码
3) selector的机制

selector最关键的上个点就是初始化/注册/以及select过程,以上面的代码为例,分别说明这3个关键点

3.1)selector的初始化
Selector selector = Selector.open();
根据操作系统实例化不同Selector(通常见sun.nio.ch.DefaultSelectorProvider.create())
常见的Linux且 kernels >= 2.6,会使用sun.nio.ch.EPollSelectorImpl
实例化EPollSelectorImpl
实例化EPollArrayWrapper
调用epollCreate产生epoll FD

实例化AllocatedNativeObject,得到上文提到的pollArray
3.2)注册
ServerSocketChannel.register
如果该通道曾经注册过那么
SelectionKeyImpl.interestOps[SelectionKey.OP_ACCEPT] -->
SelectionKeyImpl.nioInterestOps[SelectionKey.OP_ACCEPT]-->
ServerSocketChannelImpl.translateAndSetInterestOps[SelectionKey.OP_ACCEPT]--> :将SelectionKey.OP_ACCEPT转化为PollArrayWrapper.POLLIN
EPollSelectorImpl.putEventOps[PollArrayWrapper.POLLIN]-->
EPollArrayWrapper.setInterest[fd,PollArrayWrapper.POLLIN] :加入updateList
如果没有注册过
EPollSelectorImpl.register-->:仅仅是将key所对应的fd加入epoll
EPollSelectorImpl.implRegister-->
EPollArrayWrapper.add:加入updateList
将该key加入到keys集合中
SelectionKeyImpl.interestOps:调用栈见上面,功能和上面一样就是更新fd感兴趣的事件
抛开上面的代码细节,注册会
往EPollArrayWrapper的updateList添加记录,updateList会在select的时候使用
如果没有注册过,会将该key加入到keys集合中即所有注册过的key都会在keys中,除非以后取消掉了
应用这边感兴趣的事件为
SelectionKey.OP_READ
SelectionKey.OP_WRITE
SelectionKey.OP_CONNECT
SelectionKey.OP_ACCEPT
底层的epoll接受的事件
PollArrayWrapper.POLLIN
PollArrayWrapper.POLLOUT
PollArrayWrapper.POLLERR
PollArrayWrapper.POLLHUP
PollArrayWrapper.POLLNVAL
PollArrayWrapper.POLLREMOVE
由于存在上面提到的两种事件类型:应用级别和系统(epoll)级别,所以需要转换一下,见SocketChannelImpl及ServerSocketChannelImpl的translateAndSetInterestOps和translateReadyOps方法,前者是将应用-->系统,后者是系统->应用
注意到上面的注册实际上分两步
现将key转换成一个内部数据结构EPollArrayWrapper$Updator添加到updateList,此时事件为空
再更新EPollArrayWrapper$Updator的事件为感兴趣的事件
为什么要分两步??

3.3)selector.select()
EPollSelectorImpl.doSelect
注销cancelledKeys【已取消的键的集合】中的key
EPollArrayWrapper.poll
EPollArrayWrapper.updateRegistrations:遍历上面的updateList,调用epollCtl真正到向epoll fd注册
调用epollWait等待事件发生,可能会阻塞,返回更新的事件
此时telnet 127.0.0.1 1234发