简介

在之前的系列文章中,我们提到了netty中的channel只接受ByteBuf类型的对象,如果不是ByteBuf对象的话,需要用编码和解码器对其进行转换,今天来聊一下netty自定义的编码和解码器实现中需要注意的问题。

自定义编码器和解码器的实现

在介绍netty自带的编码器和解码器之前,告诉大家怎么实现自定义的编码器和解码器。

netty中所有的编码器和解码器都是从ChannelInboundHandlerAdapter和ChannelOutboundHandlerAdapter衍生而来的。

对于ChannelOutboundHandlerAdapter来说,最重要的两个类是MessageToByteEncoder 和 MessageToMessageEncoder

MessageToByteEncoder是将消息编码成为ByteBuf,这个类也是我们自定义编码最常用的类,直接继承这个类并实现encode方法即可。注意到这个类有一个泛型,这个泛型指定的就是消息的对象类型。

例如我们想将Integer转换成为ByteBuf,可以这样写:

  1. public class IntegerEncoder extends MessageToByteEncoder<Integer> {
  2. @Override
  3. public void encode(ChannelHandlerContext ctx, Integer msg, ByteBuf out)
  4. throws Exception {
  5. out.writeInt(msg);
  6. }
  7. }

MessageToMessageEncoder是在消息和消息之间进行转换,因为消息并不能直接写入到channel中,所以需要和MessageToByteEncoder配合使用。

下面是一个Integer到String的例子:

  1. public class IntegerToStringEncoder extends
  2. MessageToMessageEncoder<Integer> {
  3. @Override
  4. public void encode(ChannelHandlerContext ctx, Integer message, List<Object> out)
  5. throws Exception {
  6. out.add(message.toString());
  7. }
  8. }

对于ChannelInboundHandlerAdapter来说,最重要的两个类是ByteToMessageDecoder和MessageToMessageDecoder

ByteToMessageDecoder是将ByteBuf转换成对应的消息类型,我们需要继承这个类,并实现decode方法,下面是一个从ByteBuf中读取所有可读的字节,并将结果放到一个新的ByteBuf中,

  1. public class SquareDecoder extends ByteToMessageDecoder {
  2. @Override
  3. public void decode(ChannelHandlerContext ctx, ByteBuf in, List<Object> out)
  4. throws Exception {
  5. out.add(in.readBytes(in.readableBytes()));
  6. }
  7. }

MessageToMessageDecoder是消息和消息之间的转换,同样的只需要实现decode方法即可,如下从String转换到Integer:

  1. public class StringToIntegerDecoder extends
  2. MessageToMessageDecoder<String> {
  3. @Override
  4. public void decode(ChannelHandlerContext ctx, String message,
  5. List<Object> out) throws Exception {
  6. out.add(message.length());
  7. }
  8. }

ReplayingDecoder

上面的代码看起来很简单,但是在实现的过程中还有一些问题要注意。

对于Decoder来说,我们从ByteBuf中读取数据,然后进行转换。但是在读取的过程中,并不知道ByteBuf中数据的变动情况,有可能在读取的过程中ByteBuf还没有准备好,那么就需要在读取的时候对ByteBuf中可读字节的大小进行判断。

比如我们需要解析一个数据结构,这个数据结构的前4个字节是一个int,表示后面byte数组的长度,我们需要先判断ByteBuf中是否有4个字节,然后读取这4个字节作为Byte数组的长度,然后再读取这个长度的Byte数组,最终得到要读取的结果,如果其中的某一步出现问题,或者说可读的字节长度不够,那么就需要直接返回,等待下一次的读取。如下所示:

  1. public class IntegerHeaderFrameDecoder extends ByteToMessageDecoder {
  2. @Override
  3. protected void decode(ChannelHandlerContext ctx,
  4. ByteBuf buf, List<Object> out) throws Exception {
  5. if (buf.readableBytes() < 4) {
  6. return;
  7. }
  8. buf.markReaderIndex();
  9. int length = buf.readInt();
  10. if (buf.readableBytes() < length) {
  11. buf.resetReaderIndex();
  12. return;
  13. }
  14. out.add(buf.readBytes(length));
  15. }
  16. }

