Netty从入门到实战(七)--- pipline 与 channelHandler

本系列文章参考内容为 闪电侠 的掘金小册

地址 :Netty入门与实战:仿写微信IM即时通信系统

通过上篇文章 Netty从入门到实战(七)— 实现客户端与服务端收发消息 我们会发现代码中充斥了大量的 if else 代码块,哪如何避免 else 泛滥 ?

我们先回忆一下客户端和服务端的数据处理流程:

  • 数据传输
  • 编码(decode)
  • if else 登录 消息收发
  • 解码(encode)
  • 数据传输

以上逻辑过程我们都是写到一个类中的,客户端对应 ClientHandler.java, 服务端对应 ServerHandler.java,这就造成一个问题:我们一旦要做功能扩展,哪就只能去这个类里面改改改,久而久之,这个类就变得杂乱不堪,十分臃肿!

此外,每次数据传输之前我们都需要手动调用 ByteBuf 对数据进行处理,以上场景我们可以使用模块化处理,不同的逻辑放置到单独的类来处理,最后将这些逻辑串联起来,形成一个完整的逻辑处理链。

基于此,我们今天要学习的 pipelinechannelHandler 正好是用来解决这个问题的:

pipeline 和 channelHandler 通过责任链设计模式来组织代码逻辑,并且能够支持逻辑的动态添加和删除,Netty 能够支持各种协议的扩展,靠的就是 pipeline 和 channelHandler 。

pipeline 与 channelHandler 的构成

在 Netty 中 ,一条连接对应一个 Channel,这条 Channel 所有的处理逻辑都在 ChannelPipeline 的对象里面,ChannelPipeline 是一个双向链表结构,它和 Channel 是一对一的关系。

ChannelPipeline 里面每一个节点都是一个 ChannelHandlerContext 对象,这个对象能拿到 Channel 相关的所有上下文信息。

channelHandler 分类

  • ChannelHandler
    • ChannelInBoundHandler
      • ChannelInBoundHandlerAdapter
    • ChannelOutBoundHandler
      • ChannelOutBoundHandlerAdapter

ChannelHandler 有两大子接口

  • ChannelInBoundHandler 处理读数据的逻辑,也就是说在组装响应之前的逻辑,都可以放在 ChannelInBoundHandler 中处理,最重要的方法就是 channelRead()方法;
  • ChannelOutBoundHandler 处理写数据的逻辑,最核心的一个方法是 write()

这两个子接口有两个默认实现 ChannelInBoundHandlerAdapterChannelOutBoundHandlerAdapter,默认情况下会把读写事件传播到下一个 handler。

ChannelInboundHandler 的事件传播

在服务端的 pipeline 添加三个处理器, 添加的顺序为 A -> B -> C

NettyServer.java

serverBootstrap.group(bossGroup,workerGroup)
                .channel(NioServerSocketChannel.class)
                .childHandler(new ChannelInitializer<NioSocketChannel>() {
                    @Override
                    protected void initChannel(NioSocketChannel ch) throws Exception {
                        ch.pipeline().addLast(new InBoundHandlerA());
                        ch.pipeline().addLast(new InBoundHandlerB());
                        ch.pipeline().addLast(new InBoundHandlerC());
                    }
                });

InBoundHandlerA.java

public class InBoundHandlerA extends ChannelInboundHandlerAdapter {
    @Override
    public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
        System.out.println("InBoundHandlerA: " + msg);
        super.channelRead(ctx, msg);
    }
}

InBoundHandlerB.java

public class InBoundHandlerB extends ChannelInboundHandlerAdapter {
    @Override
    public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
        System.out.println("InBoundHandlerB: " + msg);
        super.channelRead(ctx, msg);
    }
}

InBoundHandlerC.java

public class InBoundHandlerC extends ChannelInboundHandlerAdapter {
    @Override
    public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
        System.out.println("InBoundHandlerC: " + msg);
        super.channelRead(ctx, msg);
    }
}

执行结果

可以发现输出的结果和添加顺序一致,是 A -> B -> C

在这里插入图片描述

我为什么会在此处强调事件传播性的顺序,想必大家自己也想过,哪么,此时的你就应该先停下来,然后撸一段代码,证明一下 ChannelOutBoundHandler的事件传播是什么样的?

-----------------------------我是一条分割线-----------------------------

channelOutBoundHandler 的事件传播

和上述操作过程差不多,开始撸代码!

NettyServer.java

serverBootstrap
                .group(boosGroup, workerGroup)
                .channel(NioServerSocketChannel.class)
                .childHandler(new ChannelInitializer<NioSocketChannel>() {
                    protected void initChannel(NioSocketChannel ch) {
                        // inBound,处理读数据的逻辑链
                        ch.pipeline().addLast(new InBoundHandlerA());
                        ch.pipeline().addLast(new InBoundHandlerB());
                        ch.pipeline().addLast(new InBoundHandlerC());

                        // outBound,处理写数据的逻辑链
                        ch.pipeline().addLast(new OutBoundHandlerA());
                        ch.pipeline().addLast(new OutBoundHandlerB());
                        ch.pipeline().addLast(new OutBoundHandlerC());
                    }
                });

OutBoundHandlerA.java

public class OutBoundHandlerA extends ChannelOutboundHandlerAdapter {

    @Override
    public void write(ChannelHandlerContext ctx, Object msg, ChannelPromise promise) throws Exception {
        System.out.println("OutBoundHandlerA: " + msg);
        super.write(ctx, msg, promise);
    }
}

OutBoundHandlerB.java

public class OutBoundHandlerB extends ChannelOutboundHandlerAdapter {

    @Override
    public void write(ChannelHandlerContext ctx, Object msg, ChannelPromise promise) throws Exception {
        System.out.println("OutBoundHandlerA: " + msg);
        super.write(ctx, msg, promise);
    }
}

OutBoundHandlerC.java

public class OutBoundHandlerC extends ChannelOutboundHandlerAdapter {

    @Override
    public void write(ChannelHandlerContext ctx, Object msg, ChannelPromise promise) throws Exception {
        System.out.println("OutBoundHandlerC: " + msg);
        super.write(ctx, msg, promise);
    }
}

执行结果

在这里插入图片描述

可以发现输出的结果和添加顺序正好相反,是 C -> B -> A

由此可以判断出 pipeline 的结构是类似双向链表的,如下如所示:

pipeline 的结构

在这里插入图片描述

pipeline 的执行顺序

在这里插入图片描述

也就是说虽然读数据和写数据的 handler 都是在同一个双向链表中,但是因为他们的分工不一样,inBoundHandler 的事件只会传播到下一个 inBoundHandler,outBoundHandler 的事件也只会传播到下一个 outBoundHandler, 两者之间是不受干扰的。

总结

  • channelHandler 分为 inBound 和 outBound 两种类型的接口,分别负责读数据和写数据的处理逻辑;
  • ChannelInboundHandlerAdapter 和 ChannelOutboundHandlerAdapter 是对应接口的默认实现,默认会把事件传递到下一个 handler 继续处理;
  • inBoundHandler 的执行顺序与我们实际的添加顺序相同,而 outBoundHandler 则相反。

欢迎关注微信公众号 【程序猿杂货铺】,第一时间获取更多干货内容!

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值