Netty中对于ChannelPipeline责任链的个人理解总结,以及对于客户端和服务端的Channel的理解
Channel客户端和服务器端都有
在Netty中,Channel表示是对端的连接。比如说如果是在服务器端,那么每一个客户端来连接自己,服务器就会为这个客户端创建一个Channel,所以对于服务器来说一个Channel就表示一个客户端;
如果是在客户端,那么客户端可能需要连接多台服务器,客户端每连接一个服务器,就会为这个服务器创建一个对应的Channel,所以对于客户端来说也可以把一个Channel理解成一个服务器。
因此在Netty中你会发现,不管是Client客户端,还是Server服务器端,它都会有一个Channel,这个Channel会与唯一一个ChannelPipeline通道关联,通道里面是一个由多个ChannelHandler组成的处理器链。Netty的这种思想内部使用的是责任链设计模式。
Netty中的入站和出站指的是什么
我刚开始理解的是,凡是客户端到服务器的请求都是入站,凡是服务器到客户端的请求都是出站,这种理解非常的错误;
入站和出站其实与客户端服务器没有太大的关系,入站和出站是相对于网络和应用程序的,如果是网络中的数据流入到应用程序中,那么就是入站;如果是应用程序中的数据流出到网络中,那么就是出站。如下图:
从上图中可以发现,对于客户端来说,它的编码处理器就是一个出站处理器,为什么呢?因为客户端在给服务器端发送请求的时候,需要先把数据流入到网络中,而从应用程序流入网络,这就是出站;
而对于客户端来说它的解码处理器就是一个入站处理器,为什么呢?因为服务端在接收客户端的数据的时候,是从网络中接收的,而从网络流入应用程序,这就是出站。
因此是入站还是出站和客户端服务器端没有太大的关系,主要是网络与应用程序有关系,如果你的数据是从应用程序流向网络的,那么就是出站;如果你的数据是从网路流向应用程序的,那么就是入站。
Netty中的ChannelHandler构成的责任链是什么样的?
首先看下官方说明:
- 官方说每个Channel都会唯一关联一个ChannelPipeline通道。
- 官方说一个ChannelPipeline通道里面会有多个ChannelHandler组成一个链条。
- 官方说内部是使用的ChannelHandlerContext组成的双向链表。
- 官方说Netty里面的ChannelHandlerContext上下文对象不是全局唯一的,和其他地方的上下文对象设计可能略有不同。
- 官方说双向链表的每个节点对应的ChannelHandlerContext中都有一个唯一对应的ChannelHandler处理器。
大致的图像模型如下图:
但是只看官方说明我还是没有理解内部的运行过程,因此我进行了一个自己的转化,可能底层思想会有些许的偏差,但是对待每一应用场景的时候确实可以用我这一套理论解释,因此我就先这样理解,以后随着自己了解逐渐加深再更正。
现在以客户端的角度,看一下它的入站和出站是怎么经理ChannelHandler责任链上的每个处理器的?
看下我们自定义的责任链中每个ChannelHandler的顺序,如下图:
所以我们转换之后,责任链上的ChannelHandler处理器的顺序就如下图:
假设我们远程已经有个服务器启动成功在等待我们客户端连接了,那么这个时候我去启动客户端,启动完之后,当客户端连接服务器成功的时候会自动的执行clienthandlerInbound中的连接服务器成功的方法,这个过程不牵涉到入站出站,方法如下图:
其实也就是当客户端连接成功之后会去执行channelActive方法,然后会往网络中冲刷数据,这个时候就牵涉到了出站操作了。那么出站的时候会调用哪些ChannelHandler处理器呢?首先我们需要有个往网络中冲刷数据的ChannelHandler,出站的时候就是从这个ChannelHandler为起始点,因为是出站,所以就是从这个ChannelHandler起始点往链表的头部head方向,也就是链表的左边以此调用责任链上的每个ChannelHandler处理器,因为我们是从应用程序往网络冲刷数据,因此是出站操作,因此我们往左调用责任链的ChannelHandler处理对象的时候只会调用outBoundHandler出站处理器,因此对于我们上面的情况,责任链中只有一个出站处理器就是encodeOutbound,因此出站的时候只会执行这一个ChannelHandler处理器,我本地打断点测试也确实是如此。
怎样验证客户端出站操作的时候是以clientHandlerInbound这个处理器为起始点呢?我们可以再写一个出站处理器ChannelHandler,然后把这个处理器加入到责任链的最尾端,也就是加入到clientHandlerInbound这个起始点处理器的后面,只要我们尾端的新加的这个出站处理器不被执行,那么就证明了客户端出站操作的时候是以clientHandlerInbound这个处理器为起始点的。
代码添加新的出站处理器如下图:
看一下此时的责任链的结构 如下图:
因为我们出站操作的时候,是往链表的头部执行,也就是从当前起始点clientHandlerInbound往左执行,因此是不能执行到newOutbound的,结果也确实是没有执行到,本地调试的测试结果。出站的时候只执行了encoderOutbound这个处理器。
但是我如果把newOutbound放在clientHandlerInbound起始点ChannelHandler处理器的前面的话,出站的话就会先执行newOutbound出站处理器,然后再执行encoderOutbound出站处理器了。把addAfter换成addBefore就可以了,代码如下:
上面说的是客户端出站,从客户端应用程序往网络中冲刷数据,那如果是客户端入站呢?也就是从网络中往客户端应用程序流入数据,这个时候还可以用上面的思想理解吗?这个时候clienthandlerInbound处理器就不是起点了,而是终点。
具体的思想逻辑就是,客户端业务处理的那个ChannelHandler,就是接收服务端传送的实际的数据的那个ChannelHandler不是起点就是终点。那么起点是谁呢?对于客户端应用程序往网络中冲刷数据,起点就是clientHandlerInbound,然后从责任链中的这个处理器往左以此调用执行其他的出站处理器;那么现在问题来了,对于入站的顺序呢?对于入站的起点处理器是谁?对于入站的顺序是从责任链的头部处理器,也就是从责任链的左边的第一个处理器开始,以此向右执行所有的入站处理器,并不一定走到责任链的最右边,也就是责任链的尾端,因为有个终点处理器,就是我们责任链最重要的思想还是处理逻辑,所以我们的最终处理器就是业务逻辑处理器,也就是最终接收到客户端实际数据的那个处理器,这里是clientHandlerInbound如下图:
就算没有执行到最终处理器,入站的时候,可能到不了终点业务处理器也会执行完毕,因为可能执行到责任链中间的某个处理器的时候,这个处理器不让往下继续执行了,就是不会把请求处理传送给下一个处理器了,如下图:
总结一下其实就是,看一下我们之前的责任链结构 如下图:
现在我们想要加一个新的入站处理器newInbound,假如我们加在了clienthandlerInbound这个最终的入站处理器的右边,那么新加的入站处理器newInbound后面就不会被执行到了;但是如果我们把newInbound放到clientHandlerInbound终点入站处理器的前面的话,它就可以被执行到了。
执行入站操作的起点是责任链头部及往右的第一个入站处理器,这里因为第一个入站处理器是decoderInbound,因此执行入站操作的时候,执行的第一个入站处理器就是decoderInbound。
详细代码参考码云:https://gitee.com/xuanyuanzy/netty.git