Netty4.1源码阅读——核心(Channel)

494 阅读4分钟

前言

之前几篇文章分析了Pipeline、EventLoop和出站缓存的源码,这几个模块都与Channel有着千丝万缕的关系,将它们与Channel组合在一起形成了完整的处理流程,下面看一下Channel的源码了解具体实现。

正文

Channel接口

image.png

从这个接口图可以看出来:Channel内部有一个Unsafe接口,主要用于封装JAVA底层I/O连接,同时Channel可以分配内存、与Pipeline一对一绑定以及线程模型是之前介绍的EventLoop

AbstractChannel实现

image.png

AbstractChannel保存了Channel的通用属性,在这一层可以看到之前介绍的DefaultChannelPipeline和EventLoop,以及用ChannelId来表示全局ID

image.png

image.png

构造方法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找到能处理的节点

image.png

Channel和ChannelOutboundBuffer的Writable改变都会通知给Pipeline,Pipeline把消息链式传播,如果这是一个connect或bind等I/O事件,会交给Unsafe去处理


image.png

ChannelOutboundBuffer是保存在AbstractUnsafe里面,因为实际wirte和flush都是由unsafe去调用的

随便看一个方法,大体上都差不多

image.png

通用register方法前后处理了状态问题以及eventLoop问题,交给了register0去处理

image.png

register0方法依旧把核心注册交给了doRegister这个抽象方法,由子类负责实现,这它只处理了注册后Channel状态改变,fireChannelRegistered和fireChannelActive就是在这里被调用的。前面也提到过,如果想自定义handler去获取这事件,可以重写channelRegistered来获取注册通知

image.png

image.png image.png

unsafe的write方法和flush方法都是按照上一篇文章讲述的一样,交给了ChannelOutboundBuffer,剩下读取真实数据的逻辑还是交给子类实现的

总的来说:AbstractChannel以及AbstractUnsafe只干了一件事,就是将调用方法传给Pipeline,让它去通知handler响应我们关心的事件,而具体逻辑交给了子类去实现

AbstractNioChannel源码

image.png

AbstractNioChannel里面有几个属性的东西:SelectableChannel、SelectionKey,都是java nio代码,这里可以看到已经逐渐进入了java nio的封装

image.png

构造方法将configureBlocking设置为false,表示启用不阻塞模式。

image.png

这一层的AbstractNioUnsafe实现了doRegister,unwrappedSelector之前介绍过,就是打开了一个Selector,然后注册到java通道上面。这段代码把它摊开来讲,就是java nio的东西,只是封装了一层实现原理都是一样的

image.png 上一层调用过doBeginRead方法,实际上就是向selectionKey注册了Read事件而已

这一层只是将nio进行了抽象,它将selector注册到了channel上,只是开启了流程的第一步register,还需要进行bind端口、发送数据等

AbstractNioByteChannel源码

关注NioByteUnsafe实现了哪些方法

image.png

read方法使用了堆外内存,这也是netty高性能的原因其中之一,doReadBytes依旧是一个抽象放,当读完数据后使用pipeline进行通知

image.png doWrite0方法通过获取出站缓存的flushedEntry节点,交给doWriteInternal

image.png

还是没有涉及到核心读取,这里处理了三个不同的类型的消息,值得一提的是FileRegion的transferTo是一个零拷贝方法,这也是netty高性能的原因之一。

NioSocketChannel源码

终于到最终类了,看一下内部实现了哪些

image.png

这里直接用了底层的SelectorProvider去打开一个通道

image.png

image.png

调用java的bind方法,同时还注册了connect事件

image.png

从doWrite可以看到,出站缓存的nioBuffers方法终于在这里调用了,将累计的entry转换为ByteBuffer数组

image.png

这里将nioBufferCnt分为了三种情况,0、1和大于1,做了这么多还是交给了java channel的write方法

总结

简单的看了Channel接口和它的部分实现类,了解到了java Channel的封装思路,netty使用了出站缓存、堆外内存、零拷贝、FastThreadLocal和对象回收池等操作,在细节上对SelectionKey存储方式进行优化,结合nio的非阻塞,将性能优化到极致。同时通过对源码的查看,能使用得更熟练和有助于对netty进行自定义改造。