前言
之前几篇文章分析了Pipeline、EventLoop和出站缓存的源码,这几个模块都与Channel有着千丝万缕的关系,将它们与Channel组合在一起形成了完整的处理流程,下面看一下Channel的源码了解具体实现。
正文
Channel接口
从这个接口图可以看出来:Channel内部有一个Unsafe接口,主要用于封装JAVA底层I/O连接,同时Channel可以分配内存、与Pipeline一对一绑定以及线程模型是之前介绍的EventLoop
AbstractChannel实现
AbstractChannel保存了Channel的通用属性,在这一层可以看到之前介绍的DefaultChannelPipeline和EventLoop,以及用ChannelId来表示全局ID
构造方法new了一个DefaultChannelPipeline
@Override
public boolean isWritable() {
ChannelOutboundBuffer buf = unsafe.outboundBuffer();
return buf != null && buf.isWritable();
}
@Override
public ChannelFuture connect(SocketAddress remoteAddress) {
return pipeline.connect(remoteAddress);
}
随便看两个方法,isWritable交给了ChannelOutboundBuffer来处理,根据上一篇文章的介绍,isWritable表示write的字节数量是否超过了设置的高水位线;而connect等方法交给了pipeline,根据pipeline的介绍,它的内部有Head和Tail节点,而Head节点也有connect等方法,而Head的connect方法是交给unsafe进行处理,这里的connect等方法相当于给pipeline发送了一个通知,pipeline内部遍历handler找到能处理的节点
Channel和ChannelOutboundBuffer的Writable改变都会通知给Pipeline,Pipeline把消息链式传播,如果这是一个connect或bind等I/O事件,会交给Unsafe去处理
ChannelOutboundBuffer是保存在AbstractUnsafe里面,因为实际wirte和flush都是由unsafe去调用的
随便看一个方法,大体上都差不多
通用register方法前后处理了状态问题以及eventLoop问题,交给了register0去处理
register0方法依旧把核心注册交给了doRegister这个抽象方法,由子类负责实现,这它只处理了注册后Channel状态改变,fireChannelRegistered和fireChannelActive就是在这里被调用的。前面也提到过,如果想自定义handler去获取这事件,可以重写channelRegistered来获取注册通知
unsafe的write方法和flush方法都是按照上一篇文章讲述的一样,交给了ChannelOutboundBuffer,剩下读取真实数据的逻辑还是交给子类实现的
总的来说:AbstractChannel以及AbstractUnsafe只干了一件事,就是将调用方法传给Pipeline,让它去通知handler响应我们关心的事件,而具体逻辑交给了子类去实现
AbstractNioChannel源码
AbstractNioChannel里面有几个属性的东西:SelectableChannel、SelectionKey,都是java nio代码,这里可以看到已经逐渐进入了java nio的封装
构造方法将configureBlocking设置为false,表示启用不阻塞模式。
这一层的AbstractNioUnsafe实现了doRegister,unwrappedSelector之前介绍过,就是打开了一个Selector,然后注册到java通道上面。这段代码把它摊开来讲,就是java nio的东西,只是封装了一层实现原理都是一样的
上一层调用过doBeginRead方法,实际上就是向selectionKey注册了Read事件而已
这一层只是将nio进行了抽象,它将selector注册到了channel上,只是开启了流程的第一步register,还需要进行bind端口、发送数据等
AbstractNioByteChannel源码
关注NioByteUnsafe实现了哪些方法
read方法使用了堆外内存,这也是netty高性能的原因其中之一,doReadBytes依旧是一个抽象放,当读完数据后使用pipeline进行通知
doWrite0方法通过获取出站缓存的flushedEntry节点,交给doWriteInternal
还是没有涉及到核心读取,这里处理了三个不同的类型的消息,值得一提的是FileRegion的transferTo是一个零拷贝方法,这也是netty高性能的原因之一。
NioSocketChannel源码
终于到最终类了,看一下内部实现了哪些
这里直接用了底层的SelectorProvider去打开一个通道
调用java的bind方法,同时还注册了connect事件
从doWrite可以看到,出站缓存的nioBuffers方法终于在这里调用了,将累计的entry转换为ByteBuffer数组
这里将nioBufferCnt分为了三种情况,0、1和大于1,做了这么多还是交给了java channel的write方法
总结
简单的看了Channel接口和它的部分实现类,了解到了java Channel的封装思路,netty使用了出站缓存、堆外内存、零拷贝、FastThreadLocal和对象回收池等操作,在细节上对SelectionKey存储方式进行优化,结合nio的非阻塞,将性能优化到极致。同时通过对源码的查看,能使用得更熟练和有助于对netty进行自定义改造。