概述
Netty中ChannelPipeline与Channel的对应关系是一一对应,也就是每个Channel中有且仅有一个ChannelPipeline,可以通过Channel获取唯一的ChannelPipeline,也可以通过ChannelPipeline获取唯一的Channel。
由上图可得,每个Channel维护了一个ChannelPipeline,而ChannelPipeline中又维护了一个由ChannelHandlerContext组成的双向链表,这个链表的表头是HeadContext,链表的末尾是TailContext,并且每个ChannelHanderContext中又关联了一个ChannelHandler。
HeadContext和TailContext并没有包含ChannelHandler,那是因为HeadContext和TailContext继承了AbstractChannelHandlerContext同时实现了ChannelHandler接口,所以它们有了Context和Handler双重属性。 就是HeadContext和TailContext以继承的形式实现这双重属性。
ChannelInitializer的添加
从上面我们知道,最开始的时候ChannelPipeline含有两个ChannelHandlerContext(HeadContext、TailContext),但是这个Pipeline不能实现什么特殊功能,因为我们还没有给他添加自定义的ChannelHandler,通常来说,我们在初始化Bootstrap时,会添加我们自定义的ChannelHandler。
实际上添加到Pipeline里面的是ChannelHandlerContext,只是每个对象维护了一个ChannelHandler。
Bootstrap bootstrap = new Bootstrap();
bootstrap.group(group)
channel(NioSocketChannel.class)
.option(ChannelOption.SO_KEEPALIVE, true)
.handler(new ChannelInitializer<SocketChannel>() {
@Override
protected void initChannel(SocketChannel ch) throws Exception {
ChannelPipeline pipeline = ch.pipeline();
//这里添加自定义的。
pipeline.addLast(new ChatClientHandler(nickName));
}
});
在调用handler方法时,传入了ChannelInitializer对象,它提供了一个initChannel方法给我们进行重写,来进行初始化ChannelHandler,addLast表示在pipeline中的链表的TailContext之前添加一个ChannelHandler,用于自定义的功能实现。
这个是ChannelInitializer的类图,可以看出,他仅实现了ChannelInboundHandler接口。
上图是DefaultHandlerContext类的内容,可以看到有两个方法isInbound和isOutbound方法,这两个方法的逻辑是:当一个 handler 实现了 ChannelInboundHandler 接口,则 isInbound 返回 true;类似地,当一个handler 实现了 ChannelOutboundHandler 接口,则 isOutbound 就返回 true。而这两个 boolean 变量会传递到父类AbstractChannelHandlerContext 中,并初始化父类的两个字段:inbound 与 outbound。
并根据上面的ChannelInitializer的类关系图可得,ChannelInitializer的Inbound属性是true,Outbound属性是false。
addLast源码:
private void addLast0(AbstractChannelHandlerContext newCtx) {
//定位到TailContext的上一个Context,如果是第一次添加,那就是HeadContext
AbstractChannelHandlerContext prev = tail.prev;
//假如是第一次添加,那么把要加入的ChannelContext的前置节点赋值为HeadContext
newCtx.prev = prev;
//把要加入的ChannelContext的后置节点赋值为TailContext
newCtx.next = tail;
//如果是第一次添加,把HeadContext的后置节点赋值为新节点
prev.next = newCtx;
//把Tail节点的前置节点赋值为新结点。
tail.prev = newCtx;
//总的来说就是新加入的ChannelContext会在尾部加入,尾部指的是TailContext之前的节点,因为Pipeline的HeadContext和TailCOntext的位置总是不变的,一个在表头,一个在表尾。
}
自定义Handler的添加过程:按照上面代码,把注释去掉。
- 通过hand方法定义一个ChannelInitializer Handler到Pipeline中。此时的图如下:
- 然后执行ChannelInitializer的initChannel方法,添加自定义的ChannelHandler,添加一个MyNettyChannelHandler。
就是在TailContext前添加,保证TailContext是最后的节点。 - 如果继续添加以此类推。
- 最后,initChannel方法执行完成添加之后,会在finally代码块里把ChannelInitializer自身删除。
上面方法是ChannelInitializer的方法。
所以最后的双向链表了只有HeadContext 之后是一些在initChannel方法中添加的自定义Channel,最后是TailContext,ChannelInitializer会被删除。
ChannelHandler的命名规则:
ChannelPipeline.addXXX都有一个重载版本:
比如
ChannelPipeline addLast(String name, ChannelHandler handler);
第一个参数指定添加的 handler 的名字(更准确地说是 ChannelHandlerContext 的名字,说成 handler 的名字更便于理解)。
public final ChannelPipeline addLast(String name, ChannelHandler handler) {
return this.addLast((EventExecutorGroup)null, name, handler);
}
public final ChannelPipeline addLast(EventExecutorGroup group, String name, ChannelHandler handler) {
final AbstractChannelHandlerContext newCtx;
synchronized(this) {
//检查
checkMultiplicity(handler);
//会通过这个filterName方法进行handler名字的检查或者生成名字,如果名字重复,就会抛出异常,具体在下面
newCtx = this.newContext(group, this.filterName(name, handler), handler);
this.addLast0(newCtx);
if (!this.registered) {
newCtx.setAddPending();
this.callHandlerCallbackLater(newCtx, true);
return this;
}
EventExecutor executor = newCtx.executor();
if (!executor.inEventLoop()) {
newCtx.setAddPending();
executor.execute(new Runnable() {
public void run() {
DefaultChannelPipeline.this.callHandlerAdded0(newCtx);
}
});
return this;
}
}
this.callHandlerAdded0(newCtx);
return this;
}
private String filterName(String name, ChannelHandler handler) {
if (name == null) {
//如果没有传递handler的名字,就使用默认命名规则
return this.generateName(handler);
} else {
//否则就进行检查。
this.checkDuplicateName(name);
return name;
}
}
//名字重复检查
private void checkDuplicateName(String name) {
if (this.context0(name) != null) {
throw new IllegalArgumentException("Duplicate handler name: " + name);
}
}
//默认的命名规则:默认命名的规则很简单,就是用反射获取 handler 的 simpleName 加上"#0",代码就不解释了,源码在这里。
private String generateName(ChannelHandler handler) {
Map<Class<?>, String> cache = (Map)nameCaches.get();
Class<?> handlerType = handler.getClass();
String name = (String)cache.get(handlerType);
if (name == null) {
name = generateName0(handlerType);
cache.put(handlerType, name);
}
if (this.context0(name) != null) {
String baseName = name.substring(0, name.length() - 1);
int i = 1;
while(true) {
String newName = baseName + i;
if (this.context0(newName) == null) {
name = newName;
break;
}
++i;
}
}
return name;
}
Pipeline的事件传播机制
理解Pipeline的事件传播机制很重要,因为这样我们才能写出符合预期的netty程序。
前面可知,我们已经知道 AbstractChannelHandlerContext 中有 inbound 和 outbound 两个 boolean 变量,分别用于标识 Context 所对应的 handler 的类型。
即:
- inbound 为 true 是,表示其对应的 ChannelHandler 是 ChannelInboundHandler 的子类。
- outbound 为 true 时,表示对应的 ChannelHandler 是 ChannelOutboundHandler 的子类。
上图是官网的一张图,从上图可以看出inbound和outbound的流向是不一样的,inbound通常是客户端到服务器,Outbound是从服务器到客户端。
inbound是从Head到Tail。
Outbound是从Tail到Head。
服务端读取成功客户端传来的数据后,按照pipeline的双向链表从HeadContext往后遍历,找到第一个inbound为true的handler,开始依次向后执行,只执行inbound为true的handler。
Outbound时,从TailContext开始遍历,找到第一个Outbound为true的handler开始依次往前执行,只执行Outbound为true的handler,最后write返回数据给客户端。
所以,为了能够使得编写的Netty程序符合预期,handler的添加顺序很关键。
再次说明:
两个Netty程序进行通信,有客户端和服务端。
以本体为角度而言,数据向本体流进来,就是入站,从本体流出去,就是出站。
就客户端而言:此时客户端是本体,在客户端角度。
入站是客户端读取到了服务端发送过来的数据(从服务端流到客户端(本体)),然后进行一系列InboundHandler操作。这个就是入站。
出站是指入站操作做完,要把数据进行一系列操作(比如编码等),再回写给服务端,数据是从客户端(本体)流到服务端的,属于出站操作。
就服务端而言:此时服务端是本体,在服务端角度:
入站是服务端读取到了客户端发送过来的数据(从客户端流向服务端(本体)),然后进行一系列操作(比如解码、业务处理等)。这个就是入站。
出站是入站操作完成,要把数据进行一系列操作(比如编码等),再回写给客户端端,数据是从服务端(本体)流到客户端的,属于出站操作。
所以入站出站要看从哪个角度的数据流向决定。
事件传播要手动传播的,要调用相应的传播方法。
Inbound的事件传播方式:
//这个接口定义了入站各种事件的回调方法,啥事件发生了,就会调用相关方法,而事件传播的方法就是
//ctx.fireXXXX方法,比如channel已经注册时,会发送事件调用channelRegistered方法,如果你想
//这个事件传播到Pipeline链中的下一个Inbound Handler,就要在重写该方法时使用
//ctx.fireChannelRegistered();方法进行事件的传播,就是fireXXXX机制,下面的事件也一样。
public interface ChannelInboundHandler extends ChannelHandler {
void channelRegistered(ChannelHandlerContext ctx) throws Exception;
void channelUnregistered(ChannelHandlerContext ctx) throws Exception;
void channelActive(ChannelHandlerContext ctx) throws Exception;
void channelInactive(ChannelHandlerContext ctx) throws Exception;
void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception;
void channelReadComplete(ChannelHandlerContext ctx) throws Exception;
void userEventTriggered(ChannelHandlerContext ctx, Object evt) throws Exception;
void channelWritabilityChanged(ChannelHandlerContext ctx) throws Exception;
@Override
@SuppressWarnings("deprecation")
void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception;
}
//这个是实现了ChannelInboundHandler 接口的实现类,复写全部方法,内容是进行事件传播。
//如果我们继承这个类来实现自己的InboundHandler的话,我们不重写的方法,默认就什么也不改,只进行事件传播。
//但是我们复写了的方法也要手动进行事件传播
public class ChannelInboundHandlerAdapter extends ChannelHandlerAdapter implements ChannelInboundHandler {
@Override
public void channelRegistered(ChannelHandlerContext ctx) throws Exception {
ctx.fireChannelRegistered();
}
@Override
public void channelUnregistered(ChannelHandlerContext ctx) throws Exception {
ctx.fireChannelUnregistered();
}
@Override
public void channelActive(ChannelHandlerContext ctx) throws Exception {
ctx.fireChannelActive();
}
@Override
public void channelInactive(ChannelHandlerContext ctx) throws Exception {
ctx.fireChannelInactive();
}
@Override
public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
ctx.fireChannelRead(msg);
}
@Override
public void channelReadComplete(ChannelHandlerContext ctx) throws Exception {
ctx.fireChannelReadComplete();
}
@Override
public void userEventTriggered(ChannelHandlerContext ctx, Object evt) throws Exception {
ctx.fireUserEventTriggered(evt);
}
@Override
public void channelWritabilityChanged(ChannelHandlerContext ctx) throws Exception {
ctx.fireChannelWritabilityChanged();
}
@Override
public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause)
throws Exception {
ctx.fireExceptionCaught(cause);
}
}
Outbound的事件传播机制
Outbound 事件的传播方向是 tail -> customContext -> head
//这个接口定义了出站各种事件的回调方法,啥事件发生了,就会调用相关方法,而事件传播的方法就是
//ctx.xxxx方法,比如连接时,会发送事件调用connect方法,如果你想
//这个事件传播到Pipeline链中的下一个outbound Handler,就要在重写该方法时使用
//ctx.connect();方法进行事件的传播,就是ctx.xxxx机制,下面的事件也一样。
public interface ChannelOutboundHandler extends ChannelHandler {
void bind(ChannelHandlerContext ctx, SocketAddress localAddress, ChannelPromise promise) throws Exception;
void connect(
ChannelHandlerContext ctx, SocketAddress remoteAddress,
SocketAddress localAddress, ChannelPromise promise) throws Exception;
void disconnect(ChannelHandlerContext ctx, ChannelPromise promise) throws Exception;
void close(ChannelHandlerContext ctx, ChannelPromise promise) throws Exception;
void deregister(ChannelHandlerContext ctx, ChannelPromise promise) throws Exception;
void read(ChannelHandlerContext ctx) throws Exception;
void write(ChannelHandlerContext ctx, Object msg, ChannelPromise promise) throws Exception;
void flush(ChannelHandlerContext ctx) throws Exception;
}
//这个是实现了ChannelOutboundHandler 接口的实现类,复写全部方法,内容是进行事件传播。
//如果我们继承这个类来实现自己的OutboundHandler的话,我们不重写的方法,默认就什么也不改,只进行事件传播。
//但是我们复写了的方法也要手动进行事件传播
public class ChannelOutboundHandlerAdapter extends ChannelHandlerAdapter implements ChannelOutboundHandler {
@Override
public void bind(ChannelHandlerContext ctx, SocketAddress localAddress,
ChannelPromise promise) throws Exception {
ctx.bind(localAddress, promise);
}
@Override
public void connect(ChannelHandlerContext ctx, SocketAddress remoteAddress,
SocketAddress localAddress, ChannelPromise promise) throws Exception {
ctx.connect(remoteAddress, localAddress, promise);
}
@Override
public void disconnect(ChannelHandlerContext ctx, ChannelPromise promise)
throws Exception {
ctx.disconnect(promise);
}
@Override
public void close(ChannelHandlerContext ctx, ChannelPromise promise)
throws Exception {
ctx.close(promise);
}
@Override
public void deregister(ChannelHandlerContext ctx, ChannelPromise promise) throws Exception {
ctx.deregister(promise);
}
@Override
public void read(ChannelHandlerContext ctx) throws Exception {
ctx.read();
}
@Override
public void write(ChannelHandlerContext ctx, Object msg, ChannelPromise promise) throws Exception {
ctx.write(msg, promise);
}
@Override
public void flush(ChannelHandlerContext ctx) throws Exception {
ctx.flush();
}
}
测试
服务端:
public class Server {
private int port;
public Server(int port){
this.port = port;
}
public void start() throws InterruptedException {
NioEventLoopGroup bossGroup = new NioEventLoopGroup();
NioEventLoopGroup workerGroup = new NioEventLoopGroup();
try {
ServerBootstrap serverBootstrap = new ServerBootstrap();
serverBootstrap.group(bossGroup,workerGroup)
.channel(NioServerSocketChannel.class)
.option(ChannelOption.SO_BACKLOG,128)
.childOption(ChannelOption.SO_KEEPALIVE,true)
.childHandler(new ChannelInitializer<SocketChannel>() {
@Override
protected void initChannel(SocketChannel socketChannel) throws Exception {
ChannelPipeline pipeline = socketChannel.pipeline();
//添加一个出站handler和一个入站handler。
pipeline.addLast(new ServerInboundHandlerDemo());
pipeline.addLast(new ServerOutboundHandlerDemo());
}
});
ChannelFuture bindFuture = serverBootstrap.bind("127.0.0.1", port);
bindFuture.channel().closeFuture().sync();
}finally {
bossGroup.shutdownGracefully();
workerGroup.shutdownGracefully();
}
}
public static void main(String[] args) throws InterruptedException {
Server server = new Server(9898);
server.start();
}
}
客户端:
public class Client {
private int serverPort;
private SocketChannel socketChannel;
public Client(int serverPort){
this.serverPort = serverPort;
}
public void start() throws InterruptedException {
NioEventLoopGroup worker = new NioEventLoopGroup();
Bootstrap bootstrap = new Bootstrap();
try {
bootstrap.group(worker)
.channel(NioSocketChannel.class)
.handler(new ChannelInitializer<SocketChannel>() {
@Override
protected void initChannel(SocketChannel sc) throws Exception {
socketChannel = sc;
ChannelPipeline pipeline = sc.pipeline();
//添加一个客户端的出站handler和入站handler。
pipeline.addLast(new ClientOutboundHandlerDemo());
pipeline.addLast(new ClientInboundHandlerDemo());
}
});
ChannelFuture channelFuture = bootstrap.connect("127.0.0.1", serverPort);
//添加一个监听器,在客户端连接成功后的回调,在回调里面开启一个线程用于发送消息给服务端。每隔三秒发送一次。
channelFuture.addListener(new GenericFutureListener<Future<? super Void>>() {
@Override
public void operationComplete(Future<? super Void> future) throws Exception {
System.out.println("连接成功");
new Thread(()->{
while (true) {
socketChannel.writeAndFlush(Unpooled.copiedBuffer("Hello, I am Yehaocong", CharsetUtil.UTF_8));
try {
TimeUnit.SECONDS.sleep(3);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}).start();
}
});
channelFuture.channel().closeFuture().sync();
}finally {
worker.shutdownGracefully();
}
}
public static void main(String[] args) throws InterruptedException {
Client client = new Client(9898);
client.start();
}
}
服务端的出站和入站handler:
public class ServerOutboundHandlerDemo extends ChannelOutboundHandlerAdapter {
@Override
public void write(ChannelHandlerContext ctx, Object msg, ChannelPromise promise) throws Exception {
System.out.println(System.currentTimeMillis() + "--:ServerOutboundHandlerDemo write");
//睡眠一秒是为了可以看到先后顺序。
TimeUnit.MILLISECONDS.sleep(1);
super.write(ctx, msg, promise);
}
}
public class ServerInboundHandlerDemo extends ChannelInboundHandlerAdapter {
@Override
public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
System.out.println("ServerInboundHandlerDemo channelRead");
//睡眠一秒是为了可以看到先后顺序。
TimeUnit.MILLISECONDS.sleep(1);
ByteBuf byteBuf = (ByteBuf) msg;
//在netty中,对象的生命周期由引用计数器控制,
//ByteBuf就是这样,每个对象的初始化引用计数为1,调用一次release方法,引用计数器会减1,
//当尝试访问计数器为0的,对象时会抛出IllegalReferenceCountException。
//所以要采用retain把计数调回去,才能不抛出异常,或者使用新的Bytebuf也可。
byteBuf.retain();
//服务端接收到数据就把数据发回给客户端
ctx.channel().writeAndFlush(byteBuf);
//事件传播
super.channelRead(ctx, msg);
}
}
客户端的出站入站规则:
public class ClientInboundHandlerDemo extends ChannelInboundHandlerAdapter {
@Override
public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
System.out.println(System.currentTimeMillis() + "--:ClientInboundHandlerDemo channelRead");
//睡眠一秒是为了可以看到先后顺序。
TimeUnit.MILLISECONDS.sleep(1);
super.channelRead(ctx, msg);
}
}
public class ClientOutboundHandlerDemo extends ChannelOutboundHandlerAdapter{
@Override
public void write(ChannelHandlerContext ctx, Object msg, ChannelPromise promise) throws Exception {
System.out.println(System.currentTimeMillis() + "--:ClientOutboundHandlerDemo write" );
//睡眠一秒是为了可以看到先后顺序。
TimeUnit.MILLISECONDS.sleep(1);
super.write(ctx, msg, promise);
}
}
此时服务端和客户端的Pipeline分别如下:
服务端:
客户端:
所以流程如下:
- 启动服务端,等待客户端连接。
- 启动客户端,在连接回调里三秒发送一次信息给服务端。
- 发送信息给服务端时,处于客户端的出站操作,会从客户端的Pipeline里面从Tail节点开始往前找到第一个outbound为true的handler,找到的是ClientOutboundHandlerDemo并执行。然后进行事件传播。
- 服务端接收到客户端的数据后,此时是入站操作,从HeadContext开始寻找第一个Inbound为true的Handler,找到了ServerInboundHandlerDemo,并执行相关方法并在最后进行事件传播。
- 服务端返回数据给客户端,此时属于服务端的出站操作,会从服务端的Pipeline里面从Tail开始找到第一个Outbound为true的handler,并执行,然后进行事件传播。
- 客户端接收到服务端返回的数据后,此时是客户端的入站操作,会从客户端Pipeline从Head开始找到第一个inbound为true的handler,并执行,然后进行事件传播。
上面代码结果如下:
客户端:
服务端:
从时间戳可以看出执行的先后顺序为:
ClientOutboundHandlerDemo->ServerInboundHandlerDemo->ServerOutboundHandlerDemo->ClientInboundHandlerDemo 符合预期。
测试事件传播:
以上面代码为基础:添加4个Handler,服务端的出入站,客户端的出入站各添加一个。
public class ClientInboundHandlerDemo1 extends ChannelInboundHandlerAdapter {
@Override
public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
System.out.println(System.currentTimeMillis() + "--:ClientInboundHandlerDemo1 channelRead");
//睡眠一秒是为了可以看到先后顺序。
TimeUnit.MILLISECONDS.sleep(1);
super.channelRead(ctx, msg);
}
}
public class ClientOutboundHandlerDemo1 extends ChannelOutboundHandlerAdapter{
@Override
public void write(ChannelHandlerContext ctx, Object msg, ChannelPromise promise) throws Exception {
System.out.println(System.currentTimeMillis() + "--:ClientOutboundHandlerDemo1 write" );
//睡眠一秒是为了可以看到先后顺序。
TimeUnit.MILLISECONDS.sleep(1);
super.write(ctx, msg, promise);
}
}
public class ServerInboundHandlerDemo1 extends ChannelInboundHandlerAdapter {
@Override
public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
System.out.println(System.currentTimeMillis() + "--:ServerInboundHandlerDemo1 channelRead");
//睡眠一秒是为了可以看到先后顺序。
TimeUnit.MILLISECONDS.sleep(1);
ByteBuf byteBuf = (ByteBuf) msg;
byteBuf.retain();
ctx.channel().writeAndFlush(byteBuf);
super.channelRead(ctx, msg);
}
}
public class ServerOutboundHandlerDemo1 extends ChannelOutboundHandlerAdapter {
@Override
public void write(ChannelHandlerContext ctx, Object msg, ChannelPromise promise) throws Exception {
System.out.println(System.currentTimeMillis() + "--:ServerOutboundHandlerDemo1 write");
//睡眠一秒是为了可以看到先后顺序。
TimeUnit.MILLISECONDS.sleep(1);
super.write(ctx, msg, promise);
}
}
然后服务端和客户端的初始化时在最后添加这些handler。
最后一步,把之前的ServerInboundHandlerDemo的往客户端回写数据的代码去掉,因为一般会在最后一个Handler执行这个,所以在ServerInboundHandlerDemo1中执行。不然就会给客户端回写两次数据。
此时服务端和客户端的Pipeline发生改变,这里就不画了 太长,有点难话,就是在Tail之前添加两个Handler。
此时的流程:
- 启动服务端,等待客户端连接。
- 启动客户端,在连接回调里三秒发送一次信息给服务端。
- 发送信息给服务端时,处于客户端的出站操作,会从客户端的Pipeline里面从Tail节点开始往前找到第一个outbound为true的handler,找到的是ClientOutboundHandlerDemo1并执行。然后进行事件传播。因为是从后往前找,所以会找到ClientOutboundHandlerDemo1先。
- 客户端的Pipeline继续往前找outbound为true的handler,找到ClientOutboundHandlerDemo并执行,然后进行事件传播。
- 服务端接收到客户端的数据后,此时是入站操作,从HeadContext开始寻找第一个Inbound为true的Handler,找到了ServerInboundHandlerDemo,并执行相关方法并在最后进行事件传播。
- 服务端的Pipeline继续找到Inbound为true的Handler,找到ServerInboundHandlerDemo1,执行并进行传播。
- 服务端返回数据给客户端,此时属于服务端的出站操作,会从服务端的Pipeline里面从Tail开始找到第一个Outbound为true的handler,找到ServerOutboundHandlerDemo1,并执行,然后进行事件传播。
- 服务端继续往前找Outbound为true的handler,找到 ServerOutboundHandlerDemo,并执行,然后进行事件传播。
- 客户端接收到服务端返回的数据后,此时是客户端的入站操作,会从客户端Pipeline从Head开始找到第一个inbound为true的handler,找到ClientInboundHandlerDemo,并执行,然后进行事件传播。
10.客户端进行往后找 inbound为true的handler,找到ClientInboundHandlerDemo1,并执行,然后进行事件传播。
执行结果:
客户端:
服务端:
根据时间戳可以判断跟上面的流程是一致的。
此时把全部的handler的事件传播代码去掉。
再次执行:
客户端:
服务端:
出现这种情况的原因:
- 客户端找到了Outbound为true的ServerOutboundHandlerDemo1并执行逻辑,但是没有进行事件传播,也就是事件不会传播到Pipeline链中的下一个OutboundHandler上,也没传到HeadContext下,所以数据实际上也没写到服务端,所以服务端就没有任何输出。
- 所以自定义的Handler一定要添加事件传播的代码。
其他知识
ChannelHandler与ChannelHandlerContext的关系
每个 ChannelHandler 被添加到 ChannelPipeline 后,都会创建一个 ChannelHandlerContext 并与之创建的ChannelHandler 关联绑定。ChannelHandlerContext 允许 ChannelHandler 与其他的 ChannelHandler 实现进行交互。ChannelHandlerContext 不会改变添加到其中的 ChannelHandler,因此它是安全的。
下图描述了ChannelHandlerContext、ChannelHandler、ChannelPipeline 的关系:
Channel的生命周期:
Netty 有一个简单但强大的状态模型,并完美映射到 ChannelInboundHandler 的各个方法。下面是 Channel 生命周期中四个不同的状态:
状态 | 描述 |
---|---|
channelUnregistered() | Channel已经被创建,但是还没注册到EventLoop上,或者已经注册但是又取消注册 |
channelRegistered() | Channel已经被注册到一个EventLoop上 |
channelActive() | Channel处于活跃状态,也就是成功连接到某个远端channel上,可以进行数据的发送接收 |
channelInactive() | Channel断开连接 |
一个Channel的正常生命周期如下:
ChannelHandler事件:
对应着各个方法。
事件 | 描述 |
---|---|
handlerAdded() | ChannelHandler添加到实际上下文中准备处理事件,就是ChannelHandler被添加到对应的ChannelHandlerContext中时触发 |
handlerRemoved() | ChannelHandler从上下文中删除触发 |
exceptionCaught() | 抛出异常时触发 |
ChannelInboundHandler事件
事件 | 描述 |
---|---|
channelRegistered() | ChannelHandlerContext的Channel被注册到EventLoop,对应的Channel生命周期的channelRegistered状态 |
channelUnregistered() | ChannelHandlerContext的Channel从EventLoop中注销,对应的Channel生命周期的channelUnregistered状态 |
channelActive() | ChannelHandlerContext的Channel已激活,对应的Channel生命周期的channelActive状态 |
channelInactive | ChannelHanderContxt的Channel结束生命周期,对应的Channel生命周期的channelInactive状态 |
channelRead | 从当前Channel的对端读取消息,大概相当于触发了NIO的Read事件 |
channelReadComplete | 数据读取完成 |
channelWritabilityChanged | 改变通道的可写状态,可以使用Channel.isWritable()检查 |
exceptionCaught | 重写父类ChannelHandler的方法,处理异常 |
userEventTriggered | 一个用户自定义事件被触发 |
ChannelOutboundHandler
严格说ChannelOutboundHandler不能说是事件触发,而应该说是我们主动触发请求某个请求的回调。
而ChannelInboundHandler则是某个事件触发的回调。
请求 | 描述 |
---|---|
bind | 调用channel的bind的回调 |
connect | 调用channel的connect的回调 |
disconnect | 调用channel的disconnect的回调 |
close | 调用channel的close方法的回调 |
deregister | 调用channel的deregister方法的回调 |
write | 调用channel的write方法的回调 |
flush | 调用channel的flush的回调 |
read | 拦截ChannelHandlerContext#read()方法 |