12-ChannelHandler实现

ChannelHandler实现

  • 前面一篇文章介绍了ChannelHandler接口的继承体系,本文选择几个典型的ChannelHandler看一看源码

  • 下面是继承体系图,选取的的最底部的实现类,有些是抽象类

在这里插入图片描述

MessageToByteEncoder:编码消息( Message -> ByteBuf )
ByteToMessageDecoder:解码消息( ByteBuf -> Message )

一、SimpleChannelInboundHandler

  • 我们分析一下抽象类SimpleChannelInboundHandler,看是如何使用ChannelHandler。
  • SimpleChannelInboundHandler是抽象类,是可以处理指定类型消息的处理器,我们可以实现SimpleChannelInboundHandler后,实现对指定类型的消息的自定义处理。
/**
 * {@link ChannelInboundHandlerAdapter} which allows to explicit only handle a specific type of messages.
 * 只允许处理指定类型的消息,下面是一个简单的示例实现,只处理String类型的消息:
 * For example here is an implementation which only handle {@link String} messages.
 * <pre>
 *     public class StringHandler extends {@link SimpleChannelInboundHandler}&lt;{@link String}&gt; {
 *
 *         {@code @Override}
 *         protected void channelRead0({@link ChannelHandlerContext} ctx, {@link String} message)
 *                 throws {@link Exception} {
 *             System.out.println(message);
 *         }
 *     }
 * </pre>
 *
 *  SimpleChannelInboundHandler可以处理指定类型的消息。可以实现SimpleChannelInboundHandler来对指定类型的消息的自定义处理。
 */
public abstract class SimpleChannelInboundHandler<I> extends ChannelInboundHandlerAdapter {

    /**
     * 类型匹配器对象
     */
    private final TypeParameterMatcher matcher;
    
    /**
     * 使用完消息,是否自动释放,如果自动释放,则会调用ReferenceCountUtil.release(msg)释放引用计数
     */
    private final boolean autoRelease;

    /**
     * 构造方法
     */
    protected SimpleChannelInboundHandler(Class<? extends I> inboundMessageType, boolean autoRelease) {
        matcher = TypeParameterMatcher.get(inboundMessageType);
        this.autoRelease = autoRelease;
    }

    /**
     * 返回true表示消息应该被处理
     * 返回false,消息会被传递给ChannelPipeline中的下一个ChannelInboundHandler
     */
    public boolean acceptInboundMessage(Object msg) {
        return matcher.match(msg);
    }

    /**
     * 实现好的骨架方法,子类实现channelRead0即可
     * 方法内部已经将资源的释放实现好了,子类在channelRead0中不需要释放资源
     * */
    @Override
    public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
        //1.是否要释放消息
        boolean release = true;
        try {
            //2.判断是否为匹配的消息
            if (acceptInboundMessage(msg)) {
                @SuppressWarnings("unchecked")
                I imsg = (I) msg;
                //3.处理消息
                channelRead0(ctx, imsg);
            } else {
                //4.不需要释放消息,因为消息都不需要处理
                release = false;
                //5.触发Channel Read到下一个节点,由下一个节点处理消息
                ctx.fireChannelRead(msg);
            }
        } finally {
            //6.判断,是否要释放消息,两个标志都为true时,才释放
            if (autoRelease && release) {
                ReferenceCountUtil.release(msg);
            }
        }
    }

    /**
     * 需要子类实现的抽象方法,在接收到数据时被调用
     */
    protected abstract void channelRead0(ChannelHandlerContext ctx, I msg) throws Exception;
}
  • 下面是一个使用的例子
/**
 * @author by mozping
 * Sharable标识这类的实例之间可以在 channel 里面共享
 */
@ChannelHandler.Sharable
public class EchoClientHandler extends SimpleChannelInboundHandler<ByteBuf> {

    /**
     * channel活跃之后执行
     */
    @Override
    public void channelActive(ChannelHandlerContext ctx) {
        //1.当被通知该 channel 是活动的时候就发送信息
        ctx.writeAndFlush(Unpooled.copiedBuffer("Netty rocks!", CharsetUtil.UTF_8));
    }

    /**
     * 在接收到数据时被调用
     */
    @Override
    public void channelRead0(ChannelHandlerContext ctx,ByteBuf in) {
        System.out.println("Client received: " + in.toString(CharsetUtil.UTF_8));
    }

