netty 之 Pipeline的事件传播机制

一、说明

ChannelPipeline的默认实现DefaultChannelPipeline中的双向链表中元素AbstractChannelHandlerContext中有inbound与outbound两个属性,用于标识Context中handler类型。

 

1、inbound事件与outbound事件,在Pipeline中经过的Context(handler)流向是相反的:

例如,有6个handler:h1(inbound),h2(inhound),h3(inbound)、h4(outbound)、h5(outbound)、h6(outbound),依次调用addList接口添加到ChannelPipeline,添加后,双向链表结构如下图:

  • inbound事件(read,只经过inboundhandler):按照上图的上下顺序,从【上】——>向【下】,即:h1,h2,h3
  • inbound事件的传播方向是    Head---->custom Tail
  • inbound事件都是【通知】事件:即某事件已经发生了,然后通过inbound链进行通知(通常发生在Channel的状态改变(Channel的声明周期中有四种状态:channelUnregistered->channelRegistered->channelInactive->channelActive)或I/O事件就绪时——通常在Unsafe执行某些outbound事件后触发)
  • outbound事件(write,只经过outboundhandler):按照上图的上下顺序,从【下】——>向【上】,即:h6,h5,h4
  • ountbound事件的传播方向是Tail ----> Head
  • outbound事件都是【请求】事件:即触发某事件的发生,然后通过outbound链进行通知(通常是用户主动发起的)

2、addList是往下面添加、addFirst是往上面添加

3、Inbound事件传播方法:

  • 用户如果对下面事件感兴趣,可以在自己的Handler中重写这些方法
  • 传播流程:参考outbound

方法说明如下: 

  • 如果调用上图方法,比如fireChannelRegistered(),会发送一个channelRegistered的inount事件给当前ChannelHanlerContext的下一个ChannelHanlerContext(从上面流程图来看,是上一个),(方法参数ChannelHandlerContext即为当前被调用的context(handler))
  • 比如在我们自己定义的handler中的某个事件回调函数中,处理完事件后(这个过程通常叫做捕获了一个事件),想让事件继续传播下去的话,需要调用ctx.对应的方法,比如:

4、Outbound事件传播方法:用户如果对下面事件感兴趣,可以在自己的Handler中重写这些方法

  • 用户如果对下面事件感兴趣,可以复写对应方法
  • 传播流程:当用户调用某个方法,比如Bootstrap的connect方法,会触发一个对应的事件请求(这里是connect事件),进而调用DefaultChannelPipeline的connect方法,而一旦调用DefaultChannelPipeline的connect方法,便会将事件在双向链表中进行流转,对于outbound事件,在链表中是从Tail开始传递的,而tail的connect调用的是AbstractChannelHandlerContext的connect方法,其逻辑是寻找下一个inbound handler然后进行调用,传递期间如果用户没有重写这些方法,会调用ChannelOutboundHandlerAdapter的对应方法,而这个Adapter中,方法实现基本都是调用ctx.对应方法,而这个调用又回到了AbstractChannelHandlerContext中对应方法,而其逻辑是寻找下一个outbound handler然后进行调用,这样一直循环下去,直到事件传递到双向链表的Head节点(connect事件的真正执行,就是在Head这个handler中)

  • 如果调用上图方法,比如bind(),会发送一个bind的outbound事件给当前ChannelHanlerContext的下一个ChannelHanlerContext,(方法参数ChannelHandlerContext即为当前被调用的context(handler))
  • 其中read、write两个方法都是outbount的?为啥read不是inbound呢?请知道答案的大神给解答下,万分感谢

5、inbound与outbound事件的联系:

b.connect方法调用会触发一个outbound事件,最终事件执行是在Head中调用unsafe的connect方法,unsafe类在我的其他博文中介绍过,这就是一个封装了底层JDK Nio操作的工具类(AbstractNioChannel.AbstractNioUnsafe),在unsafe的connect方法中,先调用doConnect方法进行实际的Socket连接,当链接后会调用【fulfillConnectPromise方法】

可见,在上面方法中,通过调用pipeline().fireChannelActive方法,而这就是inbound事件的起点,在fireXX方法中,将产生一个ChannelActive的inbound事件,而inbound事件,将从Head开始传递,接下来的代码我们就比较熟悉了。

~!!!:与outbound不同的是,outbound链中,如果用户handler不阻断事件的传播的话(也不应该阻断),事件最终是在Head的对应方法中处理,同时Head中对应方法大部分调用的都是unsafe的方法;而inbound链中,如果用户handler不足段事件的传播的话(也不应该阻断),事件最终会在Tail中处理,而我们可以看到Tail的方法实现大部分都是空实现,也就是如果inbound事件,当用户没有实现自定义的处理器并进行处理时,那么netty默认是不处理的。

二、总结

三、编写代码验证Pipeline的事件传播机制

1、服务端代码

三个inbound,三个outbound