这种判断是比较复杂同时也是可能出错的,为了解决这个问题,netty提供了 ReplayingDecoder用来简化上面的操作,在ReplayingDecoder中,假设所有的ByteBuf已经处于准备好的状态,直接从中间读取即可。

上面的例子用ReplayingDecoder重写如下:

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

它的实现原理是去尝试读取对应的字节信息,如果没有读到,则抛出异常,ReplayingDecoder接收到异常之后,会重新调用decode方法。

虽然ReplayingDecoder使用起来非常简单,但是它有两个问题。

第一个问题是性能问题,因为会去重复调用decode方法,如果ByteBuf本身并没有变化,就会导致重复decode同一个ByteBuf,照成性能的浪费。解决这个问题就是在在decode的过程中分阶段进行,比如上面的例子中,我们需要先读取Byte数组的长度,然后再读取真正的byte数组。所以在读完byte数组长度之和,可以调用checkpoint()方法做一个保存点,下次再执行decode方法的时候就可以跳过这个保存点,继续后续的执行过程,如下所示:

  1. public enum MyDecoderState {
  2. READ_LENGTH,
  3. READ_CONTENT;
  4. }
  5. public class IntegerHeaderFrameDecoder
  6. extends ReplayingDecoder<MyDecoderState> {
  7. private int length;
  8. public IntegerHeaderFrameDecoder() {
  9. // Set the initial state.
  10. super(MyDecoderState.READ_LENGTH);
  11. }
  12. @Override
  13. protected void decode(ChannelHandlerContext ctx,
  14. ByteBuf buf, List<Object> out) throws Exception {
  15. switch (state()) {
  16. case READ_LENGTH:
  17. length = buf.readInt();
  18. checkpoint(MyDecoderState.READ_CONTENT);
  19. case READ_CONTENT:
  20. ByteBuf frame = buf.readBytes(length);
  21. checkpoint(MyDecoderState.READ_LENGTH);
  22. out.add(frame);
  23. break;
  24. default:
  25. throw new Error("Shouldn't reach here.");
  26. }
  27. }
  28. }

第二个问题是同一个实例的decode方法可能会被调用多次,如果我们在ReplayingDecoder中有私有变量的话,则需要考虑对这个私有变量的清洗工作,避免多次调用造成的数据污染。

总结

通过继承上面的几个类,我们就可以自己实现编码和解码的逻辑了。但是好像还有点问题,自定义编码和解码器是不是太复杂了?还需要判断要读取的byte数组的大小。有没有更加简单的方法呢?

有的,敬请期待netty系列的下一篇文章:netty自带的编码器和解码器.

本文的例子可以参考:learn-netty4

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

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

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