    /**
     * 异常时候执行
     */
    @Override
    public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) {
        //1.记录日志错误并关闭 channel
        cause.printStackTrace();
        ctx.close();
    }
}
  • 我们可以按照自己的想法重写对应的方法,继承SimpleChannelInboundHandler后只有channelRead0方法是必须重写的,通过参数来指定处理消息的类型。

二、MessageToByteEncoder

  • MessageToByteEncoder 是一个出站处理器,将消息进行编码,预留了 encode 方法交给子类重写,主处理流程已经 在MessageToByteEncoder 中实现好了。
/**
 * {@link ChannelOutboundHandlerAdapter} which encodes message in a stream-like fashion from one message to an
 * {@link ByteBuf}.
 * ChannelOutboundHandlerAdapter 将消息编码为ByteBuf,下面是实现的示例,将 Integer 编码为ByteBuf
 * 下面是实现的示例,将Integer编码为ByteBuf
 * Example implementation which encodes {@link Integer}s to a {@link ByteBuf}.
 *
 * <pre>
 *     public class IntegerEncoder extends {@link MessageToByteEncoder}&lt;{@link Integer}&gt; {
 *         {@code @Override}
 *         public void encode({@link ChannelHandlerContext} ctx, {@link Integer} msg, {@link ByteBuf} out)
 *                 throws {@link Exception} {
 *             out.writeInt(msg);
 *         }
 *     }
 * </pre>
 */
public abstract class MessageToByteEncoder<I> extends ChannelOutboundHandlerAdapter {

    /**
     * 类型匹配器
     */
    private final TypeParameterMatcher matcher;
    /**
     * 是否偏向使用Direct直接内存
     */
    private final boolean preferDirect;

    /**
     * 构造方法
     */
    protected MessageToByteEncoder() {
        this(true);
    }
    
    /**
     * 构造方法
     */
    protected MessageToByteEncoder(boolean preferDirect) {
        // <1> 获得 matcher
        matcher = TypeParameterMatcher.find(this, MessageToByteEncoder.class, "I");
        this.preferDirect = preferDirect;
    }

    /**
     * 返回true表示消息需要被处理
     * 返回false就会将消息传递给下一个ChannelOutboundHandler处理
     */
    public boolean acceptOutboundMessage(Object msg) throws Exception {
        return matcher.match(msg);
    }

    @Override
    public void write(ChannelHandlerContext ctx, Object msg, ChannelPromise promise) throws Exception {
        ByteBuf buf = null;
        try {
            //1.判断是否为匹配的消息,匹配才需要处理
            if (acceptOutboundMessage(msg)) {
                @SuppressWarnings("unchecked")
                I cast = (I) msg;
                //2.分配buf
                buf = allocateBuffer(ctx, cast, preferDirect);
                try {
                    //3.编码,子类实现
                    encode(ctx, cast, buf);
                } finally {
                    //4.释放msg
                    ReferenceCountUtil.release(cast);
                }
                //5.buf可读,说明有编码到数据
                if (buf.isReadable()) {
                    //6.写入buf到下一个节点
                    ctx.write(buf, promise);
                } else {
                    //7.反之就释放buf
                    buf.release();
                    //8.写入EMPTY_BUFFER到下一个节点,为了promise的回调
                    ctx.write(Unpooled.EMPTY_BUFFER, promise);
                }
                //9.置空buf
                buf = null;
            } else {
                //10.不匹配,就提交write事件,交给下一个节点处理
                ctx.write(msg, promise);
            }
        } catch (EncoderException e) {
            throw e;
        } catch (Throwable e) {
            throw new EncoderException(e);
        } finally {
            //11.释放buf
            if (buf != null) {
                buf.release();
            }
        }
    }

    /**
     *分配一个ByteBuf,作为encode 方法的参数,子类可以重写该方法来返回一个ByteBuf
     */
    protected ByteBuf allocateBuffer(ChannelHandlerContext ctx, @SuppressWarnings("unused") I msg, boolean preferDirect) throws Exception {
        if (preferDirect) {
            return ctx.alloc().ioBuffer();
        } else {
            return ctx.alloc().heapBuffer();
        }
    }

    /**
     * 子类重写的方法,将message消息编码成ByteBuf
     */
    protected abstract void encode(ChannelHandlerContext ctx, I msg, ByteBuf out) throws Exception;

