请先参考我以前的博文JAVA学习笔记--3.Network IO的 NIO(NonBlocking IO) SOCKET 章节。这里主要讲下JAVA NIO其中几个比较被忽略的细节,不求全,欢迎补充。html
Selectjava
当调用ServerSocketChannel.accept();
时,若是该channel处于非阻塞状态并且没有等待(pending)的链接,那么该方法会返回null;不然该方法会阻塞直到链接可用或者发生I/O错误。此时实际上Client发送了connect请求而且服务端是处于non-blocking模式下,那么这个accept()会返回一个不为null的channel。算法
当调用SocketChannel.connect()
时,若是该channel出于non-blocking模式,只有在本地链接下才可能当即完成链接,并返回true;在其余状况下,该方法返回false,而且必须在后面调用 SocketChannel.finishConnect
方法。若是SocketChannel.finishConnect
的执行结果为true,才意味着链接真正创建。编程
SocketChannel.write()
方法内部不涉及缓冲,它直接把数据写到socket的send buffer中。设计模式
每次迭代selector.keys()完时,记得remove该SelectionKey,防止发生CPU100%的问题。api
一般不应register OP_WRITE,通常来讲socket 缓冲区老是可写的,仅在write()方法返回0时或者未彻底写完数据才须要register OP_WRITE操做。当数据写完的时候,须要deregister OP_WRITE。 socket空闲时,即为可写.有数据来时,可读。对于nio的写事件,只在发送数据时,若是由于通道的阻塞,暂时不能所有发送,才注册写事件key.interestOps(key.interestOps() | SelectionKey.OP_WRITE);
。等通道可写时,再写入。同时判断是否写完,若是写完,就取消写事件便可key.interestOps(key.interestOps() & ~SelectionKey.OP_WRITE);
。 空闲状态下,全部的通道都是可写的,若是你给每一个通道注册了写事件,那么确定是死循环了,致使发生CPU100%的问题。详见Netty的NioSocketChannel的父类方法setOpWrite()和clearOpWrite()方法。这个在iteye里面的帖子Java nio网络编程的一点心得也有说明。网络
在register OP_CONNECT后,而且触发OP_CONNECT后,须要再deregister OP_CONNECT。详见Netty的NioEventLoop.processSelectedKey()方法后半部分。多线程
另外,须要注意到Selector.wakeup()是一个昂贵的操做,通常须要减小是用。详见NioEventLoop.run()方法内的处理方式。架构
还有各类各样的坑,实在太多了。详见Java NIO框架的实现技巧和陷阱框架
虽然笔者阅读完几个重要的交互过程,可是代码细节较多,没法体现NETTY对NIO的使用。因而画了两幅图,与读者共享。
事先说明下,下图图片上的标记2表示是一个新的EventLoop线程,非main线程。
另外,虽然Netty中大量使用了ChannelFuture异步,可是换种方式理解的话,能够理解为同步执行。
这里有个细节,就是boss线程注册了OP_ACCEPT线程,而后当接收到客户端的请求时,会使用work线程池里面的线程来处理客户端请求。代码细节在NioServerSocketChannel.doReadMessages
的tag1.1.2处以及child.unsafe().register(child.newPromise());
处。
从Netty的Maven仓库能够看 到,大体分为以下模块:
综上,能够看出,首先对框架机制和传输的数据进行了抽象,而后又对数据如何在框架中传输进行了抽象。
下面的介绍的思想比较零散,不成系统,主要是阅读代码过程当中产生的碰撞,供读者参考。
Netty 一样也采用了多个Selector并存的结构,主要缘由是单个Selector的结构在支撑大量链接事件的管理和触发已经遇到瓶颈。
Bootstrap.channel()方法经过传递class对象,而后经过反射来实例化具体的Channel实例,必定程度上避免了写死类名字符串致使将来版本变更时发生错误的可能性。
框架必备的类,你们还能够看下common工程,里面真是一个宝藏。
InternalLogger
用来避免对第三方日志框架的依赖,如slf,log4j等等。ChannelOption
灵活地使用泛型机制,避免用户设置参数发生低级错误。SystemPropertyUtil
提供对xt属性的访问方法DefaultThreadFactory
提供自定义线程工程类,方便定位问题。PlatformDependent
若是须要支持不一样平台的话,能够把平台相关的操做都放在一块儿进行管理。打印日志时,能够参考这样来拼接参数,String.format("nThreads: %d (expected: > 0)", nThreads)
@Sharable表示该类是无状态的,仅仅起“文档”标记做用。
在子接口里仅仅把父接口方法返回值覆写了,而后什么都不作。这样必定程度上避免了强制转型的尴尬。
把前一个future做为下一个调用方法的参数,这样能够异步执行。而后后面的逻辑能够先判断future结果后再进行处理,从而提高性能。
addLast0 等以0为结尾的方法表示私有的含义。
有些方法的getter、setter前缀省略,有点相似jQuery里面命名风格。不过不太建议在本身的产品中使用这种风格,尽可能使用和和产品内的同样的命名风格。
心中存疑,请你们不吝赐教。
在NioEventLoop.run()方法中,好像每次用完SelectionKey没有remove 掉它,可能和SelectedSelectionKeySet实现机制有关。可是没直接看出来具体之间的联系,
为何boss也要是个线程池?目前来看,服务端2个线程保持不变,main线程出于wait状态,boss线程池其中的一个线程进行接收客户端链接请求,而后把请求转发给worker线程池。
javaChannel().register(eventLoop().selector, 0, this);
,为何ops参数默认是0?
socketChannel.register() and key.interestOps()
仍是有点不太明白,估计个人思路钻进死胡同了。
b.bind(port)这个里面的内容很是复杂,不只仅是bind一个port那么简单。因此该方法命名不是很好。
父接口依赖子接口,也不是很好。
DefaultChannelPipeline.addLast 这个方法太坑了,并非把handler加到最后一个上面,而是加到tail前一个。
无一类外的是,继承体系相对复杂。父类,子类的命名一般不能体现出谁是父类,谁是子类,除了一个Abstract可以直接看出来。
客户端单实例,防止消耗过多线程。这个在http://hellojava.info/中屡次提到。
目前网上的NIO例子都基本都不太靠谱,BUG多多。建议能够参考下《JAVA 分布式JAVA应用 基础与实践》。
JAVA NIO不必定就比OIO快,重点是更加Scalable。
框架帮趟坑,不要轻易制造轮子,除非现有轮子不好劲。JAVA NIO里面的坑太多了,Netty里面的大量的issue充分说明了问题。技术某种程度上不是最重要的,若是你们努力程度差很少的话,技术上不会差到哪里去。开源产品主要是生态圈的建设。
接收对端数据时,数据经过netty从网络中读取,进行其余各类处理,而后供应用程序使用。发送本地数据时,应用程序首先完成数据处理, 而后经过netty进行各类处理,最终把数据发出。可是为何要区分inbound,outbound,或者说提供了head->tail以及tail->head的遍历?
当咱们跳出里面的细节时,考虑一下,若是你是做者的话,会如何考虑。总体的一个算法 。不一样的通信模型,nio,sun jdk bug, option(默认和用户设置),异步future、executor,pipeline、context、handler, 设计模式 。
我想象中操做应该是这样的,handler链管理不须要走pipeline,event链也不须要走pipiline,仅仅对数据的发送,接收和操做才走pipeline。事件机制应该起一些加强型、辅助型做用,不该该影响到核心流程的执行或者起到什么关键做用,主要是应该给第三方扩展用。而netty中,全部的一切都要走pipelne。
太多异步,怎么测试?
招式vs心法。招式,至关于api;心法,至关于api工做原理,利与弊。仍是要理解底层,不然仍是可能理解不清楚。cpu,内存,io,网络,而最终浮于招式。
看完了么?知道了Netty是什么,内部大概是怎么运做的,可是有些细节还不知道为何。真的就理解精髓了么?NIO的坑.. 底层OS的坑.. Netty的精髓是在于对各类细节的处理,坑的处理,对性能的处理,而不是仅仅一个XX模式运用。JAVA NIO细节和坑实在太多,估计再给我一周的时间,也研究不完。有点小沮丧。
Stackoverflow:Java NIO - using Selectors
Stackoverflow:Difference between socketChannel.register() and key.interestOps()