点击量:6549
最近在研究和学习Netty,按照书上的教程写了一个很简单的demo,但是运行却报错了,代码如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 |
package com.fan3cn.netty; import java.net.InetSocketAddress; import io.netty.bootstrap.ServerBootstrap; import io.netty.buffer.ByteBuf; import io.netty.channel.ChannelFuture; import io.netty.channel.ChannelFutureListener; import io.netty.channel.ChannelHandlerContext; import io.netty.channel.SimpleChannelInboundHandler; import io.netty.channel.nio.NioEventLoopGroup; import io.netty.channel.socket.nio.NioServerSocketChannel; public class TestDemo { //端口号 private static int port = 7777; public static void main(String[] args) { // TODO Auto-generated method stub TestDemo testDemo = new TestDemo(); testDemo.startup(); } public void startup(){ ServerBootstrap sb = new ServerBootstrap(); sb.group(new NioEventLoopGroup(), new NioEventLoopGroup()); sb.channel(NioServerSocketChannel.class); sb.childHandler(new MySimpleChannelInboundHandler()); ChannelFuture future = sb.bind(new InetSocketAddress(port)); future.addListener(new ChannelFutureListener() { @Override public void operationComplete(ChannelFuture f) throws Exception { // TODO Auto-generated method stub if(f.isSuccess()){ System.out.println("bind success,listening on "+port); }else{ System.out.println("bind failed"); } } }); } /** * handler * @author Eric * */ public class MySimpleChannelInboundHandler extends SimpleChannelInboundHandler<ByteBuf>{ @Override protected void channelRead0(ChannelHandlerContext ctx, ByteBuf data) throws Exception { // TODO Auto-generated method stub int readIdx = data.readableBytes(); byte[] bytes = data.readBytes(readIdx).array(); String msg = new String(bytes); System.out.println("recevied data:\n"+msg); } } } |
启动好了之后,浏览器输入:http://127.0.0.1:7777?x=1&y=2 ,但是令人奇怪的是这个服务器只能接受一次请求,后面的请求均会产生如下的报错:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 |
Oct 24, 2016 9:52:41 PM io.netty.channel.DefaultChannelPipeline$TailHandler exceptionCaught WARNING: 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.fan3cn.netty.TestDemo$MySimpleChannelInboundHandler 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 io.netty.bootstrap.ServerBootstrap$ServerBootstrapAcceptor.channelRead(ServerBootstrap.java:232) at io.netty.channel.DefaultChannelHandlerContext.invokeChannelRead(DefaultChannelHandlerContext.java:372) at io.netty.channel.DefaultChannelHandlerContext.fireChannelRead(DefaultChannelHandlerContext.java:357) at io.netty.channel.DefaultChannelPipeline.fireChannelRead(DefaultChannelPipeline.java:780) at io.netty.channel.nio.AbstractNioMessageChannel$NioMessageUnsafe.read(AbstractNioMessageChannel.java:87) at io.netty.channel.nio.NioEventLoop.processSelectedKey(NioEventLoop.java:497) at io.netty.channel.nio.NioEventLoop.processSelectedKeysOptimized(NioEventLoop.java:465) at io.netty.channel.nio.NioEventLoop.run(NioEventLoop.java:359) at io.netty.util.concurrent.SingleThreadEventExecutor$2.run(SingleThreadEventExecutor.java:101) at java.lang.Thread.run(Thread.java:745) |
按照报错的提示,原因就是这个channelHandler没有使用@Shareable注解,加上之后果然问题解决了。但是到这里我的疑问并没有得到解决,为什么这个handler必须要加上@Shareable注解?这一步步到底是怎么回事?经过几次断点,再结合阅读源码,总算是搞清楚为什么了。
首先来看抛出异常的方法DefaultChannelPipeline->checkMultiplicity,代码如下:
1 2 3 4 5 6 7 8 9 10 11 12 |
private static void checkMultiplicity(ChannelHandlerContext ctx) { ChannelHandler handler = ctx.handler(); if (handler instanceof ChannelHandlerAdapter) { ChannelHandlerAdapter h = (ChannelHandlerAdapter) handler; if (!h.isSharable() && h.added) { throw new ChannelPipelineException( h.getClass().getName() + " is not a @Sharable handler, so can't be added or removed multiple times."); } h.added = true; } } |
这段代码很简单,如果这个handler被加过了,也就是h.adder=true,并且不是shareable的,就会抛出异常。但是为很么会重复加呢?
再来看ServerBootstrap.java 第232行:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 |
@Override @SuppressWarnings("unchecked") public void channelRead(ChannelHandlerContext ctx, Object msg) { Channel child = (Channel) msg; child.pipeline().addLast(childHandler);//源代码232行 for (Entry<ChannelOption<?>, Object> e: childOptions) { try { if (!child.config().setOption((ChannelOption<Object>) e.getKey(), e.getValue())) { logger.warn("Unknown channel option: " + e); } } catch (Throwable t) { logger.warn("Failed to set a channel option: " + child, t); } } for (Entry<AttributeKey<?>, Object> e: childAttrs) { child.attr((AttributeKey<Object>) e.getKey()).set(e.getValue()); } try { childGroup.register(child); } catch (Throwable t) { child.unsafe().closeForcibly(); logger.warn("Failed to register an accepted channel: " + child, t); } } |
我们可以发现每当有新的数据可读时都会往这个channel的pipeline里加入handler,这里加的是childHander。值得注意的是,我们初始化的时候这个childHandler都是同一个实例,也就说会导致不同的channel用了同一个handler,这个从netty的设计角度来说是要避免的。因为netty的一大好处就是每一个channel都有自己绑定的eventloop和channelHandler,这样可以保证代码串行执行,不必考虑并发同步的问题。所以才会有checkMultiplicity这个方法来检查这个问题。那该怎么办呢?netty的这段代码:child.pipeline().addLast(childHandler)就是用了同一个handler啊,怎么才能为每一个channel创建不同的handler呢?
很简单,只要写个类继承ChannelInitializer就行了,ChannelInitializer这个类比较特殊,你可以把它想象成是很多channelhandler的集合体,而且这个类就是@Shareable的,继承了这个类之后你可以为每一个channel单独创建handler,甚至是多个handler。
1 2 3 4 5 6 7 8 9 10 11 |
final class ChannelInitializerImpl extends ChannelInitializer<Channel> { @Override protected void initChannel(Channel ch) throws Exception { ChannelPipeline pipeline = ch.pipeline(); pipeline.addLast(new MySimpleChannelInboundHandler()); //pipeline.addLast(new InboundHandler1()); //pipeline.addLast(new InboundHandler2()); //... //... } } |
和之前那个handler一样,虽然所有的channel都共享了ChannelInitializerImpl这个实例,但是这个实例却能为每一个channel new出新的handler实例,这就是区别。
Pingback引用通告: Java IO学习笔记八:Netty入门 - 算法网