    protected boolean isPreferDirect() {
        return preferDirect;
    }
}
  • 看到其实 MessageToByteEncoder 的代码模式和 SimpleChannelInboundHandler 类似,都将方法主体实现好,预留一个方法给子类实现,通过这种模板设计让具体的子类实现起来就更加简单,比如看一个 MessageToByteEncoder的实现 NumberEncoder,它将数字编码为ByteBuf

  • NumberEncoder:NumberEncoder 将数字编码为ByteBuf ,代码十分简单,只需要重写code 方法即可

/**
 * 将数字编码为ByteBuf,并写到对应的ByteBuf里面,
 * 这里回将数字编码为对应的二进制编码,
 * Encodes a {@link Number} into the binary representation prepended with
 * a magic number ('F' or 0x46) and a 32-bit length prefix.  For example, 42
 * will be encoded to { 'F', 0, 0, 0, 1, 42 }.
 */
public class NumberEncoder extends MessageToByteEncoder<Number> {

    @Override
    protected void encode(ChannelHandlerContext ctx, Number msg, ByteBuf out) {
        // 转化成 BigInteger 对象
        // Convert to a BigInteger first for easier implementation.
        BigInteger v;
        if (msg instanceof BigInteger) {
            v = (BigInteger) msg;
        } else {
            v = new BigInteger(String.valueOf(msg));
        }

        // 转换为字节数组
        // Convert the number into a byte array.
        byte[] data = v.toByteArray();
        int dataLength = data.length;

        // Write a message.
        out.writeByte((byte) 'F'); // magic number,魔数开头
        out.writeInt(dataLength);  // data length,数字长度
        out.writeBytes(data);      // data
    }
}

三、LoggingHandler

  • LoggingHandler 是双向处理器 ,用于打印日志,在指定事件触发的时候,LoggingHandler中对应的回调方法会被调用,这些回调方法就会打印日志。
  • 下面是类声明和构造方法,有很多重载的构造方法,这里只给出默认的构造方法,默认是DEBUG 日志级别
public class LoggingHandler extends ChannelDuplexHandler {
    public LoggingHandler() {
        this(DEFAULT_LEVEL);
    }
    
    public LoggingHandler(LogLevel level) {
        if (level == null) {
            throw new NullPointerException("level");
        }

        // 获得 logger
        logger = InternalLoggerFactory.getInstance(getClass());
        this.level = level;
        internalLevel = level.toInternalLevel();
    }
}
  • 当事件发生时,就会打印日志,LoggingHandler就是在对应事件的回调方法里面打印日志的
/**
     * 注册成功后回调打印日志
     */
    @Override
    public void channelRegistered(ChannelHandlerContext ctx) throws Exception {
        if (logger.isEnabled(internalLevel)) {
            logger.log(internalLevel, format(ctx, "REGISTERED"));
        }
        //调用下一个Handler传递事件
        ctx.fireChannelRegistered();
    }
    
    /**
     * 注销成功后回调打印日志
     */
    @Override
    public void channelUnregistered(ChannelHandlerContext ctx) throws Exception {
        if (logger.isEnabled(internalLevel)) {
            logger.log(internalLevel, format(ctx, "UNREGISTERED"));
        }
        //调用下一个Handler传递事件
        ctx.fireChannelUnregistered();
    }
    
    /**
     * 连接活跃后回调打印日志
     */
    @Override
    public void channelActive(ChannelHandlerContext ctx) throws Exception {
        // 打印日志
        if (logger.isEnabled(internalLevel)) {
            logger.log(internalLevel, format(ctx, "ACTIVE"));
        }
        //调用下一个Handler传递事件
        ctx.fireChannelActive();
    }
    
    /**
     * 不活跃后回调打印日志
     */
    @Override
    public void channelInactive(ChannelHandlerContext ctx) throws Exception {
        if (logger.isEnabled(internalLevel)) {
            logger.log(internalLevel, format(ctx, "INACTIVE"));
        }
        //调用下一个Handler传递事件
        ctx.fireChannelInactive();
    }
    
    /**
     * 绑定后回调打印日志
     */
    @Override
    public void bind(ChannelHandlerContext ctx, SocketAddress localAddress, ChannelPromise promise) throws Exception {
        if (logger.isEnabled(internalLevel)) {
            logger.log(internalLevel, format(ctx, "BIND", localAddress));
        }
        //调用下一个Handler传递事件
        ctx.bind(localAddress, promise);
    }
    
    /**
     * 读事件产生后回调打印日志
     */
    @Override
    public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
        if (logger.isEnabled(internalLevel)) {
            logger.log(internalLevel, format(ctx, "READ", msg));
        }
        //调用下一个Handler传递事件
        ctx.fireChannelRead(msg);
    }

    /**
     * 写事件产生后回调打印日志
     */
    @Override
    public void write(ChannelHandlerContext ctx, Object msg, ChannelPromise promise) throws Exception {
        if (logger.isEnabled(internalLevel)) {
            logger.log(internalLevel, format(ctx, "WRITE", msg));
        }
        //调用下一个Handler传递事件
        ctx.write(msg, promise);
    }
  • 从代码可以看出,针对所有的事件回调方法,LoggingHandler 需要做的只是打印对应的日志,然后将事件传递给下一个处理器,通过内部的format 方法格式化日志,这个比较繁琐也不是特别重要,就不贴代码了
  • 下面是使用示例,很简单只要在 ChannelPipeline 添加即可
    new ChannelInitializer<SocketChannel>() {
        @Override
        public void initChannel(SocketChannel ch)
            throws Exception {
                ch.pipeline().addLast("logging",new LoggingHandler(LogLevel.INFO));
                ch.pipeline().addLast(new EchoClientHandler());
            }
        }
  • 打印的日志示例如下:
