简介

netty提供了一个从ByteBuf到用户自定义的message的解码器叫做ByteToMessageDecoder,要使用这个decoder,我们需要继承这个decoder,并实现decode方法,从而在这个方法中实现ByteBuf中的内容到用户自定义message对象的转换。

那么在使用ByteToMessageDecoder的过程中会遇到什么问题呢?为什么又会有一个ReplayingDecoder呢?带着这个问题我们一起来看看吧。

ByteToMessageDecoder可能遇到的问题

要想实现自己的解码器将ByteBuf转换成为自己的消息对象,可以继承ByteToMessageDecoder,然后实现其中的decode方法即可,先来看下decode方法的定义:

     protected void decode(ChannelHandlerContext ctx,
ByteBuf buf, List<Object> out) throws Exception

输入的参数中buf是要解码的ByteBuf,out是解码过后的对象列表,我们需要把ByteBuf中的数据转换成为我们自己的对象加入out的list中。

那么这里可能会遇到一个问题,因为我们在调用decode方法的时候buf中的数据可能还没有准备好,比如我们需要一个Integer,但是buf中的数据不够一个整数,那么就需要一些buf中数据逻辑的判断,我们以一个带有消息长度的Buf对象来描述一下这个过程。

所谓带有消息长度的Buf对象,就是说Buf消息中的前4位,构成了一个整数,这个整数表示的是buf中后续消息的长度。

所以我们读取消息进行转换的流程是,先读取前面4个字节,得到消息的长度,然后再读取该长度的字节,这就是我们真正要获取的消息内容。

来看一下如果是继承自ByteToMessageDecoder应该怎么实现这个逻辑呢?

   public class IntegerHeaderFrameDecoder extends ByteToMessageDecoder {

      @Override
protected void decode(ChannelHandlerContext ctx,
ByteBuf buf, List<Object> out) throws Exception { if (buf.readableBytes() < 4) {
return;
} buf.markReaderIndex();
int length = buf.readInt(); if (buf.readableBytes() < length) {
buf.resetReaderIndex();
return;
} out.add(buf.readBytes(length));
}
}

在decode中,我们首先需要判断buf中可读的字节有没有4个,没有的话直接返回。如果有,则先读取这4个字节的长度,然后再判断buf中的可读字节是否小于应该读取的长度,如果小于,则说明数据还没有准备好,需要调用resetReaderIndex进行重置。

最后,如果所有的条件都满足,才真正进行读取工作。

有没有一个办法可以不提前进行判断,可以直接按照自己想要的内容来读取buf的方式呢?答案就是ReplayingDecoder。

我们先来看一下上面的例子用ReplayingDecoder重写是什么情况:

   public class IntegerHeaderFrameDecoder
extends ReplayingDecoder<Void> { protected void decode(ChannelHandlerContext ctx,
ByteBuf buf, List<Object> out) throws Exception { out.add(buf.readBytes(buf.readInt()));
}
}

使用ReplayingDecoder,我们可以忽略buf是否已经接收到了足够的可读数据,直接读取即可。

相比之下ReplayingDecoder非常的简单。接下来,我们来探究一下ReplayingDecoder的实现原理。

ReplayingDecoder的实现原理

ReplayingDecoder实际上是ByteToMessageDecoder的一个子类,它的定义如下:

public abstract class ReplayingDecoder<S> extends ByteToMessageDecoder

在ByteToMessageDecoder中,最重要的方法是channelRead,在这个方法中实际调用了callDecode(ctx, cumulation, out);来实现cumulation到out的解码过程。

