一条Netty通道需要很多业务处理器来处理业务。每条通道内部都有一条流水线(Pipeline)将Handler装配起来。Netty的业务处理器流水线ChannelPipeline是基于责任链设计模式(Chain of Responsibility)来设计的,内部是一个双向链表结构,能够支持动态地添加和删除业务处理器。
1 Pipeline入站处理流程
import io.netty.buffer.ByteBuf;
import io.netty.buffer.Unpooled;
import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.ChannelInboundHandlerAdapter;
import io.netty.channel.ChannelInitializer;
import io.netty.channel.embedded.EmbeddedChannel;
import org.junit.Test;
import java.util.concurrent.TimeUnit;
/**
* @author wyaoyao
* @date 2021/8/13 9:46
*/
public class InPipeline {
// 入栈处理器
static class SimpleInHandlerA extends ChannelInboundHandlerAdapter {
@Override
public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
System.out.println("SimpleInHandler A 被回调");
super.channelRead(ctx, msg);
}
}
static class SimpleInHandlerB extends ChannelInboundHandlerAdapter {
@Override
public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
System.out.println("SimpleInHandler B 被回调");
super.channelRead(ctx, msg);
}
}
static class SimpleInHandlerC extends ChannelInboundHandlerAdapter {
@Override
public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
System.out.println("SimpleInHandler C 被回调");
super.channelRead(ctx, msg);
}
}
@Test
public void test1() {
// 构建 channel初始化器
ChannelInitializer<EmbeddedChannel> channelInitializer = new ChannelInitializer<EmbeddedChannel>() {
@Override
protected void initChannel(EmbeddedChannel ch) throws Exception {
// 添加处理器
ch.pipeline().addLast(new SimpleInHandlerA());
ch.pipeline().addLast(new SimpleInHandlerB());
ch.pipeline().addLast(new SimpleInHandlerC());
}
};
// 构建EmbeddedChannel,传入channel初始化器
EmbeddedChannel channel = new EmbeddedChannel(channelInitializer);
ByteBuf buf = Unpooled.buffer();
buf.writeInt(1);
//向通道写一个入站报文
channel.writeInbound(buf);
try {
TimeUnit.SECONDS.sleep(Integer.MAX_VALUE);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
三个内部入站处理器的channelRead()方法中,我们打印当前Handler业务处理器的信息,然后调用父类的channelRead()方法,而父类的channelRead()方法的主要作用是把当前入站处理器中处理完毕的结果传递到下一个入站处理器。
按照添加的顺序,最终输出:
SimpleInHandler A 被回调
SimpleInHandler B 被回调
SimpleInHandler C 被回调
在入站处理器的channelRead()方法中,如果不调用父类的channelRead()方法,结果会如何呢? 显然B,C两个处理器就不会调用了(父类的channelRead()方法的主要作用是把当前入站处理器中处理完毕的结果传递到下一个入站处理器。)
2 出站处理器流程
import io.netty.buffer.ByteBuf;
import io.netty.buffer.Unpooled;
import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.ChannelInitializer;
import io.netty.channel.ChannelOutboundHandlerAdapter;
import io.netty.channel.ChannelPromise;
import io.netty.channel.embedded.EmbeddedChannel;
import org.junit.Test;
import java.util.concurrent.TimeUnit;
public class OutPipeline {
static class SimpleOutHandlerA extends ChannelOutboundHandlerAdapter {
@Override
public void write(ChannelHandlerContext ctx, Object msg, ChannelPromise promise) throws Exception {
System.out.println("SimpleOutHandler A 被回调");
super.write(ctx, msg, promise);
}
}
static class SimpleOutHandlerB extends ChannelOutboundHandlerAdapter {
@Override
public void write(ChannelHandlerContext ctx, Object msg, ChannelPromise promise) throws Exception {
System.out.println("SimpleOutHandler B 被回调");
super.write(ctx, msg, promise);
}
}
static class SimpleOutHandlerC extends ChannelOutboundHandlerAdapter {
@Override
public void write(ChannelHandlerContext ctx, Object msg, ChannelPromise promise) throws Exception {
System.out.println("SimpleOutHandler C 被回调");
super.write(ctx, msg, promise);
}
}
@Test
public void test1() {
// 构建 channel初始化器
ChannelInitializer<EmbeddedChannel> channelInitializer = new ChannelInitializer<EmbeddedChannel>() {
@Override
protected void initChannel(EmbeddedChannel ch) throws Exception {
// 添加处理器
ch.pipeline().addLast(new SimpleOutHandlerA());
ch.pipeline().addLast(new SimpleOutHandlerB());
ch.pipeline().addLast(new SimpleOutHandlerC());
}
};
// 构建EmbeddedChannel,传入channel初始化器
EmbeddedChannel channel = new EmbeddedChannel(channelInitializer);
ByteBuf buf = Unpooled.buffer();
buf.writeInt(1);
//向通道写一个出站报文
channel.writeOutbound(buf);
try {
TimeUnit.SECONDS.sleep(Integer.MAX_VALUE);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
在以上出站处理器的write()方法中,打印当前Handler业务处理器的信息,然后调用父类的write()方法,而这里父类的write()方法会将出站数据通过通道流水线发送到下一个出站处理器。运行上面的案例程序,控制台的输出如下:
SimpleOutHandler C 被回调
SimpleOutHandler B 被回调
SimpleOutHandler A 被回调
在代码中,通过pipeline. addLast()方法添加OutBoundHandler出站处理器的顺序为A→B→C。从结果可以看出,出站流水处理次序为从后向前(C→B→A)
3 ChannelHandlerContext
在Netty的设计中Handler是无状态的,不保存和Channel有关的信息。Handler的目标是将自己的处理逻辑做得很通用,可以给不同的Channel使用。与Handler不同的是,Pipeline是有状态的,保存了Channel的关系。于是,Handler和Pipeline之间需要一个中间角色将它们联系起来: ChannelHandlerContext(通道处理器上下文)!
当业务处理器被添加到流水线中时会为其专门创建一个ChannelHandlerContext实例,主要封装了ChannelHandler(通道处理器)和ChannelPipeline(通道流水线)之间的关联关系。所以,流水线ChannelPipeline中的双向链接实质是一个由ChannelHandlerContext组成的双向链表。作为Context的成员,无状态的Handler关联在ChannelHandlerContext中。
ChannelHandlerContext中包含了许多方法,主要可以分为两类:第一类是获取上下文所关联的Netty组件实例,如所关联的通道、所关联的流水线、上下文内部Handler业务处理器实例等;第二类是入站和出站处理方法。
在Channel、ChannelPipeline、ChannelHandlerContext三个类中,都存在同样的出站和入站处理方法,这些出现在不同的类中的相同方法,功能有何不同呢?
如果通过Channel或ChannelPipeline的实例来调用这些出站和入站处理方法,它们就会在整条流水线中传播。如果是通过ChannelHandlerContext调用出站和入站处理方法,就只会从当前的节点开始往同类型的下一站处理器传播,而不是在整条流水线从头至尾进行完整的传播。
总结: Channel拥有一条ChannelPipeline,每一个流水线节点为一个ChannelHandlerContext上下文对象,每一个上下文中包裹了一个ChannelHandler。在ChannelHandler的入站/出站处理方法中,Netty会传递一个Context实例作为实际参数。处理器中的回调代码可以通过Context实参,在业务处理过程中去获取ChannelPipeline实例或者Channel实例。
4 HeadContext与TailContext
通道流水线在没有加入任何处理器之前装配了两个默认的处理器上下文:一个头部上下文HeadContext,一个尾部上下文TailContext。pipeline的创建、初始化除了保存一些必要的属性外,核心就在于创建了HeadContext头节点和TailContext尾节点。
每个流水线中双向链表结构从一开始就存在了HeadContext和TailContext两个节点,后面添加的处理器上下文节点都添加在HeadContext实例和TailContext实例之间
流水线尾部的TailContext不仅仅是一个上下文类,还是一个入站处理器类,实现了所有入站处理回调方法,这些回调实现的主要工作基本上都是有关收尾处理的,如释放缓冲区对象、完成异常处理等。
TailContext是流水线默认实现类DefaultChannelPipeline的一个内部类
流水线头部的HeadContext比TailContext复杂得多,既是一个出站处理器,也是一个入站处理器,还保存了一个unsafe(完成实际通道传输的类)实例,也就是HeadContext还需要负责最终的通道传输工作。
HeadContext也是流水线默认实现类DefaultChannelPipeline的一个内部类,