最近在看《Netty实战》这本书,看到书中讲解ChannelHandlerContext、Channel、ChannelPipeline的write()的不同时有点懵逼,没大看懂,遂动手写了一个Demo来验证到底是怎么回事。
服务启动类OneServer:
package com.ccf.netty_learn.server;
import io.netty.bootstrap.ServerBootstrap;
import io.netty.channel.ChannelInitializer;
import io.netty.channel.ChannelPipeline;
import io.netty.channel.EventLoopGroup;
import io.netty.channel.nio.NioEventLoopGroup;
import io.netty.channel.socket.SocketChannel;
import io.netty.channel.socket.nio.NioServerSocketChannel;
import io.netty.handler.codec.string.StringDecoder;
import io.netty.handler.codec.string.StringEncoder;
import io.netty.handler.logging.LogLevel;
import io.netty.handler.logging.LoggingHandler;
public class OneServer {
public static void main(String[] args) throws Exception{
EventLoopGroup bossGroup = new NioEventLoopGroup(1);
EventLoopGroup workGroup = new NioEventLoopGroup();
try {
ServerBootstrap b = new ServerBootstrap();
b.group(bossGroup, workGroup)
.channel(NioServerSocketChannel.class)
.handler(new LoggingHandler(LogLevel.INFO))
.childHandler(new ChannelInitializer() {
@Override
protected void initChannel(SocketChannel ch) throws Exception {
ChannelPipeline cp = ch.pipeline();
cp.addLast(new StringDecoder());
cp.addLast(new OneServerOutHandler());
cp.addLast(new StringEncoder());
cp.addLast(new OneServerInHandler());
}
});
b.bind(8888).sync().channel().closeFuture().sync();
}finally {
bossGroup.shutdownGracefully();
workGroup.shutdownGracefully();
}
}
}
入站Handler:
package com.ccf.netty_learn.server;
import io.netty.buffer.Unpooled;
import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.SimpleChannelInboundHandler;
public class OneServerInHandler extends SimpleChannelInboundHandler{
@Override
protected void channelRead0(ChannelHandlerContext ctx, String msg) throws Exception {
System.out.println("OneServerInHandler:"+msg);
ctx.write(Unpooled.copiedBuffer(msg.getBytes()));
ctx.flush();
// ctx.channel().write(Unpooled.copiedBuffer(msg.getBytes()));
// ctx.channel().flush();
}
}
出站Handler:
package com.ccf.netty_learn.server;
import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.ChannelOutboundHandlerAdapter;
import io.netty.channel.ChannelPromise;
public class OneServerOutHandler extends ChannelOutboundHandlerAdapter{
@Override
public void write(ChannelHandlerContext ctx, Object msg, ChannelPromise promise) throws Exception {
System.out.println("OneServerOutHandler-write()");
super.write(ctx, msg, promise);
}
}
客户端启动类OneClient:
package com.ccf.netty_learn.client;
import io.netty.bootstrap.Bootstrap;
import io.netty.channel.ChannelInitializer;
import io.netty.channel.ChannelPipeline;
import io.netty.channel.EventLoopGroup;
import io.netty.channel.nio.NioEventLoopGroup;
import io.netty.channel.socket.SocketChannel;
import io.netty.channel.socket.nio.NioSocketChannel;
import io.netty.handler.codec.string.StringDecoder;
import io.netty.handler.codec.string.StringEncoder;
public class OneClient {
public static void main(String[] args) throws Exception{
EventLoopGroup group = new NioEventLoopGroup();
try {
Bootstrap b = new Bootstrap();
b.group(group)
.channel(NioSocketChannel.class)
.handler(new ChannelInitializer() {
@Override
protected void initChannel(SocketChannel ch) throws Exception {
ChannelPipeline cp = ch.pipeline();
cp.addLast(new StringDecoder());
cp.addLast(new StringEncoder());
cp.addLast(new OneClientHandler());
}
});
b.connect("localhost", 8888).sync().channel().closeFuture().sync();
}finally {
group.shutdownGracefully();
}
}
}
客户端Handler:
package com.ccf.netty_learn.client;
import io.netty.buffer.Unpooled;
import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.SimpleChannelInboundHandler;
public class OneClientHandler extends SimpleChannelInboundHandler{
@Override
protected void channelRead0(ChannelHandlerContext ctx, String msg) throws Exception {
System.out.println("OneClientHandler:"+msg);
}
@Override
public void channelActive(ChannelHandlerContext ctx) throws Exception {
ctx.writeAndFlush(Unpooled.copiedBuffer("Hello".getBytes()));
}
}
一、当服务端的Handler链的顺序如下,既自定义出站Handler在入站Handler之后时,
ChannelPipeline cp = ch.pipeline();
cp.addLast(new StringDecoder());
cp.addLast(new OneServerInHandler());
cp.addLast(new OneServerOutHandler());
cp.addLast(new StringEncoder());
在入站Handler中调用ChannelHandlerContext的write()方法结果如下:
八月 21, 2019 11:24:45 上午 io.netty.handler.logging.LoggingHandler channelRegistered
信息: [id: 0x95fb994b] REGISTERED
八月 21, 2019 11:24:45 上午 io.netty.handler.logging.LoggingHandler bind
信息: [id: 0x95fb994b] BIND: 0.0.0.0/0.0.0.0:8888
八月 21, 2019 11:24:45 上午 io.netty.handler.logging.LoggingHandler channelActive
信息: [id: 0x95fb994b, L:/0:0:0:0:0:0:0:0:8888] ACTIVE
八月 21, 2019 11:25:12 上午 io.netty.handler.logging.LoggingHandler channelRead
信息: [id: 0x95fb994b, L:/0:0:0:0:0:0:0:0:8888] RECEIVED: [id: 0xdaf189ea, L:/127.0.0.1:8888 - R:/127.0.0.1:18270]
OneServerInHandler:Hello
说明并没有调用到自定义的出站Handler中的write()方法。
此时如果在入站Handler中改成调用Channel或者ChannelPipeline的write()方法,如下
System.out.println("OneServerInHandler:"+msg);
// ctx.write(Unpooled.copiedBuffer(msg.getBytes()));
// ctx.flush();
ctx.channel().write(Unpooled.copiedBuffer(msg.getBytes()));
ctx.channel().flush();
结果是:
八月 21, 2019 11:28:20 上午 io.netty.handler.logging.LoggingHandler channelRegistered
信息: [id: 0x111c797e] REGISTERED
八月 21, 2019 11:28:20 上午 io.netty.handler.logging.LoggingHandler bind
信息: [id: 0x111c797e] BIND: 0.0.0.0/0.0.0.0:8888
八月 21, 2019 11:28:20 上午 io.netty.handler.logging.LoggingHandler channelActive
信息: [id: 0x111c797e, L:/0:0:0:0:0:0:0:0:8888] ACTIVE
八月 21, 2019 11:28:34 上午 io.netty.handler.logging.LoggingHandler channelRead
信息: [id: 0x111c797e, L:/0:0:0:0:0:0:0:0:8888] RECEIVED: [id: 0xa1c88958, L:/127.0.0.1:8888 - R:/127.0.0.1:18371]
OneServerInHandler:Hello
OneServerOutHandler-write()
通过上面的演示确实能证明ChannelHandlerContext、Channel、ChannelPipeline的write()方法底层调用链是不同的,那么到底是什么不同呢,接下来分别进入它们的源码中如一探究竟。
二、源码探索
1、深入到ChannelHandlerContext的write()方法,直到调用到AbstractChannelHandlerContext的如下方法
private void write(Object msg, boolean flush, ChannelPromise promise) {
AbstractChannelHandlerContext next = findContextOutbound();
final Object m = pipeline.touch(msg, next);
EventExecutor executor = next.executor();
if (executor.inEventLoop()) {
if (flush) {
next.invokeWriteAndFlush(m, promise);
} else {
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);
}
}
上面方法的第一行AbstractChannelHandlerContext next = findContextOutbound();很明显这里是在查找下一个AbstractChannelHandlerContext,那么我们继续深入看看到底是怎么查找的下一个AbstractChannelHandlerContext
private AbstractChannelHandlerContext findContextOutbound() {
AbstractChannelHandlerContext ctx = this;
do {
ctx = ctx.prev;
} while (!ctx.outbound);
return ctx;
}
这就很明显了,这里是倒退的方式去查询的第一个出站Handler,但是我们这里的自定义出站Handler在入站Handler的后面,因此肯定就不会调用到它的write()方法了。
2、接着深入到Channel的write()方法看看,进入到AbstractChannel,发现其直接调用的ChannelPipeline的write()方法
@Override
public ChannelFuture write(Object msg) {
return pipeline.write(msg);
}
进入DefaultChannelPipeline,发现调用了tail的write(),tail是ChannelPipeline的尾结点,它既是一个入站Handler又是一个Handler,接下去又调用到AbstractChannelHandlerContext中去了,也就是回到了上面的情况中,这就表明从尾结点tail开始往回找到所有的出站Handler直到头结点head去执行它们的所有的write()方法。
@Override
public final ChannelFuture write(Object msg) {
return tail.write(msg);
}
总结:我们需要理解ChannelPipeline中的Handler链,才能完成理解其中入站出站的调用关系,入站是从head到tail,出站则相反从tail到head,随便提一句,异常是不会管是出站还是入站的都从下一个(next)开始,因此一般来说异常处理Handler都加到ChannelPipeline的tail节点前一个节点即可(即自定义Handler的最后一个),从上面的演示来看ChannelHandlerContext的write()方法是从当前节点的上(prev)一个出站节点开始执行,只经过了部分链路,而Channel和ChannelPipeline的write()方法是从尾结点开始往后执行的,经过了全部链路,这就是区别。