一、简介

在网络应用中需要实现某种编解码器,将原始字节数据与自定义的消息对象进行互相转换。网络中都是以字节码的数据形式来传输数据的,服务器编码数据后发送到客户端,客户端需要对数据进行解码。

编解码器由两部分组成:编码器、解码器。

netty提供了强大的编解码器框架,使得我们编写自定义的编解码器很容易,也容易封装个重用。

解码器:负责将消息从字节或其他序列形式转成指定的消息对象。
编码器:将消息对象转成字节或其他序列形式在网络上传输。

编码器和解码器的结构很简单,消息被编码后解码后会自动通过ReferenceCountUtil.release(message)释放,如果不想释放消息可以使用ReferenceCountUtil.retain(message),这将会使引用数量增加而没有消息发布,大多数时候不需要这么做。

其实,各种编解码器的实现都是ChannelHandler的实现。

二、解码器

Netty提供了丰富的解码器抽象基类,我们可以很容易的实现这些基类来自定义解码器。下面是解码器的类型:

  • 解码字节到消息
  • 解码消息到消息

解码器负责解码“入站”数据从一种格式到另一种格式,解码器处理入站数据是抽象ChannelInboundHandler的实现。实践中使用解码器很简单,就是将入站数据转换格式后传递到ChannelPipeline中的下一个ChannelInboundHandler进行处理;这样的处理时很灵活的,我们可以将解码器放在ChannelPipeline中,重用逻辑。

2.1 ByteToMessageDecoder

通常我们需要将消息从字节解码成消息或者从字节解码成其他的序列化字节 。这是一个常见的任务,Netty提供了抽象基类,我们可以使用它们来实现。Netty中提供的ByteToMessageDecoder可以将字节消息解码成POJO对象,下面列出了ByteToMessageDecoder两个主要方法:

decode(ChannelHandlerContext, ByteBuf, List<Object>)//这个方法是唯一的一个需要自己实现的抽象方法,作用是将ByteBuf数据解码成其他形式的数据。
decodeLast(ChannelHandlerContext, ByteBuf, List<Object>)//实际上调用的是decode(...)。

例如服务器从某个客户端接收到一个整数值的字节码,服务器将数据读入ByteBuf 并经过ChannelPipeline中的每个 ChannelInboundHandler进行处理。

从“入站”ByteBuf读取bytes后由 ToIntegerDecoder 进行解码,然后将解码后的消息存入List集合中,然后传递到ChannelPipeline中的下一个ChannelInboundHandler。看下面ToIntegerDecoder的实现代码:

/**
* Integer解码器,ByteToMessageDecoder实现
*
*/
public class ToIntegerDecoder extends ByteToMessageDecoder {
@Override
protected void decode(ChannelHandlerContext ctx, ByteBuf in, List<Object> out) throws Exception {
//接收到字节大于4个字节了就处理。
if(in.readableBytes() >= 4){
out.add(in.readInt());
}
}
}

从上面的代码可能会发现,我们需要检查ByteBuf读之前是否有足够的字节,这是与TCP黏包有关,若没有这个检查岂不更好?是的,Netty提供了这样的处理允许byte-to-message解码。除了ByteToMessageDecoder之外,Netty还提供了许多其他的解码接口。

2.2 ReplayingDecoder

ReplayingDecoder是byte-to-message解码的一种特殊的抽象基类,byte-to-message解码读取缓冲区的数据之前需要检查缓冲区是否有足够的字节,使用ReplayingDecoder就无需自己检查;若ByteBuf中有足够的字节,则会正常读取;若没有足够的字节则会停止解码。也正因为这样的包装使得ReplayingDecoder带有一定的局限性。 
• 不是所有的操作都被ByteBuf支持,如果调用一个不支持的操作会抛出DecoderException。 
• ByteBuf.readableBytes()大部分时间不会返回期望值

如果你能忍受上面列出的限制,相比ByteToMessageDecoder,你可能更喜欢ReplayingDecoder。在满足需求的情况下推荐使用ByteToMessageDecoder,因为它的处理比较简单,没有ReplayingDecoder实现的那么复杂。ReplayingDecoder继承于ByteToMessageDecoder,所以他们提供的接口是相同的。下面代码是ReplayingDecoder的实现:

/**
* Integer解码器,ReplayingDecoder实现
*/
public class ToIntegerReplayingDecoder extends ReplayingDecoder<Void> { @Override
protected void decode(ChannelHandlerContext ctx, ByteBuf in, List<Object> out) throws Exception {
out.add(in.readInt());
}
}

