简谈启动过程的初始化
服务端、客户端的启动都是类似的, 主要有几个方法: bs.group(bossGroup) .channel(NioSocketChannel.class) .option(ChannelOption.SO_KEEPALIVE, true) .handler(new ChannelInitializer() { ChannelPipeline pipeline = socketChannel.pipeline(); pipeline.addLast(new StringDecoder(Charset.forName("GBK"))); }); 其中做了很多初始配置的工作,这里略去,不过需要注意的的是ChannelInitializer,ChannelInitializer 直接继承于ChannelInboundHandlerAdapter,也是一个ChannelHandler ,就是处理器,不过它的作用一般就是初始化一个pipeline,如上,通过pipeline.addLast 可以添加很多的处理器。这些处理器Handler就是 用户逻辑,是关键的业务代码。 每条pipeline 上可以有多个handler,这个逻辑就是每次连接成功之后,会执行ChannelInitializer, 即上面的 .handler方法设置的 Handler,是 初始化的作用Channel
Channel 是接口,其两个主要的顶层的实现是 AbstractChannel、其成员变量有Channel parent、Unsafe、SocketAddress localAddress、remoteAddress、EventLoop。 其中 Unsafe 的主要实现是AbstractUnsafe、 AbstractNioUnsafe,后者是对java 的nio做了封装。 所以其实简单来说,netty 的Channel 就是java 的nio的Channel ,也就是 对端的实际发出的连接 从channel 的主要实现AbstractChannel看,它有一个DefaultChannelPipeline指针 pipeline
一个channel 就是一个连接,就是每次和对端建立连接之后,都会创建一个 channel, 不多不少 然后channel 对应一条 pipeline,每条pipeline 上可以有多个handler,从构造器可以看出来: protected AbstractChannel(Channel parent) { this.parent = parent; id = newId(); unsafe = newUnsafe(); pipeline = newChannelPipeline(); } 不过是不是1:1,这里还看不出来。因为其实有可能是 ctx1 -> handler1, 然后 handler1 -> ctx2, ctx2 -> handler2 这样的链式关系。链式关系显然不是简单直观的 1:1 关系。 但是跟进去会发现,确实是1:1 关系:
protected DefaultChannelPipeline newChannelPipeline() { return new DefaultChannelPipeline(this); // 正是这里的this,完成了双向绑定 } // // 这里的参数是Channel , 相当于是这里做了双向绑定 protected DefaultChannelPipeline(Channel channel) { // 这里的channel就是前面的AbstractChannel实例 this.channel = ObjectUtil.checkNotNull(channel, "channel"); succeededFuture = new SucceededChannelFuture(channel, null); voidPromise = new VoidChannelPromise(channel, true); tail = new TailContext(this);// 为pipeline 和TailContext绑定, 这里也是双向绑定 head = new HeadContext(this);// 这里也是双向绑定 head.next = tail; // 建立好pipeline的管道链条 tail.prev = head; }
ChannelPipeline
ChannelPipeline 的直接实现就一个:DefaultChannelPipeline, 从源码看DefaultChannelPipeline, 它主要有一个Channel、两个AbstractChannelHandlerContext,分别是 head、tail,分别是pipeline的头和尾,两者必不可少,而且是固定不可变的; 另外 还有表示 executor group 和 executor 的对应关系: Map<eventexecutorgroup, eventexecutor=""> childExecutors; 其 里面的方法值得好好看看, 比如: @Override public final ChannelFuture bind(SocketAddress localAddress, ChannelPromise promise) { return tail.bind(localAddress, promise); } 里面写了很多很多,每一个方法都值得推敲,都是准确无误ChannelHandler
ChannelHandler 几乎没有成员变量,比如ChannelHandlerAdapter 只有一个 boolean added: ChannelInboundHandler 提供了入站事件的处理入口。类似地,ChannelInboundHandlerAdapter 提供了默认空实现,以方便其他的或 自定义的 Handler继承,然后只需实现自己想实现的、感兴趣的方法即可。 ChannelHandler 的子类包括ChannelOutboundHandlerAdapter、ChannelInboundHandlerAdapter、ChannelDuplexHandler、MessageToMessageEncoder、MessageToByteEncoder 基本上是没什么成员变量的。 可见它是无状态的,他和其他组件有关系, 但基本是他被其他组件使用。 ChannelHandler 虽然成员变量不多,但是方法众多,要处理好这些,其实并不容易。netty为我们提供了非常非常繁多的各种处理器, 也是非常复杂的,此处按下不表,后文再叙。我们基本上只要拿来用即可。但是前提是知道如何使用。 ChannelHandler 看起来很单薄? 确实是这样,它没有netty框架相关的成员变量,它甚至可以没有成员变量。因为它是 用户需要去实现的处理器, netty已经做了很好的封装,它已经是netty 核心内容之外的东西了。 所以它就不需要 再直接对接 netty的 reactor 打交道。 它只需要处理reactor 模型派发的 各种事件即可。 但是呢,它和它的两个直接子接口,提供了 handlerAdded 等触发。 ChannelHandlerAdapter 提供了默认空实现。 所有的这些接口方法, 都至少存在一个ChannelHandlerContext类型的参数,然后通过ChannelHandlerContext 就可以获取所需的 pipeline、 channel、eventloop 等。 ChannelOutboundHandler 提供了出站事件的处理入口。类似地,ChannelOutboundHandlerAdapter提供了默认空实现。 ChannelHandlerContext 理解ChannelHandlerContext 是非常关键的。 它处在中间,上下都是文,可谓是 上下文啊。 不过, ChannelHandlerContext 到底是什么? 其实就是对ChannelHandler进行了 上下的 封装,使得它可以不用再去和pipeline、 channel、eventloop打交道了。不用去,也不需要去理解哪些难搞的概念,如 pipeline、 channel、eventloop,使得编程稍微“简单”了些。 观察 ChannelHandlerContext 发现,其实他就是已经对 pipeline、 channel、eventloop 做了很多工作。 复杂的自己搞定,然后把简单的事情 给 ChannelHandler。其实ChannelHandlerContext extends AttributeMap, ChannelInboundInvoker, ChannelOutboundInvoker,然后基本上ChannelHandler所有的方法都是直接调用ctx完成的,而ctx又是封装、调用了pipeline、 channel、eventloop,或者就把事件传递给了 next。. 话虽如此,但是,很多时候,ChannelHandler还是需要去获取 pipeline、 channel、eventloop 之类的。 比如,我可能需要从头发送信息。 ChannelHandlerContext的直接实现是AbstractChannelHandlerContext ,它主要就是包含了向后向前的指针,next、prev,然后还有就是 顺序: ordered。 还有一个DefaultChannelPipeline指针 pipeline :
AbstractChannelHandlerContext的具体实现 主要有3个:HeadContext、TailContext、DefaultChannelHandlerContext 对于普通的 ChannelHandler 自然是使用 DefaultChannelHandlerContext,
代码在DefaultChannelPipeline, private AbstractChannelHandlerContext newContext(EventExecutorGroup group, String name, ChannelHandler handler) { return new DefaultChannelHandlerContext(this, childExecutor(group), name, handler); } 其实newContext还是在pipeline的 addXxx方法中 被调用的。
@Override public final ChannelPipeline addLast(EventExecutorGroup group, String name, ChannelHandler handler) { final AbstractChannelHandlerContext newCtx; synchronized (this) { checkMultiplicity(handler); newCtx = newContext(group, filterName(name, handler), handler); addLast0(newCtx); ... } callHandlerAdded0(newCtx); return this; } private void addLast0(AbstractChannelHandlerContext newCtx) { // 加入到链表之中 AbstractChannelHandlerContext prev = tail.prev; newCtx.prev = prev; newCtx.next = tail; prev.next = newCtx; tail.prev = newCtx; }
DefaultChannelHandlerContext 构造器的参数的this是pipeline,而pipeline的头尾之间有很多ctx,所以pipeline和ctx关系 可以认为是 1 : N 。 当然,这个关系不是简单的1个管道 : 一个集合的ctx ,而是1个管道 : 一个链表的ctx。 newContext的第三个参数,即DefaultChannelHandlerContext 构造器第四个参数handler,是创建时候就分配给ctx 的。显然,因为每次创建ctx 分配一个handler,handler有可能不是同一个,所以,handler:ctx 肯定是 1:N. 但是 ,是不是1:1,这里还看不出来。但至少是有单向关系的, 事实证明,这个确实是是1:1关系。因为handler 中方法的ctx参数,全都是事件形式传递的。ctx 是在链表之上的,而事件在ctx 组成的链表上传递,然后ctx 把事件交给了其拥有的 handler 进行处理。所以 是1:1关系。 当然,如果handler 可以共享,那么就是handler:ctx = 1:N, 另当别论。
观察&分析总结
综上,提供观察&分析可见,基本是如下关系:
其中,每一条线, 表示一个类的对应的成员变量或者说一个字段、一个指针,把主要字段用直线表示出来,全部画好之后, 得到如上的图。 ChannelPipeline 是连接创建的时候就会创建好的,而Channel 也是一个连接一个, Channel 比ChannelPipeline 是 1:1 的关系。 其中的Handler 基本是固定的,当然,我们也可以动态增删ChannelHandler,上面只画了3个ctx; ChannelHandler 和ChannelHandlerContext 是1:1 关系,就是说每次确实是建立连接之后,创建一个新的Channel ,然后绑定到唯一的 ChannelPipeline 实例上去... (准确说是 创建,不是绑定. ) 可以说 pipeline、handler 关系是 1 : N, 但他们不是直接的关系,原因见上图的链条 ... PS: 有几个图很流行,但是 看完其实还是晕乎乎的。因为你不去分析总结,那么你永远都是迷迷糊糊..
这个图 其实是没错的,但是不够清晰啊!head、 tail 都没有画出来啊!