刚学netty,一些细节上还不是很清楚,发现踩了很多坑。一点一点记录下来。
先看一个小demo
bootstrap.group(bossGroup,workerGroup)
.channel(NioServerSocketChannel.class)
.localAddress(new InetSocketAddress(port))
.childHandler(new ChannelInitializer() {
@Override
protected void initChannel(SocketChannel ch) throws Exception {
//重点看这里,先添加EchoServerOutHandler
//再添加EchoServerInHandler
ch.pipeline().addLast(outHandler);
ch.pipeline().addLast(inHandler);
}
});
//这里是EchoServerOutHandler 的详细代码
public class EchoServerOutHandler extends ChannelOutboundHandlerAdapter {
@Override
public void write(ChannelHandlerContext ctx, Object msg, ChannelPromise promise) throws Exception {
System.out.println("來到了Out Handler");
super.write(ctx, msg, promise);
super.flush(ctx);
}
}
//这里是EchoServerInHandler的详细代码
public class EchoServerInHandler extends SimpleChannelInboundHandler<ByteBuf> {
@Override
protected void channelRead0(ChannelHandlerContext ctx, ByteBuf msg) throws Exception {
ByteBuf in = (ByteBuf) msg;
System.out.println("server received:" + in.toString(CharsetUtil.UTF_8));
ctx.write(Unpooled.wrappedBuffer(ByteBufUtil.decodeHexDump("aa")));
//ctx.channel().write(Unpooled.wrappedBuffer(ByteBufUtil.decodeHexDump("aa")));
}
}
1、先简单介绍下ChannelPipeline,ChannelPipeline将Handler封装为ChannelHandlerContext再按添加的顺序组合为一条双向链表,通过一个标志位来区别是Inbound还是outbound。
上面例子的handler顺序为
2、下面通过跟踪源码查看ctx.write()执行过程
第一步
protected void channelRead0(ChannelHandlerContext ctx, ByteBuf msg) throws Exception {
ByteBuf in = (ByteBuf) msg;
System.out.println("server received:" + in.toString(CharsetUtil.UTF_8));
//执行ctx的write方法
ctx.write(Unpooled.wrappedBuffer(ByteBufUtil.decodeHexDump("aa")));
//ctx.channel().write(Unpooled.wrappedBuffer(ByteBufUtil.decodeHexDump("aa")));
}
第二步
private void write(Object msg, boolean flush, ChannelPromise promise) {
//找到第一个处理该write方法的handler,具体细节看第三步
AbstractChannelHandlerContext next = findContextOutbound();
final Object m = pipeline.touch(msg, next);
EventExecutor executor = next.executor();
if (executor.inEventLoop()) {
if (flush) {
next.invokeWriteAndFlush(m, promise);
} else {
//调用获取到的outbound的handler的write方法
next.invokeWrite(m, promise);
}
} else {
AbstractWriteTask task;
if (flush) {
task = WriteAndFlushTask.newInstance(next, m, promise);
} else {
task = WriteTask.newInstance(next, m, promise);
}
safeExecute(executor, task, promise, m);
}
}
第三步
//从当前handler开始往前移动指针,找到第一个outbound的handler。这里当前的handler就是EchoServerOutHandler 了
private AbstractChannelHandlerContext findContextOutbound() {
AbstractChannelHandlerContext ctx = this;
do {
ctx = ctx.prev;
} while (!ctx.outbound);
return ctx;
}
第四步
//最后就会执行到EchoServerOutHandler 的write方法了。查看控制台输出了“來到了Out Handler”
@Override
public void write(ChannelHandlerContext ctx, Object msg, ChannelPromise promise) throws Exception {
System.out.println("來到了Out Handler");
super.write(ctx, msg, promise);
super.flush(ctx);
}
总结:所以如果我们是调用ctx.write()方法的话,会从当前的hander往前找第一个outbound来执行。记住一定要将OutBoundHandler先添加进ChannelPipeline。否则会跟期望的结果不一致。
我们可以试下将添加顺序换下,控制台不会打印“來到了Out Handler”。
3、接着继续跟踪源码查看channel().write()的执行过程
第一步
//执行ctx.channel().write()方法
@Override
protected void channelRead0(ChannelHandlerContext ctx, ByteBuf msg) throws Exception {
ByteBuf in = (ByteBuf) msg;
System.out.println("server received:" + in.toString(CharsetUtil.UTF_8));
//ctx.write(Unpooled.wrappedBuffer(ByteBufUtil.decodeHexDump("aa")));
ctx.channel().write(Unpooled.wrappedBuffer(ByteBufUtil.decodeHexDump("aa")));
}
第二步
//将write方法委托给pipeline执行
@Override
public ChannelFuture write(Object msg) {
return pipeline.write(msg);
}
第三步
//这里就是区别的地方了,pipeline直接选择最后一个handler来执行write。
//所以这里先从tail开始找第一个是OutBound的handler。
@Override
public final ChannelFuture write(Object msg) {
return tail.write(msg);
}
总结:如果我们是使用channel().write()方法。可以不用考虑outbound和inbound的添加顺序。每次都会从tail往前找第一个是outbound的handler来执行。
我们可以试下无论如何修改添加顺序,控制台都会打印“來到了Out Handler”。
这里考虑一个问题,如果我们在outboundhandler调用channel().write()方法会出现什么问题?
死循环吗?试下就会知道了。