netty系列之:自定义编码和解码器要注意的问题的更多相关文章

  1. netty系列之:自定义编码解码器

    目录 简介 自定义编码器 自定义解码器 添加编码解码器到pipeline 计算2的N次方 总结 简介 在之前的netty系列文章中,我们讲到了如何将对象或者String转换成为ByteBuf,通过使用 ...

  2. netty系列之:内置的Frame detection

    目录 简介 Frame detection DelimiterBasedFrameDecoder FixedLengthFrameDecoder LengthFieldBasedFrameDecode ...

  3. netty系列之:netty中的frame解码器

    目录 简介 LineBasedFrameDecoder DelimiterBasedFrameDecoder FixedLengthFrameDecoder LengthFieldBasedFrame ...

  4. netty系列之:netty中的核心编码器base64

    目录 简介 netty codec的实现逻辑 netty中Base64的实现 netty中的base64编码和解码器 Base64Encoder Base64Decoder 总结 简介 我们知道数据在 ...

  5. netty系列之:netty中的核心编码器bytes数组

    目录 简介 byte是什么 netty中的byte数组的工具类 netty中byte的编码器 总结 简介 我们知道netty中数据传输的核心是ByteBuf,ByteBuf提供了多种数据读写的方法,包 ...

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

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

  7. Netty系列(四)TCP拆包和粘包

    Netty系列(四)TCP拆包和粘包 一.拆包和粘包问题 (1) 一个小的Socket Buffer问题 在基于流的传输里比如 TCP/IP,接收到的数据会先被存储到一个 socket 接收缓冲里.不 ...

  8. netty系列之:使用netty搭建websocket服务器

    目录 简介 netty中的websocket websocket的版本 FrameDecoder和FrameEncoder WebSocketServerHandshaker WebSocketFra ...

  9. netty系列之:分离websocket处理器

    目录 简介 netty的消息处理 处理WebSocketFrame 处理HTTP 编码和解码器 总结 简介 在上一篇文章中,我们使用了netty构建了可以处理websocket协议的服务器,在这个服务 ...

随机推荐

  1. 企业如何通过CRM系统使销售周期缩短

    企业为什么要缩短销售周期?因为这意味着可以节约更多开支,从而达到企业利润最大化.但是有不少企业尤其是B2B行业,销售周期都在三个月以上.通过调查发现,很多企业在客户信息和销售管道上缺乏管理和策略.Zo ...

  2. gRPC 拦截器能做些什么?

    什么是拦截器? 拦截器是一种横切维度的功能延展. 具象说明一下,高速收费站就是一种拦截器.它可以做什么?收费,查证,交通控制等等,面向所有穿行过往的车辆. gRPC 拦截器主要分为两种:客户端拦截器( ...

  3. Python装饰器-给你的咖啡加点料

    今天你的咖啡加糖了吗? 让我们通过一个简单的例子来引出装饰器的概念及用法.在引出装饰器之前,我们先来了解一下函数的概念. 一.函数回顾 1.在python中函数是一等公民,函数也是对象.我们可以把函数 ...

  4. MyBatis:条件构造器QueryWrapper方法详解

    QueryWrapper 说明:      继承自 AbstractWrapper ,自身的内部属性 entity 也用于生成 where 条件及 LambdaQueryWrapper, 可以通过 n ...

  5. 删除表的语句(drop truncate delete)

    p.p1 { margin: 0; font: 12px ".PingFang SC" } p.p2 { margin: 0; font: 12px "Helvetica ...

  6. spring cloud 微服务介绍(转)

    一.理解微服务   我们通过软件架构演进过程来理解什么是微服务,软件架构的发展经历了从单体结构.垂直架构.SOA架构到微服务架构的过程. 1. 单体架构 1.1 特点(1)所有的功能集成在一个项目工程 ...

  7. [网络流24题]最长k可重线段集[题解]

    最长 \(k\) 可重线段集 题目大意 给定平面 \(x-O-y\) 上 \(n\) 个开线段组成的集合 \(I\) ,和一个正整数 \(k\) .试设计一个算法,从开线段集合 \(I\) 中选取开线 ...

  8. YAOI Round #1 题解

    前言 比赛网址:http://47.110.12.131:9016/contest/3 总体来说,这次比赛是有一定区分度的, \(\text{ACM}\) 赛制也挺有意思的. 题解 A. 云之彼端,约 ...

  9. TCP连接的11种状态

    传输控制协议(TCP,Transmission Control Protocol)是一种面向连接的.可靠的.基于字节流的传输层通信协议.TCP协议主要针对三次握手建立连接和四次挥手断开连接,其中包括了 ...

  10. springMVC-6-restful_CRUD

    1.大体框架 POJO层代码 Employee @Data public class Employee { private Integer id; private String lastName; p ...