package com.mzj.netty.mynettysrc.pipelinetransport;

import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.ChannelInboundHandlerAdapter;

/**
 * @Auther: mazhongjia
 * @Date: 2020/9/27 15:14
 * @Version: 1.0
 */
public class InboundHandlerA extends ChannelInboundHandlerAdapter {

    @Override
    public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
//        super.channelRead(ctx, msg);
        System.out.println("[server]InboundHandlerA....");
        ctx.fireChannelRead(msg);
    }
}
package com.mzj.netty.mynettysrc.pipelinetransport;

import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.ChannelInboundHandlerAdapter;

/**
 * @Auther: mazhongjia
 * @Date: 2020/9/27 15:14
 * @Version: 1.0
 */
public class InboundHandlerB extends ChannelInboundHandlerAdapter {

    @Override
    public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
//        super.channelRead(ctx, msg);
        System.out.println("[server]InboundHandlerB....");
        ctx.fireChannelRead(msg);
    }
}
package com.mzj.netty.mynettysrc.pipelinetransport;

import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.ChannelInboundHandlerAdapter;

/**
 * @Auther: mazhongjia
 * @Date: 2020/9/27 15:14
 * @Version: 1.0
 */
public class InboundHandlerC extends ChannelInboundHandlerAdapter {

    @Override
    public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
//        super.channelRead(ctx, msg);
        System.out.println("[server]InboundHandlerC....");
        ctx.fireChannelRead(msg);
    }
}
package com.mzj.netty.mynettysrc.pipelinetransport;

import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.ChannelOutboundHandlerAdapter;
import io.netty.channel.ChannelPromise;

/**
 * @Auther: mazhongjia
 * @Date: 2020/9/27 15:21
 * @Version: 1.0
 */
public class OutboundHandlerA extends ChannelOutboundHandlerAdapter {

    @Override
    public void write(ChannelHandlerContext ctx, Object msg, ChannelPromise promise) throws Exception {
//        super.write(ctx, msg, promise);
        System.out.println("[server]OutboundHandlerA.write");
        ctx.write(msg, promise);
    }
}
package com.mzj.netty.mynettysrc.pipelinetransport;

import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.ChannelOutboundHandlerAdapter;
import io.netty.channel.ChannelPromise;

import java.util.concurrent.TimeUnit;

/**
 * @Auther: mazhongjia
 * @Date: 2020/9/27 15:21
 * @Version: 1.0
 */
public class OutboundHandlerB extends ChannelOutboundHandlerAdapter {

    @Override
    public void write(ChannelHandlerContext ctx, Object msg, ChannelPromise promise) throws Exception {
//        super.write(ctx, msg, promise);
        System.out.println("[server]OutboundHandlerB.write");
        ctx.write(msg, promise);
    }

    @Override
    public void handlerAdded(final ChannelHandlerContext ctx) throws Exception {
        ctx.executor().schedule(new Runnable() {
            @Override
            public void run() {
                ctx.channel().write("say hello~~~");
            }
        }, 3, TimeUnit.SECONDS);
    }
}
package com.mzj.netty.mynettysrc.pipelinetransport;

import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.ChannelOutboundHandlerAdapter;
import io.netty.channel.ChannelPromise;

/**
 * @Auther: mazhongjia
 * @Date: 2020/9/27 15:21
 * @Version: 1.0
 */
public class OutboundHandlerC extends ChannelOutboundHandlerAdapter {

    @Override
    public void write(ChannelHandlerContext ctx, Object msg, ChannelPromise promise) throws Exception {
//        super.write(ctx, msg, promise);
        System.out.println("[server]OutboundHandlerC.write");
        ctx.write(msg, promise);
    }
}

服务端6个handler添加顺序为:

  • InboundHandlerA
  • InboundHandlerB
  • InboundHandlerC
  • OutboundHandlerA
  • OutboundHandlerB
  • OutboundHandlerC 
package com.mzj.netty.mynettysrc.pipelinetransport;

import io.netty.bootstrap.ServerBootstrap;
import io.netty.channel.ChannelFuture;
import io.netty.channel.ChannelInitializer;
import io.netty.channel.ChannelOption;
import io.netty.channel.EventLoopGroup;
import io.netty.channel.nio.NioEventLoopGroup;
import io.netty.channel.socket.SocketChannel;
import io.netty.channel.socket.nio.NioServerSocketChannel;

/**
 * @Auther: mazhongjia
 * @Date: 2020/9/27 15:34
 * @Version: 1.0
 */
public class PipelineTestServer {