2.3 MessageToMessageDecoder

将消息对象转成消息对象可以使用MessageToMessageDecoder,它是一个抽象类,需要我们自己实现其decode(…)。message-to-message同上面讲的byte-to-message的处理机制一样,实现代码:

/**
* 将接收的Integer消息转成String类型,MessageToMessageDecoder实现
* @author c.k
*
*/
public class IntegerToStringDecoder extends MessageToMessageDecoder<Integer> { @Override
protected void decode(ChannelHandlerContext ctx, Integer msg, List<Object> out) throws Exception {
out.add(String.valueOf(msg));
}
}

2.4 TooLongFrameException类

由于Netty是一个异步框架,所以需要在字节可以解码之前在内存中缓冲它们。因此,不能让解码器缓冲大量的数据以至于耗尽可用的内存。为了解除这个常见的顾虑,Netty提供了TooLongFrameException类,其将由解码器在帧超出指定的大小限制时抛出。
为了避免这种情况,你可以设置一个最大字节数的阈值,如果超出该阈值,则会导致抛出一个TooLongFrameException(随后会被ChannelHandler.exceptionCaught()方法捕获)。然后,如何处理该异常则完全取决于该解码器的用户。某些协议(如HTTP)可能允许你返回一个特殊的响应。而在其他的情况下,唯一的选择可能就是关闭对应的连接。

以下代码展示了ByteToMessageDecoder是如何使用TooLongFrameException来通知ChannelPipeline中的其他ChannelHandler发生了帧大小溢出的。需要注意的是,如果你正在使用一个可变帧大小的协议,那么这种保护措施将是尤为重要的。

public class SafeByteToMessageDecoder extends ByteToMessageDecoder {// 扩展ByteToMessageDecoder以将字节解码为消息
private static final int MAX_FRAME_SIZE = 1024;
@Override
public void decode(ChannelHandlerContext ctx, ByteBuf in,
List<Object> out) throws Exception {
int readable = in.readableBytes();
if (readable > MAX_FRAME_SIZE) {// 检查缓冲区中是否有超过MAX_FRAME_SIZE个字节
in.skipBytes(readable);
throw new TooLongFrameException("Frame too big!");// 跳过所有的可读字节,抛出TooLongFrameException并通知ChannelHandler
}
// do something
...
}
}

三、编码器

Netty提供了一些基类,我们可以很简单的编码。同样的,编码器有下面两种类型:

  • 消息对象编码成消息对象
  • 消息对象编码成字节码

相对解码器,编码器少了一个byte-to-byte的类型,因为出站数据这样做没有意义。编码器的作用就是将处理好的数据转成字节码以便在网络中传输。对照上面列出的两种编码器类型,Netty也分别提供了两个抽象类:MessageToByteEncoder和MessageToMessageEncoder。

3.1 MessageToByteEncoder

MessageToByteEncoder是抽象类,我们自定义一个继承MessageToByteEncoder的编码器只需要实现其提供的encode(…)方法。实现代码如下:

/**
* 编码器,将Integer值编码成byte[],MessageToByteEncoder实现
*/
public class IntegerToByteEncoder extends MessageToByteEncoder<Integer> {
@Override
protected void encode(ChannelHandlerContext ctx, Integer msg, ByteBuf out) throws Exception {
out.writeInt(msg);
}
}

3.2 MessageToMessageEncoder

需要将消息编码成其他的消息时可以使用Netty提供的MessageToMessageEncoder抽象类来实现。例如将Integer编码成String,实现代码如下:

/**
* 编码器,将Integer编码成String,MessageToMessageEncoder实现
*/
public class IntegerToStringEncoder extends MessageToMessageEncoder<Integer> { @Override
protected void encode(ChannelHandlerContext ctx, Integer msg, List<Object> out) throws Exception {
out.add(String.valueOf(msg));
}
}

四、抽象的编解码器类

虽然我们一直将解码器和编码器作为单独的实体讨论,但是你有时将会发现在同一个类中管理入站和出站数据和消息的转换是很有用的。Netty的抽象编解码器类正好用于这个目的,因为它们每个都将捆绑一个解码器/编码器对,以处理我们一直在学习的这两种类型的操作。正如同你可能已经猜想到的,这些类同时实现了ChannelInboundHandler和ChannelOutboundHandler接口。
为什么我们并没有一直优先于单独的解码器和编码器使用这些复合类呢?因为通过尽可能地将这两种功能分开,最大化了代码的可重用性和可扩展性,这是Netty设计的一个基本原则。在我们查看这些抽象的编解码器类时,我们将会把它们与相应的单独的解码器和编码器进行比较和参照。

