netty(十九)ChannelInitializer 使用公共handler(@Shareable)实践及逻辑解答【重点】
在 netty channel的线程安全性与@Sharable 中讨论了ChannelInitializer,Pipeline,@Shareable,本质就2点:
1)ChannelInitializer可以实现每个连接创建一个pipeline,而且pipeline内的handler,每个连接都能有个新的sethandler
这个地方会有过误解:其实不是你用了ChannelInitializer,就是每个连接都有一个新的handler了,你也可以在ChannelInitializer中设置一个公共对象(@Shareable修饰,意为:可共享的)
当然对于这样一个对象,书上说的不全准确,比起对象的线程安全性,更重要的是你得确定它是关于连接无状态的,比如StringDecoder,不能是LengthDecoder这样的
2)channel属于一个线程,ChannelPipeline属于一个channel,所以对ChannelPipeline的操作始终在一个线程内,可以随意remove add而不用考虑同一时刻,有另外一个线程在操作pipeline,因为对一个channel的操作,netty承诺始终在一个线程中
对第2)点,有一个案例 netty tcp(ws)鉴权2个方案
本文对第1)点,尝试一个案例,在netty粘包(一)消息定长 实践的代码基础上,使ChannelInitializer中的handler为同一对象
一 多个handler
ChannelHandler [] channelHandlers = { new FixedLengthFrameDecoder(14), new StringDecoder(), new StringEncoder(), new ServerHandler4(), new ServerHandler5() }; //设置管道工厂 bootstrap.childHandler(new ChannelInitializer<SocketChannel>() { // 多个handler @Override protected void initChannel(SocketChannel socketChannel) throws Exception { //获取管道 ChannelPipeline pipeline = socketChannel.pipeline(); //定长解码类 pipeline.addLast(channelHandlers[0]); //字符串解码类 pipeline.addLast(channelHandlers[1]); pipeline.addLast(channelHandlers[2]); //处理类 pipeline.addLast(channelHandlers[3]); pipeline.addLast(channelHandlers[4]); } });
二 单个handler
// 单个handler bootstrap.childHandler(new SimpleChannelInboundHandler<ByteBuf>() { @Override protected void channelRead0(ChannelHandlerContext ctx, ByteBuf byteBuf) throws Exception { System.out.println("Received data"); } });
第一个client连接
a 单个handler输出
server start ......
Received data
与预期一致,单handler服务端沾包了,只打印了一条
b 多个handler按5条正常输出
当第二个client连接时
一
十二月 17, 2019 9:34:19 下午 io.netty.channel.ChannelInitializer channelRegistered
警告: Failed to initialize a channel. Closing: [id: 0x08956454, /127.0.0.1:58422 => /127.0.0.1:8866]
io.netty.channel.ChannelPipelineException: io.netty.handler.codec.FixedLengthFrameDecoder is not a @Sharable handler, so can't be added or removed multiple times.
at io.netty.channel.DefaultChannelPipeline.checkMultiplicity(DefaultChannelPipeline.java:461)
at io.netty.channel.DefaultChannelPipeline.addLast0(DefaultChannelPipeline.java:138)
at io.netty.channel.DefaultChannelPipeline.addLast(DefaultChannelPipeline.java:131)
at io.netty.channel.DefaultChannelPipeline.addLast(DefaultChannelPipeline.java:258)
at io.netty.channel.DefaultChannelPipeline.addLast(DefaultChannelPipeline.java:245)
at com.jds.test.stringlength.Server5$1.initChannel(Server5.java:47)
-=======-
二
十二月 17, 2019 10:10:26 下午 io.netty.channel.DefaultChannelPipeline$TailHandler exceptionCaught
警告: An exceptionCaught() event was fired, and it reached at the tail of the pipeline. It usually means the last handler in the pipeline did not handle the exception.
io.netty.channel.ChannelPipelineException: com.jds.test.stringlength.Server6$1 is not a @Sharable handler, so can't be added or removed multiple times.
io.netty.handler.codec.FixedLengthFrameDecoder这是一个连接相关的handler,是不能共用的,每个连接应保持内部读取的字节状态以处理沾包,它不像StringDecoder和StringEncoder,连接无关,输入参byte数组,输出字符串,可以共用,事实上netty也将这两个ChannelHandlerAdapter声明为@Shareable