ReplayingDecoder的秘密就在于对这个方法的重写,我们来看下这个方法的具体实现:

   protected void callDecode(ChannelHandlerContext ctx, ByteBuf in, List<Object> out) {
replayable.setCumulation(in);
try {
while (in.isReadable()) {
int oldReaderIndex = checkpoint = in.readerIndex();
int outSize = out.size();
if (outSize > 0) {
fireChannelRead(ctx, out, outSize);
out.clear();
if (ctx.isRemoved()) {
break;
}
outSize = 0;
}
S oldState = state;
int oldInputLength = in.readableBytes();
try {
decodeRemovalReentryProtection(ctx, replayable, out);
if (ctx.isRemoved()) {
break;
}
if (outSize == out.size()) {
if (oldInputLength == in.readableBytes() && oldState == state) {
throw new DecoderException(
StringUtil.simpleClassName(getClass()) + ".decode() must consume the inbound " +
"data or change its state if it did not decode anything.");
} else {
continue;
}
}
} catch (Signal replay) {
replay.expect(REPLAY);
if (ctx.isRemoved()) {
break;
} // Return to the checkpoint (or oldPosition) and retry.
int checkpoint = this.checkpoint;
if (checkpoint >= 0) {
in.readerIndex(checkpoint);
} else {
}
break;
}
if (oldReaderIndex == in.readerIndex() && oldState == state) {
throw new DecoderException(
StringUtil.simpleClassName(getClass()) + ".decode() method must consume the inbound data " +
"or change its state if it decoded something.");
}
if (isSingleDecode()) {
break;
}
}
} catch (DecoderException e) {
throw e;
} catch (Exception cause) {
throw new DecoderException(cause);
}
}

这里的实现和ByteToMessageDecoder不同的是ReplayingDecoder中定义了一个checkpoint,这个checkpint是在尝试进行数据解码之初设置的:

int oldReaderIndex = checkpoint = in.readerIndex();

如果是在解码的过程中出现了异常,则使用checkpoint重置index:

    int checkpoint = this.checkpoint;
if (checkpoint >= 0) {
in.readerIndex(checkpoint);
} else {
}

这里捕获的异常是Signal,Signal是什么呢?

Signal是一个Error对象:

public final class Signal extends Error implements Constant<Signal>

这个异常是从replayable中抛出来的。

replayable是一个特有的ByteBuf对象,叫做ReplayingDecoderByteBuf:

final class ReplayingDecoderByteBuf extends ByteBuf

在ReplayingDecoderByteBuf中定义了Signal属性:

    private static final Signal REPLAY = ReplayingDecoder.REPLAY;

这个Signal异常是从ReplayingDecoderByteBuf中的get方法中抛出的,这里以getInt为例,看一下异常是如何抛出的:

    public int getInt(int index) {
checkIndex(index, 4);
return buffer.getInt(index);
}

getInt方法首先会去调用checkIndex方法进行buff中的长度检测,如果小于要读取的长度,则会抛出异常REPLAY:

    private void checkIndex(int index, int length) {
if (index + length > buffer.writerIndex()) {
throw REPLAY;
}
}

这就是Signal异常的由来。

总结

以上就是对ReplayingDecoder的介绍,虽然ReplayingDecoder好用,但是从它的实现可以看出,ReplayingDecoder是通过抛出异常来不断的重试,所以在某些特殊的情况下会造成性能的下降。

也就是说在减少我们代码量的同时,降低了程序的执行效率。看来要想马儿跑又想马儿不吃草,这样的好事是不可能的了。

本文已收录于 http://www.flydean.com/14-4-netty-replayingdecoder/

最通俗的解读,最深刻的干货,最简洁的教程,众多你不知道的小技巧等你来发现!

欢迎关注我的公众号:「程序那些事」,懂技术,更懂你!