4.1 抽象类ByteToMessageCodec

让我们来研究这样的一个场景:我们需要将字节解码为某种形式的消息,可能是POJO,随后再次对它进行编码。ByteToMessageCodec将为我们处理好这一切,因为它结合了ByteToMessageDecoder 以及它的逆向——MessageToByteEncoder。下面列出了其中重要的方法。

4.1.1 decode(ChannelHandlerContext ctx,ByteBuf in,List<Object>)

只要有字节可以被消费,这个方法就将会被调用。它将入站ByteBuf 转换为指定的消息格式,并将其转发给ChannelPipeline中的下一个ChannelInboundHandler。

4.1.2 decodeLast(ChannelHandlerContext ctx,ByteBuf in,List<Object> out)

这个方法的默认实现委托给了decode()方法。它只会在Channel的状态变为非活动时被调用一次。它可以被重写以实现特殊的处理。

4.1.3 encode(ChannelHandlerContext ctx,I msg,ByteBuf out)

对于每个将被编码并写入出站ByteBuf的(类型为I的)消息来说,这个方法都将会被调用。

任何的请求/响应协议都可以作为使用ByteToMessageCodec的理想选择。例如,在某个SMTP的实现中,编解码器将读取传入字节,并将它们解码为一个自定义的消息类型,如SmtpRequest。而在接收端,当一个响应被创建时,将会产生一个SmtpResponse,其将被编码回字节以便进行传输。

4.2 抽象类MessageToMessageCodec

通过使用MessageToMessageCodec,我们可以在一个单个的类中实现将一种消息格式转换为另外一种消息格式的转换的往返过程。MessageToMessageCodec是一个参数化的类,定义如下:

public abstract class MessageToMessageCodec<INBOUND_IN,OUTBOUND_IN>

以下列出其中重要方法。

4.2.1 protected abstract decode(ChannelHandlerContext ctx,INBOUND_IN msg,List<Object> out)

这个方法被调用时会被传入INBOUND_IN 类型的消息。它将把它们解码为OUTBOUND_IN 类型的消息,这些消息将被转发给ChannelPipeline 中的下一个ChannelInboundHandler。

4.2.2 protected abstract encode(ChannelHandlerContext ctx,OUTBOUND_IN msg,List<Object> out)

对于每个OUTBOUND_IN 类型的消息,这个方法都将会被调用。这些消息将会被编码为INBOUND_IN类型的消息,然后被转发给ChannelPipeline 中的下一个ChannelOutboundHandler。

decode()方法是将INBOUND_IN类型的消息转换为OUTBOUND_IN 类型的消息,而encode()方法则进行它的逆向操作。
通俗理解,INBOUND_IN类型的消息是通过网络发送的类型,而OUTBOUND_IN类型的消息是应用程序所处理的类型。

4.3 CombinedChannelDuplexHandler类

正如我们前面所提到的,结合一个解码器和编码器可能会对可重用性造成影响。但是,有一种方法既能够避免这种惩罚,又不会牺牲将一个解码器和一个编码器作为一个单独的单元部署所带来的便利性。CombinedChannelDuplexHandler 提供了这个解决方案,其声明为:

public class CombinedChannelDuplexHandler<I extends ChannelInboundHandler,O e xtends ChannelOutboundHandler>

这个类充当了ChannelInboundHandler和ChannelOutboundHandler(该类的类型参数I和O)的容器。通过提供分别继承了解码器类和编码器类的类型,我们可以实现一个编解码器,而又不必直接扩展抽象的编解码器类。

public class CombinedByteCharCodec extends
CombinedChannelDuplexHandler<ByteToCharDecoder, CharToByteEncoder> {// 通过该解码器和编码器实现参数化CombinedByteCharCodec
public CombinedByteCharCodec() {
super(new ByteToCharDecoder(), new CharToByteEncoder());// 将委托实例传递给父类
}
}

原文地址:http://blog.csdn.net/u010853261/article/details/54575626