[nioEventLoopGroup-2-2] INFO io.netty.handler.logging.LoggingHandler - [id: 0xcc7ebe08, L:/127.0.0.1:12345 - R:/127.0.0.1:63773] REGISTERED
[nioEventLoopGroup-2-2] INFO io.netty.handler.logging.LoggingHandler - [id: 0xcc7ebe08, L:/127.0.0.1:12345 - R:/127.0.0.1:63773] ACTIVE
[nioEventLoopGroup-2-2] INFO io.netty.handler.logging.LoggingHandler - [id: 0xcc7ebe08, L:/127.0.0.1:12345 - R:/127.0.0.1:63773] READ: 12B
         +-------------------------------------------------+
         |  0  1  2  3  4  5  6  7  8  9  a  b  c  d  e  f |
+--------+-------------------------------------------------+----------------+
|00000000| 4e 65 74 74 79 20 72 6f 63 6b 73 21             |Netty rocks!    |
+--------+-------------------------------------------------+----------------+
[nioEventLoopGroup-2-2] INFO io.netty.handler.logging.LoggingHandler - [id: 0xcc7ebe08, L:/127.0.0.1:12345 - R:/127.0.0.1:63773] WRITE: 12B
         +-------------------------------------------------+
         |  0  1  2  3  4  5  6  7  8  9  a  b  c  d  e  f |
+--------+-------------------------------------------------+----------------+
|00000000| 4e 65 74 74 79 20 72 6f 63 6b 73 21             |Netty rocks!    |
+--------+-------------------------------------------------+----------------+
[nioEventLoopGroup-2-2] INFO io.netty.handler.logging.LoggingHandler - [id: 0xcc7ebe08, L:/127.0.0.1:12345 - R:/127.0.0.1:63773] READ COMPLETE
Server received: Netty rocks!
[nioEventLoopGroup-2-2] INFO io.netty.handler.logging.LoggingHandler - [id: 0xcc7ebe08, L:/127.0.0.1:12345 - R:/127.0.0.1:63773] WRITE: 0B
[nioEventLoopGroup-2-2] INFO io.netty.handler.logging.LoggingHandler - [id: 0xcc7ebe08, L:/127.0.0.1:12345 - R:/127.0.0.1:63773] FLUSH
[nioEventLoopGroup-2-2] INFO io.netty.handler.logging.LoggingHandler - [id: 0xcc7ebe08, L:/127.0.0.1:12345 - R:/127.0.0.1:63773] CLOSE
[nioEventLoopGroup-2-2] INFO io.netty.handler.logging.LoggingHandler - [id: 0xcc7ebe08, L:/127.0.0.1:12345 ! R:/127.0.0.1:63773] INACTIVE
[nioEventLoopGroup-2-2] INFO io.netty.handler.logging.LoggingHandler - [id: 0xcc7ebe08, L:/127.0.0.1:12345 ! R:/127.0.0.1:63773] UNREGISTERED

四、小结

  • 给出了几种常见Handler的代码简单分析,从中看出,对应 ChannelHandler 的编码实现主要是重写对应的回调方法,弄清楚数据的走向在指定的回调方法做对应的逻辑处理即可。
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值