深入理解NIO(三)—— NIO原理及部分源码的解析
深入理解NIO(三)—— NIO原理及部分源码的解析
欢迎回到淦™的源码看爆系列
在看完前面两个系列之后,相信大家对NIO也有了一定的理解,接下来我们就来深入源码去解读它,我这里的是OpenJDK-8u60版本,建议大家也下一份放ide里和我一起看会比较好理解。(这里主要介绍Selector,Buffer第一篇有提到一点,Channel也不过是些Buffer的操作方法而已,这里就不提及了,大家感兴趣可以自己去看)
老哥行行好,转载和我说一声好吗,我不介意转载的,但是请把原文链接贴大点好吗
open()
// 1. 创建Selector
Selector selector = Selector.open();
首先我们来分析open方法:
// Selector
public static Selector open() throws IOException {
// 这里的静态方法provider会使用DefaultSelectorProvider.create();方法根据系统选择一个SelectorProvider
// windows平台的话是WindowsSelectorProvider,
// Linux平台是一个EPollSelectorProvider,这里主要分析Linux平台下的
// 之后openSelector方法(一会看下面)会返回一个EPollSelectorImpl作为Selector的实现,我们一般提及的Selector就是它了
return SelectorProvider.provider().openSelector();
} // EPollSelectorProvider
public AbstractSelector openSelector() throws IOException {
return new EPollSelectorImpl(this);
}
之后是EPollSelectorImpl的构造方法:
EPollSelectorImpl(SelectorProvider sp) throws IOException {
super(sp);
long pipeFds = IOUtil.makePipe(false);
fd0 = (int) (pipeFds >>> 32);
fd1 = (int) pipeFds;
// 其他的我也看不太懂,我们直接进去这个EPollArrayWrapper的构造方法
pollWrapper = new EPollArrayWrapper();
pollWrapper.initInterrupt(fd0, fd1);
fdToKey = new HashMap<>();
}
// EPollArrayWrapper
EPollArrayWrapper() throws IOException {
// 直接看这里,这里调用了一个封装出来的Linux的api:epoll_create,这个东西大概可以理解成一个selector,详细的我们下一章再讲解
epfd = epollCreate();
}
所以其实Selector方法大抵上就是封装了一个epoll_create() 方法,当然还调用了一下epoll_ctl(),把serverchannel给注册进去。
register()
// 5. 将channel注册到selector上,监听连接事件
serverSocketChannel.register(selector, SelectionKey.OP_ACCEPT);
接下来我们分析把channel注册到Selector上的register方法
// SelectableChannel
public final SelectionKey register(Selector sel, int ops)
throws ClosedChannelException
{
return register(sel, ops, null);
} // AbstractSelectableChannel
public final SelectionKey register(Selector sel, int ops,
Object att)
throws ClosedChannelException
{
// 212行,剩下的删掉了
k = ((AbstractSelector)sel).register(this, ops, att); } // SelectorImpl
protected final SelectionKey register(AbstractSelectableChannel ch,
int ops,
Object attachment)
{
if (!(ch instanceof SelChImpl))
throw new IllegalSelectorException();
//生成SelectorKey来存储到hashmap中,一共之后获取
SelectionKeyImpl k = new SelectionKeyImpl((SelChImpl)ch, this);
//attach用户想要存储的对象
k.attach(attachment);
//调用子类的implRegister方法,接下来进去这里
synchronized (publicKeys) {
implRegister(k);
}
//设置关注的option
k.interestOps(ops);
return k;
}
protected void implRegister(SelectionKeyImpl ski) {
if (closed)
throw new ClosedSelectorException();
SelChImpl ch = ski.channel;
//获取Channel所对应的fd,因为在linux下socket会被当作一个文件,也会有fd
int fd = Integer.valueOf(ch.getFDVal());
fdToKey.put(fd, ski);
//调用pollWrapper的add方法,将channel的fd添加到监控列表中
pollWrapper.add(fd);
//保存到HashSet中,keys是SelectorImpl的成员变量
keys.add(ski);
}
调用register方法并没有涉及到EpollArrayWrapper中的native方法epollCtl的调用,这是因为他们将这个方法的调用推迟到Select方法中去了.
select()
// 获取可用channel数量
int readyChannels = selector.select();
接下来我们来分析select()方法
// SelectorImpl
public int select(long timeout)
throws IOException
{
. . . .
return lockAndDoSelect((timeout == 0) ? -1 : timeout);
} // SelectorImpl
private int lockAndDoSelect(long timeout) throws IOException {
. . . .
return doSelect(timeout);
}
// EPollSelectorImpl
protected int doSelect(long timeout) throws IOException {
.....
try {
....
//调用了poll方法,底层调用了native的epollCtl和epollWait方法
pollWrapper.poll(timeout);
} finally {
....
}
....
//更新selectedKeys,为之后的selectedKeys函数做准备
int numKeysUpdated = updateSelectedKeys();
....
return numKeysUpdated;
}
// EPollArrayWrapper
int poll(long timeout) throws IOException {
// 这里面的实现就是调用epoll_ctl()方法注册先前在register方法中保存的Channel的fd和感兴趣的事件类型
updateRegistrations();
// 这里是调用epollWait方法等待感兴趣事件的生成,导致线程阻塞
updated = epollWait(pollArrayAddress, NUM_EPOLLEVENTS, timeout, epfd);
. . . .
}
上面提到的epollCtl和epollWait方法在下一章我们会详细讲,这里先不讲。
总之我们可以知道Selector其实就是封装了Linux提供的api而已,也就是epollCreate、epollCtl和epollWait方法。
selectedKeys()
// 获取可用channel的集合
Set<SelectionKey> selectionKeys = selector.selectedKeys();
接下来我们来看看selectedKeys()方法:
// SelectorImpl
//是通过Util.ungrowableSet生成的,不能添加,只能减少
private Set<SelectionKey> publicSelectedKeys;
public Set<SelectionKey> selectedKeys() {
....
return publicSelectedKeys;
}
很奇怪啊,怎麽直接就返回publicSelectedKeys了,难道在select函数的执行过程中有修改过这个变量吗?publicSelectedKeys这个对象其实是selectedKeys变量的一份副本,你可以在SelectorImpl的构造函数中找到它们俩的关系,我们再回头看一下select中updateSelectedKeys方法:
private int updateSelectedKeys() {
//更新了的keys的个数,或在说是产生的事件的个数
int entries = pollWrapper.updated;
int numKeysUpdated = 0;
for (int i=0; i<entries; i++) {
//对应的channel的fd
int nextFD = pollWrapper.getDescriptor(i);
//通过fd找到对应的SelectionKey
SelectionKeyImpl ski = fdToKey.get(Integer.valueOf(nextFD));
if (ski != null) {
int rOps = pollWrapper.getEventOps(i);
//更新selectedKey变量,并通知响应的channel来做响应的处理
if (selectedKeys.contains(ski)) {
if (ski.channel.translateAndSetReadyOps(rOps, ski)) {
numKeysUpdated++;
}
} else {
ski.channel.translateAndSetReadyOps(rOps, ski);
if ((ski.nioReadyOps() & ski.nioInterestOps()) != 0) {
// 这里加进去
selectedKeys.add(ski);
numKeysUpdated++;
}
}
}
}
return numKeysUpdated;
}
不知道大家有没有留意到,如果我们不先调用select(),直接selectedKeys()是不会获得任何Channel的,因为里面没有更新publicSelectedKeys的方法
还有一点是,publicSelectedKeys是selectedKeys的引用,所以我们获得的是它的引用,而不是每次返回一个新对象,这个引用里面的Channel我们处理完后要记得remove掉,不然下次还是会返回给你的。
顺便一提这里publicSelectedKeys是采用 publicSelectedKeys = Util.ungrowableSet(selectedKeys); 的方式创建出来的,这个方法创建出来的set如方法名ungrowableSet,是不能调用add方法的,只能remove
为什么Netty自己又从新实现了一边native相关的NIO底层方法? 听听Netty的创始人是怎麽说的吧链接。
因为Java的版本使用的epoll的LT模式,而Netty则希望使用ET模式(详情看第四篇的两种触发模式),而且Java版本没有将epoll的部分配置项暴露出来,比如说TCP_CORK和SO_REUSEPORT。
下一篇:epoll的实现原理