netty系列之:netty中的自动解码器ReplayingDecoder的更多相关文章

  1. Netty 系列之 Netty 高性能之道 高性能的三个主题 Netty使得开发者能够轻松地接受大量打开的套接字 Java 序列化

    Netty系列之Netty高性能之道 https://www.infoq.cn/article/netty-high-performance 李林锋 2014 年 5 月 29 日 话题:性能调优语言 ...

  2. 【读后感】Netty 系列之 Netty 高性能之道 - 相比 Mina 怎样 ?

    [读后感]Netty 系列之 Netty 高性能之道 - 相比 Mina 怎样 ? 太阳火神的漂亮人生 (http://blog.csdn.net/opengl_es) 本文遵循"署名-非商 ...

  3. netty系列之:netty中的核心解码器json

    目录 简介 java中对json的支持 netty对json的解码 总结 简介 程序和程序之间的数据传输方式有很多,可以通过二进制协议来传输,比较流行的像是thrift协议或者google的proto ...

  4. netty系列之:netty实现http2中的流控制

    目录 简介 http2中的流控制 netty对http2流控制的封装 Http2FlowController Http2LocalFlowController Http2RemoteFlowContr ...

  5. netty系列之:netty中各不同种类的channel详解

    目录 简介 ServerChannel和它的类型 Epoll和Kqueue AbstractServerChannel ServerSocketChannel ServerDomainSocketCh ...

  6. 【转】Netty系列之Netty编解码框架分析

    http://www.infoq.com/cn/articles/netty-codec-framework-analyse/ 1. 背景 1.1. 编解码技术 通常我们也习惯将编码(Encode)称 ...

  7. Netty系列之Netty编解码框架分析

    1. 背景 1.1. 编解码技术 通常我们也习惯将编码(Encode)称为序列化(serialization),它将对象序列化为字节数组,用于网络传输.数据持久化或者其它用途. 反之,解码(Decod ...

  8. Netty 系列之 Netty 高性能之道

    1. 背景 1.1. 惊人的性能数据 最近一个圈内朋友通过私信告诉我,通过使用 Netty4 + Thrift 压缩二进制编解码技术,他们实现了 10 W TPS(1 K 的复杂 POJO 对象)的跨 ...

  9. Netty系列之Netty高性能之道

    转载自http://www.infoq.com/cn/articles/netty-high-performance 1. 背景 1.1. 惊人的性能数据 最近一个圈内朋友通过私信告诉我,通过使用Ne ...

随机推荐

  1. 为什么操作 DOM 慢?

    DOM本身是一个js对象, 操作这个对象本身不慢, 但是操作后触发了浏览器的行为, 如repaint和reflow等浏览器行为, 使其变慢

  2. JDBC中的Statement 和PreparedStatement的区别?

    PreparedStatement是预编译的SQL语句,效率高于Statement. PreparedStatement支持操作符,相对于Statement更加灵活. PreparedStatemen ...

  3. 如何在 Spring Boot 中禁用 Actuator 端点安全性?

    默认情况下,所有敏感的 HTTP 端点都是安全的,只有具有 ACTUATOR 角色的用户才能访问它们.安全性是使用标准的 HttpServletRequest.isUserInRole 方法实施的. ...

  4. JVM-learning

    JVM是什么?? Java Virtual Mechine JRE(JavaRuntimeEnvironment,Java运行环境),也就是Java平台.所有的Java 程序都要在JRE下才能运行. ...

  5. idea常见设置一

    1.顶部执行栏调整 原: 设置:勾选view==>toolbar 后: 这个一看其实没有什么太大的作用,但是你看这个按钮 这个按钮就是File==>Settings,这样会稍微方便点,只要 ...

  6. DASCTF Oct吉林工师web

    迷路的魔法少女 进入环境给出源码 <?php highlight_file('index.php'); extract($_GET); error_reporting(0); function ...

  7. vim的vimrc配置

    windows "# modified by Neoh set helplang=cn "使用中文帮助文档 set encoding=utf-8 "查看utf-8格式的帮 ...

  8. AD18布线技巧

    3. 快乐打孔模式(颜色配置)PCB 设计完成后,补回流孔,需要打开多层,软件设置如下: 设置方法: 转载原文链接未知

  9. 前端每日实战:96# 视频演示如何用纯 CSS 和 D3 创作一艘遨游太空的宇宙飞船

    效果预览 按下右侧的"点击预览"按钮可以在当前页面预览,点击链接可以全屏预览. https://codepen.io/comehope/pen/oMqNmv 可交互视频 此视频是可 ...

  10. 【weex开发】vue-swipe 滑动组件的使用

    一,vue-swipe简介 vue-swipe 是饿了么团队开发的vue专用的轮播图插件: 可以实现简单的图片和view轮播,可控制动画时长,可限制手动滑动: 简而言之,可以实现轮播,也可以实现ppt ...