zoukankan      html  css  js  c++  java
  • netty源码解析(4.0)-19 ChannelHandler: codec--常用编解码实现

      数据包编解码过程中主要的工作就是:在编码过程中进行序列化,在解码过程中从Byte流中分离出数据包然后反序列化。在MessageToByteEncoder中,已经解决了序列化之后的问题,ByteToMessageDecoder中已经部分第解决了从Byte流中分离出数据包的问题。实现具体的数据包编解码,只需要实现MessageToByteEncoder的encode和ByteToMessageDecoder的decode方法即可。

      为了方便开发者使用Netty,在io.netty.handler.codec包中已经实现了一些开箱即用的编解码ChannelHandler,这些Handler包括:

    1. FixedLengthFrameDecoder : 固定长度的数据包解码。
    2. LengthFieldPrepender, LengthFieldBasedFrameDecoder: 带有长度字段的数据包编解码。
    3. LineBasedFrameDecoder: 以行字符串为一个数据包解码。
    4. StringEncoder, StringDecoder: 字符集转换器。
    5. Base64Encoder, Base64Decoder: Base64编码转换器。
    6. ProtobufEncoder, ProtobufDecoder: protoBuf序列化格式数据包的编解码。
    7. ZlibEncoder,ZlibDecoder: zlib压缩格式数据包的编解码。
    8. SnappyFramedEncoder,SnappyFramedDecoder: Snappy压缩格式数据包的编解码。

      接下来让我们来分析几个关键ChannelHandler的实现代码。

    带有长度字段的数据解码: LengthFieldBasedFrameDecoder

      之所以先从LengthFieldBasedFrameDecoder开始,是因为这个类的实现非常典型,它向我们展示了解码二进制数据包的一些常用方法:

    • 从Byte流中反序列化出一个整数。
    • 利用整数表示一个数据包的长度。
    • 使用偏移量得到数据包的开始位置。
    • 使用数据包的开始位置和长度从Byte流中提取数据包。

      这个类解码的数据包可能有三中格式:

       | length | content | : 其中length是长度字段,它表示content的长度。但是这样格式无法明确地找到数据包的开始位置,需要一个表示开始位置的字段。

       | header | length | conent | : 通过header字段确定数据包的开始位置。

       | header | length | header1 | content |: header2是一个固定长度的字段,它可能包含一些子字段。

      有一些必要的属性用来辅助解码数据包:

      byteOrder: 反序列化整数使用的字节序。

      lengthFieldOffset:  长度字段的偏移量,也是header的长度。

      lengthFieldLength: 长度字段的长度。长度字段是一个整数,它的长度可能是: 1, 2, 3, 4, 8。

      lengthAdjustment:  header1的长度。

      initialBytesToStrip: 从Byte流中提取数据包时去掉的数据长度。

      通过覆盖decode方法从Byte流中提取数据包:

    1     @Override
    2     protected final void decode(ChannelHandlerContext ctx, ByteBuf in, List<Object> out) throws Exception {
    3         Object decoded = decode(ctx, in);
    4         if (decoded != null) {
    5             out.add(decoded);
    6         }
    7     }

      代码比较简单,调用内部的decode方法完成数据包的提取,干货都在这个内部方法中。

     1     protected Object decode(ChannelHandlerContext ctx, ByteBuf in) throws Exception {
     2         if (discardingTooLongFrame) {
     3             discardingTooLongFrame(in);
     4         }
     5 
     6         if (in.readableBytes() < lengthFieldEndOffset) {
     7             return null;
     8         }
     9 
    10         int actualLengthFieldOffset = in.readerIndex() + lengthFieldOffset;
    11         long frameLength = getUnadjustedFrameLength(in, actualLengthFieldOffset, lengthFieldLength, byteOrder);
    12 
    13         if (frameLength < 0) {
    14             failOnNegativeLengthField(in, frameLength, lengthFieldEndOffset);
    15         }
    16 
    17         frameLength += lengthAdjustment + lengthFieldEndOffset;
    18 
    19         if (frameLength < lengthFieldEndOffset) {
    20             failOnFrameLengthLessThanLengthFieldEndOffset(in, frameLength, lengthFieldEndOffset);
    21         }
    22 
    23         if (frameLength > maxFrameLength) {
    24             exceededFrameLength(in, frameLength);
    25             return null;
    26         }
    27 
    28         // never overflows because it's less than maxFrameLength
    29         int frameLengthInt = (int) frameLength;
    30         if (in.readableBytes() < frameLengthInt) {
    31             return null;
    32         }
    33 
    34         if (initialBytesToStrip > frameLengthInt) {
    35             failOnFrameLengthLessThanInitialBytesToStrip(in, frameLength, initialBytesToStrip);
    36         }
    37         in.skipBytes(initialBytesToStrip);
    38 
    39         // extract frame
    40         int readerIndex = in.readerIndex();
    41         int actualFrameLength = frameLengthInt - initialBytesToStrip;
    42         ByteBuf frame = extractFrame(ctx, in, readerIndex, actualFrameLength);
    43         in.readerIndex(readerIndex + actualFrameLength);
    44         return frame;
    45     }

      调用extractFrame(42行)从Byte流中提取数据包之前的关键环节有:

    • 找到数据包的开始位置:  10行。  
    • 确定数据包的长度: 11行,调用getUnadjustedFrameLength方法反序列化到的length字段值。此时frameLength值是content的长度,还不是真正的包长度。17行,content长度加上header1的长度(lengthAjustment)再加上header和length的长度(lengthFieldEndOffset),得到的结果才是真正的包长度。 41行,最后减去需要丢掉的那部分数据的长度(initialBytesStrip),得到期望的数据包长度。
    • 处理in中数据不足一个数据包的情况: 6-7行,30-31行,返回null。
    • 清理掉in中超过最大数据包长度限制的数据: 23-24行如果in中的数据大于frameLength丢掉这个数据包,否则丢掉in中现有的所有数据,记录下还需要丢弃的数据长度,下次在 2-3行丢掉剩下长度的数据。 
    • 处理包长度错误的情况: 13-14行,19-20行丢掉header和length。34-35行丢掉frameLength长度的数据。 

      最后得到得到数据包的开始位置和长度之和从in缓冲区中提取出一个完整数据包,并修改in的读位置, 43-44行。  

      getUnadjustedFrameLength中反序列环length的值是计算数据包长度的关键,这个方法的实现如下:

     1     protected long getUnadjustedFrameLength(ByteBuf buf, int offset, int length, ByteOrder order) {
     2         buf = buf.order(order);
     3         long frameLength;
     4         switch (length) {
     5         case 1:
     6             frameLength = buf.getUnsignedByte(offset);
     7             break;
     8         case 2:
     9             frameLength = buf.getUnsignedShort(offset);
    10             break;
    11         case 3:
    12             frameLength = buf.getUnsignedMedium(offset);
    13             break;
    14         case 4:
    15             frameLength = buf.getUnsignedInt(offset);
    16             break;
    17         case 8:
    18             frameLength = buf.getLong(offset);
    19             break;
    20         default:
    21             throw new DecoderException(
    22                     "unsupported lengthFieldLength: " + lengthFieldLength + " (expected: 1, 2, 3, 4, or 8)");
    23         }
    24         return frameLength;
    25     }

      2行设置ByteBuf反序列化整数时使用的字节序,默认BIG_ENDIAN。这个值可以在调用构造方法时设置。

      4-24行,根据length字段的不同长度,使用ByteBuf的不同方法反序列化length的值。length字段的长度只能是: 1,2,3,4,8之一。

      

      以上是LengthFieldBasedFrameDecoder实现的核心代码的分析。这个类把一个比较关键的问题留给子类实现,就是如何处理header或header1字段的处理, header1可以没有,但一定要有header。他们内部可以有比较复杂的结构,而且长度是约定好的,不会变。这个就给子类留下了比较大的扩展空间。通过扩展这个类,我们可以实现任意格式数据包的提取功能。

    带有长度字段的数据包编码: LengthFieldPrepender

      这个类实现的功能和LengthFieldBasedFrameDecoder相反,也比它要简单很多,只是简单地在已经序列化好的数据前面加上长度字段。

     1     @Override
     2     protected void encode(ChannelHandlerContext ctx, ByteBuf msg, ByteBuf out) throws Exception {
     3         int length = msg.readableBytes() + lengthAdjustment;
     4         if (lengthIncludesLengthFieldLength) {
     5             length += lengthFieldLength;
     6         }
     7 
     8         if (length < 0) {
     9             throw new IllegalArgumentException(
    10                     "Adjusted frame length (" + length + ") is less than zero");
    11         }
    12 
    13         switch (lengthFieldLength) {
    14         case 1:
    15             if (length >= 256) {
    16                 throw new IllegalArgumentException(
    17                         "length does not fit into a byte: " + length);
    18             }
    19             out.writeByte((byte) length);
    20             break;
    21         case 2:
    22             if (length >= 65536) {
    23                 throw new IllegalArgumentException(
    24                         "length does not fit into a short integer: " + length);
    25             }
    26             out.writeShort((short) length);
    27             break;
    28         case 3:
    29             if (length >= 16777216) {
    30                 throw new IllegalArgumentException(
    31                         "length does not fit into a medium integer: " + length);
    32             }
    33             out.writeMedium(length);
    34             break;
    35         case 4:
    36             out.writeInt(length);
    37             break;
    38         case 8:
    39             out.writeLong(length);
    40             break;
    41         default:
    42             throw new Error("should not reach here");
    43         }
    44 
    45         out.writeBytes(msg, msg.readerIndex(), msg.readableBytes());
    46     }

      3-11行,计算并检查长度字段的值。

        13-43行,在数据前面加上长度字段。

      45行,把数据追加到长度长度字段之后。

    行数据包和固定长度数据包的提取

      LineBasedFrameDecoder: 用于从字符串流中按行提取数据包。用" "或" "作为前一个数据包的结束标志和下一个数据包的开始标志,也是根据这个标志算出数据包的长度。

      FixedLengthFrameDecoder: 用于从Byte流中安固定长度提取数据包。数据包没有明确的开始标志或结束标志,只是简单地根据约定的长度提取数据包。

      这个中数据包提取方式对数据源可靠性要求较高,实际应用中要多加小心。

      

    字符集转换

      StringEncoder, StringDecoder这两个类用于把字符串从一种字符集转换成另外一种字符集,不涉及数据包的提取。如:GBK和UTF-8之间的转换。

    Base64编解码

      Base64Encoder, Base64Decoder这个两个类用于base64的编解码,不涉及数据包的提取。

      

    ProtoBuf编解码

      ProtobufEncoder, ProtobufDecoder用ProtoBuf格式数据包的编解码,不涉及数据包的的提取。

    压缩和解压缩

      ZlibEncoder,ZlibDecoder: 对zlib和gzip压缩和解压缩算法的支持,不涉及数据包的提取。

      SnappyFramedEncoder,SnappyFramedDecoder: 最Snappy压缩和解压算法的支持,不涉及数据包的提取。

     本章小结

      本章分析了分析了Netty使用编解码框架实现特定用途的编解码工具,其中最重要的是LengthFieldPrepender, LengthFieldBasedFrameDecoder类,这两个类展示了对任意二进制数据包打包,和从Byte流中提取二进制数据包的常用方法,理解了这两个类的实现,就能针对任意类型数据包实现自己的编解码Handler。其他的类比较简单,大部分不涉及数据包的提取,只涉及到一些常用的算法和编码格式,Netty在这里只是提供了一些开箱即用的工具。

      前讲过LengthFieldBasedFrameDecoder把对header字段的处理留给子类,这意味着子类可以通过设计自己的header实现更加健壮的,安全的同时兼顾性能的数据包,关于这个话题将在下一章详细讲解。

      

      

  • 相关阅读:
    Spring RESTful Web Services
    Uploadify自定义提示信息
    linux下查找进程及终止进程操作的相关命令
    SecureCRT+SecureFX 7.1.1.264整合版
    关于JSP的语法和常用的内置对象,EL表达式的使用方法
    Servlet的几个常用对象
    使用bootstrap写的简单页面
    bootstrap的使用方法
    EasyUi的使用方法
    Ajax与jQuery的使用方法
  • 原文地址:https://www.cnblogs.com/brandonli/p/11359680.html
Copyright © 2011-2022 走看看