netty的编解码器理解(转)的更多相关文章

  1. 沉淀再出发:关于netty的一些理解和使用

    沉淀再出发:关于netty的一些理解和使用 一.前言 Netty是由JBOSS提供的一个java开源框架.Netty提供异步的.事件驱动的网络应用程序框架和工具,用以快速开发高性能.高可靠性的网络服务 ...

  2. NIO学习笔记,从Linux IO演化模型到Netty—— 究竟如何理解同步、异步、阻塞、非阻塞

    我的观点 首先,分开各自理解. 1. 同步:描述两个(或者多个)个体之间的协调关系. 比如,单线程中,methodA调用了methodB,methodB返回后,methodA才往下执行,那么称A同步调 ...

  3. 关于netty配置的理解serverBootstrap.option和serverBootstrap.childOption

    The parameters that we set using ServerBootStrap.option apply to the ChannelConfig of a newly create ...

  4. netty源码理解补充 之 DefaultChannelPipeline到底是个啥

  5. netty源码理解(三) 从channel读取数据

    下面的是ServerBootstrap 的内部类 ServerBootstrapAcceptor extends ChannelInboundHandlerAdapter 的方法 这里其实卡住了我很长 ...

  6. netty源码理解(二) serverstrap.bind()

    eventloop是一个线程,里面有一个executor封装了一个线程工厂,在启动的时候启动一个线程,传入的实现了runnable的内部类,里面调用了eventloop的run方法.

  7. netty源码理解(一):new一个NioEventLoopGroup的时候做了哪些事

    好了,回到构造方法的调用中

  8. Netty 中的消息解析和编解码器

    本篇内容主要梳理一下 Netty 中编解码器的逻辑和编解码器在 Netty 整个链路中的位置. 前面我们在分析 ChannelPipeline 的时候说到入站和出站事件的处理都在 pipeline 中 ...

  9. Netty编解码器(理论部分)

    背景知识 在了解Netty编解码之前,先回顾一下JAVA的编解码: 编码(Encode):在java中称之为序列化,把内存中易丢失的数据结构或对象状态转换成另一种可存储(存储到磁盘),可在网络间传输的 ...

随机推荐

  1. LeetCode之“链表”:在O(1)时间删除链表节点

    下边讨论暂不包括尾节点. 一般来说,我们要删除链表中的一个节点是需要知道其上一节点的.但我们真的需要吗? 其实我们可以将待删节点的下一节点的值和指向的下一节点赋予待删节点,然后删除待删节点的下一节点. ...

  2. 从硬件竞争到软实力PK——电视媒体竞争观察

    本文观点及数据摘自中广研究<三网融合月度精粹>第26期(2013年2月版),详细参考对应在线简版(http://doc.sarft.net/index.php?f=2013/02/2013 ...

  3. java语法部分一些小问题

    由于本人是个初学者希望自己的文章不会误导广大"群众",如果有错误之处还望前辈指出.谢谢! 一.键盘录入. A:导包 格式: import java.util.Scanner; 位置 ...

  4. How to Create Modifiers Using the API QP_MODIFIERS_PUB.PROCESS_MODIFIERS

    In this Document Goal   Solution   Example Scripts   Steps to verify the creation of modifier(s).   ...

  5. jquery.query-*.js 操作url

     做的项目中需要用到通过JS获取GET参数,上网找了一下,找到如下插件: 例如 当前你的URL是: http://www.xxx.com/index.php?test=1&kk=2 如果想 ...

  6. bash下如何使用bind[En]

    You can determine the character sequence emitted by a key by pressing Ctrl-v at the command line, th ...

  7. How tomcat works 读书笔记十五 Digester库 下

    在这一节里我们说说ContextConfig这个类. 这个类在很早的时候我们就已经使用了(之前那个叫SimpleContextConfig),但是在之前它干的事情都很简单,就是吧context里的co ...

  8. OpenCV——PS图层混合算法(六)

    具体的算法原理可以参考: PS图层混合算法之六(差值,溶解, 排除) // PS_Algorithm.h #ifndef PS_ALGORITHM_H_INCLUDED #define PS_ALGO ...

  9. java基础语法(二)--单列模式

    java基础语法(二)--单列模式 /** * 功能:单列模式 * @author Administrator * */ public class SingletonTest { public sta ...

  10. jvm栈-运行控制,jvm-堆运行存储共享单元

     JVM-栈 2012-09-17 15:43:53 分类: Java 原文转自:http://www.blogjava.net/nkjava/archive/2012/03/15/371971.ht ...