从example开始

先看源码结构,如下图,netty包含几个核心部分:

从example开始_第1张图片
netty源码结构

1, buffer:ByteBuf类的实现,类似jdk自带的ByteBuffer,区别是ByteBuffer读写用同一个指针,netty觉得使用麻烦,就自己开发类ByteBuf,分别维护readIndex和writeIndex。实现细节以及对比分析之后会有文章专门分析。

2, codec/codec-*:网络传输的都是字节流,程序的输入输出参数一般是java对象,所以需要编码和解码。从底层的channel读到的原始数据都存放在ByteBuf里,要写入channel的数据也必须先转换为ByteBuf。于是就有了一下几个类:ByteToMessageDecoder,MessageToMessageDecoder,MessageToByteEncoder,MessageToMessageEncoder,还有两个编码解码融为一体的类:ByteToMessageCodec,MessageToMessageCodec,顾名思义。整个codec实现细节稍后专门分析。

3,example:包含示例,此文章的重点,也是分析任何源码必不可少要看的。

4,handler/handler-proxy:此handler并不是ChannelHandler,稍后分析。

5,transport/transport-*:各种传输协议的实现,有一个有趣的发现,transport-native-kqueue/transport-native-epoll这两个模块都需要通过c调用操作系统的IO多播api,侧面也反应出一个问题:java的nio在linux/osx上应该是使用的poll,在windows上使用的select。关于此问题以及IO多播稍后专门分析。


开始本文的重点,从example中的echo开始,echo实现的功能:client初始化一个ByteBuf,填充数字,然后在connect上server之后发送这个ByteBuf到server,server直接把自己收到的数据原样返回,client收到server的响应之后,再把响应信息原样发给server。

从example开始_第2张图片
echo server端的核心代码

ServerBootstrap和Bootstrap都继承AbstractBootstrap,区别是ServerBootstrap要处理两种channel,一个是server channel,绑定到指定端口监听client的连接,同时还要处理client的连接channel。Bootstrap只需要处理client channel。所以ServerBootstrap比Bootstrap多一组以child开头的方法用于配置client channel。

必备核心组件:

1,EventLoopGroup:顾名思义,它包含一组EventLoop,哈哈,EventLoopGroup和EventLoop都实现接口ScheduledExecutorService,所以简单的说他们都是支持调度的线程池,只是EventLoop只有一个线程,b.group()用于指定分别处理server channel和client channel的线程池,也可以使用同一个线程池。处理server channel的线程池指定了只有一个线程,原因很简单,因为它只需要处理server channel的accept,accept之后的child channel都是交给第二个线程池处理的,稍后详细分析。

2,channel:实现底层的协议以及IO模型,bind方法调用时通过反射获取实例,细节稍后分析。

3,option:指定对应channel的协议相关参数。

4,attr:指定与channel关联的key-value数据,之后在handler中可以取回。

5,handler:指定server channel的处理链,b.bind()调用时,会在指定的handler链后面加上ServerBootstrapAcceptor,此handler的作用是accept client连接,然后把child*方法设置的参数配置到child channel上。

6, childHandler:指定client channel的handler,一般第一个handler用于初始化整个handler链,详细细节稍后分析。


ServerBootstrap启动细节:

b.bind方法调用时,先通过channel指定的类的构造方法反射获取实例,然后是初始化channel实例,设置通过option方法指定的通信协议相关参数,设置通过attr方法指定的channel实例附加属性,把handler指定的handler添加到channel附加的pipeline里,第一个handler一般用于初始化整个pipeline,然后从pipeline里删除自己。最后一个步骤是把channel注册到对应的EventLoopGroup上。然后整个系统全靠EventLoopGroup上的线程检测到IO事件之后通过channel关联的pipeline的一组fire*方法触发各种事件了。


有几个对应关系一定要捋清楚了:

每个channel都自带一个pipeline,每个pipeline里有一个ChannelHandlerContext双链表,用于保存添加到pipeline的每个channel handler。channel handler分为有状态和无状态(分局有没有实例变量区分。。。),无状态的channel handler在用Sharable注解之后,同一个实例可以添加到多个pipeline,或者添加到同一个pipeline多次,否则会报错,每次添加都会检查handler里的added属性,添加成功之后改为true。ChannelHandlerContext用于维护handler,pipeline,EventLoop的关系,handler和pipeline是理论上多对多的关系,ChannelHandlerContext相当于中间表。ChannelHandlerContext每个实例都可以维护一个map,所以一个handler要维护状态有两种实现方法:1,实例变量,一个实例只能添加到一个pipeline;2,handler在ChannelHandlerContext的map上维护状态,自身是无状态的,可以多次添加到pipeline。


每部分每个组件的细节在其他文章里有分析。对netty和java nio不熟的推荐两本书:Java IO, NIO and NIO 2.pdf和netty in action.pdf

你可能感兴趣的:(从example开始)