参考资料:
https://segmentfault.com/a/1190000017798684?utm_source=tag-newest
深入理解NIO(三)—— NIO原理及部分源码的解析的更多相关文章
- netty源码解解析(4.0)-11 Channel NIO实现-概览
结构设计 Channel的NIO实现位于io.netty.channel.nio包和io.netty.channel.socket.nio包中,其中io.netty.channel.nio是抽象实 ...
- spring5 源码深度解析----- 被面试官给虐懵了,竟然是因为我不懂@Configuration配置类及@Bean的原理
@Configuration注解提供了全新的bean创建方式.最初spring通过xml配置文件初始化bean并完成依赖注入工作.从spring3.0开始,在spring framework模块中提供 ...
- 并发编程(十五)——定时器 ScheduledThreadPoolExecutor 实现原理与源码深度解析
在上一篇线程池的文章<并发编程(十一)—— Java 线程池 实现原理与源码深度解析(一)>中从ThreadPoolExecutor源码分析了其运行机制.限于篇幅,留下了Scheduled ...
- Thrift之代码生成器Compiler原理及源码详细解析1
我的新浪微博:http://weibo.com/freshairbrucewoo. 欢迎大家相互交流,共同提高技术. 又很久没有写博客了,最近忙着研究GlusterFS,本来周末打算写几篇博客的,但是 ...
- Java的三种代理模式&完整源码分析
Java的三种代理模式&完整源码分析 参考资料: 博客园-Java的三种代理模式 简书-JDK动态代理-超详细源码分析 [博客园-WeakCache缓存的实现机制](https://www.c ...
- 深入理解Java AIO(二)—— AIO源码解析
深入理解Java AIO(二)—— AIO源码解析 这篇只是个占位符,占个位置,之后再详细写(这个之后可能是永远) 所以这里只简单说一下我看了个大概的实现原理,具体的等我之后更新(可能不会更新了) 当 ...
- sobel算子原理及opencv源码实现
sobel算子原理及opencv源码实现 简要描述 sobel算子主要用于获得数字图像的一阶梯度,常见的应用和物理意义是边缘检测. 原理 算子使用两个33的矩阵(图1)算子使用两个33的矩阵(图1)去 ...
- SpringMVC关于json、xml自动转换的原理研究[附带源码分析 --转
SpringMVC关于json.xml自动转换的原理研究[附带源码分析] 原文地址:http://www.cnblogs.com/fangjian0423/p/springMVC-xml-json-c ...
- android Service Activity三种交互方式(付源码)(转)
android Service Activity三种交互方式(付源码) Android应用服务器OSBeanthread android Service Binder交互通信实例 最下边有源代码: ...
随机推荐
- 下载cv2时下载失败或下载成功却无法使用怎么办
最近我也在安装cv2的时候遇到了奇怪的问题,导致在安装cv2的时候无法使用.我在网上查了各种资料,虽然都对的,但都不太全面.本文就把安装cv2时可能遇到的各种奇怪的问题的解决方案做一个总结,供大家参考 ...
- Object-Oriented Programming Summary Ⅰ
Part 0: 前言 令人闻风丧胆的OO还是来了.并没有像名字的外表一样可爱,简直就是恶魔. 疯狂压榨OS的时间,周末无法休息,互测狼人机制 虽然网上骂声很多,就算改进到9012年还是有很多不足的地方 ...
- Spark RDD Tutorial
Spark RDD教程 这个教程将会帮助你理解和使用Apache Spark RDD.所有的在这个教程中使用的RDD例子将会提供在github上,供大家快速的浏览. 什么是RDD(Rssilient ...
- 文本编辑器 - Sublime Text 3 换行无法自动缩进的解决方法
一.换行无法自动缩进的问题,如图: 稍微查了一下网上的办法,是把汉化文件删除,但是会造成菜单栏混乱,简直无法忍受... 那么这里介绍的是另一种解决办法.在用户的热键配置文件(preferences-k ...
- 简述树,Trie,Avl,红黑树
树的表示方法 在平时工作中通常有2种方式来表示树状结构,分别是孩子链表示法和父节点表示法.光说名词可能无法让人联系到实际场景中,但是写出代码之后大家一定就明白了. 孩子链表示法,即将树中的每个结点的孩 ...
- 我的学习笔记之node----node.js+socket.io实时聊天(1)
本想着从hello word开篇,也确实写了相关学习笔记.各种原因吧,现在又着急写出作品,便作罢. 这里将记录一个node.js+socket.io的实时聊天程序.(当然我也是跟着网上各种教程资料学习 ...
- Vue2.0 【第二季】第7节 Component 组件 props 属性设置
目录 Vue2.0 [第二季]第7节 Component 组件 props 属性设置 第7节 Component 组件 props 属性设置 一.定义属性并获取属性值 二.属性中带' - '的处理方式 ...
- 使用 VSCode 在 Mac 上配置 C/C++ 调试环境
Background VSCode是微软开发的一款开源代码编辑器,具有可拓展性强,多语言支持,跨平台等优点,在不同的个性化配置下几乎可以用作所有的轻量级开发.我在初学C的时候也使用的是类似于Xcode ...
- Python3 + requests + unittest接口测试
一.缘 起 笔者最近完成了基于Python3 + requests + unittest的接口测试脚本,故在此做一下记录,于己为复盘,于彼为学习和参考 二.思 路 接口测试无非三步: 首先,造数据 - ...
- 量化学习 | Tushare 基本面选股 (二)
量化投资比较重要的是策略,可是你得先选个好股,价值投资需要认同他的价值,值得投资的股票才有投资的机会,现在简单介绍一下基于基本面的选股,其实我现实生活中也有炒股,都是经验之说的选股原则. 首先从tus ...