    public void start(int port) throws Exception {
        EventLoopGroup bossGroup = new NioEventLoopGroup();
        EventLoopGroup workerGroup = new NioEventLoopGroup();

        try {
            ServerBootstrap serverBootstrap = new ServerBootstrap();
            serverBootstrap.group(bossGroup, workerGroup).channel(NioServerSocketChannel.class).childHandler(new ChannelInitializer<SocketChannel>() {

                @Override
                protected void initChannel(SocketChannel ch) throws Exception {

                    //InboundHandler的执行顺序为注册顺序的正序:A->B->C
                    ch.pipeline().addLast(new InboundHandlerA());
                    ch.pipeline().addLast(new InboundHandlerB());
                    ch.pipeline().addLast(new InboundHandlerC());

                    //outboundHandler的执行顺序为注册顺序的逆序:C->B->A
                    ch.pipeline().addLast(new OutboundHandlerA());
                    ch.pipeline().addLast(new OutboundHandlerB());
                    ch.pipeline().addLast(new OutboundHandlerC());
                }
            }).option(ChannelOption.SO_BACKLOG, 128)
                    .childOption(ChannelOption.SO_KEEPALIVE, true);

            ChannelFuture future = serverBootstrap.bind(port).sync();

            future.channel().closeFuture().sync();
        } finally {
            bossGroup.shutdownGracefully();
            workerGroup.shutdownGracefully();
        }
    }

    public static void main(String[] args) throws Exception {
        new PipelineTestServer().start(8080);
    }
}

服务端实现:

  • 当有客户端连接时,触发ChannelInitializer的initChannel方法调用,向NioSocketChannel添加inbound、outbound的handler
  • 所有inbound的handler监听channelRead事件,并输出自己被调用
  • 所有outbound的handler监听write事件,并输出自己被调用
  • OutboundHandlerB这个handler添加成功时(在有客户端连接成功时向childHandler中添加的),调用ctx.executor并在3秒后,向服务端发送say hello~~~~~字符串

2、客户端代码

package com.mzj.netty.mynettysrc.pipelinetransport;

import io.netty.bootstrap.Bootstrap;
import io.netty.buffer.ByteBuf;
import io.netty.channel.*;
import io.netty.channel.nio.NioEventLoopGroup;
import io.netty.channel.socket.SocketChannel;
import io.netty.channel.socket.nio.NioSocketChannel;

/**
 * @Auther: mazhongjia
 * @Date: 2020/9/27 16:41
 * @Version: 1.0
 */
public class PipelineTestClient {

    public void connect(String host, int port) throws InterruptedException {
        EventLoopGroup workerGroup = new NioEventLoopGroup();

        try {
            Bootstrap bootstrap = new Bootstrap();
            bootstrap.group(workerGroup);
            bootstrap.channel(NioSocketChannel.class);
            bootstrap.option(ChannelOption.SO_KEEPALIVE, true);
            bootstrap.handler(new ChannelInitializer<SocketChannel>() {
                @Override
                protected void initChannel(SocketChannel ch) throws Exception {
                    ch.pipeline().addLast(new ClientIntHandler());
                }
            });

            ChannelFuture future = bootstrap.connect(host, port).sync();
            future.channel().closeFuture().sync();
        } finally {
            workerGroup.shutdownGracefully();
        }
    }

    public static void main(String[] args) throws InterruptedException {
        PipelineTestClient client = new PipelineTestClient();
        client.connect("127.0.0.1", 8080);
    }

    private static class ClientIntHandler extends ChannelInboundHandlerAdapter {

        //读取服务端信息
        @Override
        public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
            System.out.println("[client]ClientIntHandler.channelRead");

            ByteBuf result = (ByteBuf) msg;
            byte[] resultBytes = new byte[result.readableBytes()];

            result.readBytes(resultBytes);

            result.release();

            ctx.close();

            System.out.println("[client]Server said : " + new String(resultBytes));

            super.channelRead(ctx, msg);
        }

        //当连接建立的时候向服务端发送消息,channelActive事件在连接建立的时候会被触发
        @Override
        public void channelActive(ChannelHandlerContext ctx) throws Exception {

            System.out.println("[client]ClientIntHandler active......");
            String msg = "Are you ok?";

            byte[] messageBytes = msg.getBytes();
            ByteBuf byteBuf = ctx.alloc().buffer(messageBytes.length);
            byteBuf.writeBytes(messageBytes);
            ctx.write(byteBuf);
            ctx.flush();
        }
    }
}
  • 客户端实现:
    • 当客户端active时,向服务端发送Are you ok?字符串
    • 并监听服务端发送的数据,进行System.out.println

3、测试(只关注服务端输出)

先后运行服务端、客户端,在客户端连接成功时:

  • 服务端:向客户端NioSocketChannel添加handler时,在OutboundHandlerB添加后,3秒后向客户端发送信息
  • 客户端:连接成功后向服务端发送数据
  • 服务端:收到客户端发送数据后,经过3个inbound handler的channelRead事件回调函数,首先输出:

  • 服务端:3秒后,在InboundHandlerB中,向客户端发送数据
  • 服务端:经过3个outbound handler的write事件回调函数,又输出:

 

  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值