Netty 编码器和解码器

    将 Request 对象转为 Memcached 所需的字节序列,Netty 需要用 MemcachedRequest 来编码成另外一种格式。这里所说的另外一种格式不单单是从对象转为字节,也可以是从对象转为对象,或者是从对象转为字符串等。编码器的内容可以详见第七章。

    Netty 提供了一个抽象类称为 MessageToByteEncoder。它提供了一个抽象方法,将一条消息(在本例中我们 MemcachedRequest 对象)转为字节。你显示什么信息实现通过使用 Java 泛型可以处理;例如 , MessageToByteEncoder 说这个编码器要编码的对象类型是 MemcachedRequest

    MessageToByteEncoder 和 Java 泛型

    使用 MessageToByteEncoder 可以绑定特定的参数类型。如果你有多个不同的消息类型,在相同的编码器里,也可以使用MessageToByteEncoder,注意检查消息的类型即可

    这也适用于解码器,除了解码器将一系列字节转换回一个对象。
    这个 Netty 的提供了 ByteToMessageDecoder 类,而不是提供一个编码方法用来实现解码。在接下来的两个部分你看看如何实现一个 Memcached 解码器和编码器。在你做之前,应该意识到在使用 Netty 时,你不总是需要自己提供编码器和解码器。自所以现在这么做是因为 Netty 没有对 Memcached 内置支持。而 HTTP 以及其他标准的协议,Netty 已经是提供的了。

    编码器和解码器

    请注意,为了程序简单,我们的编码器和解码器不检查任何值的最大大小。在实际实现中你需要一些验证检查,如果检测到违反协议,则使用 EncoderException 或 DecoderException(或一个子类)。

    本节我们将简要介绍编码器的实现。正如我们提到的,编码器负责编码消息为字节序列。这些字节可以通过网络发送到远端。为了发送请求,我们首先创建 MemcachedRequest 类,稍后编码器实现会编码为一系列字节。下面的清单显示了我们的 MemcachedRequest 类

    Listing 14.1 Implementation of a Memcached request

    1. 这个类将会发送请求到 Memcached server
    2. 幻数,它可以用来标记文件或者协议的格式
    3. opCode,反应了响应的操作已经创建了
    4. 执行操作的 key
    5. 使用的额外的 flag
    6. 表明到期时间
    7. body
    8. 请求的 id。这个id将在响应中回显。
    9. compare-and-check 的值
    10. 如果有额外的使用,将返回 true

    你如果想实现 Memcached 的其余部分协议,你只需要将 client.op(op 任何新的操作添加)转换为其中一个方法请求。我们需要两个更多的支持类,在下一个清单所示

    Listing 14.2 Possible Memcached operation codes and response statuses

    一个 Opcode 告诉 Memcached 要执行哪些操作。每个操作都由一个字节表示。同样的,当 Memcached 响应一个请求,响应头中包含两个字节代表响应状态。状态和 Opcode 类表示这些 Memcached 的构造。这些操作码可以使用当你构建一个新的 MemcachedRequest 指定哪个行动应该由它引发的。

    但现在可以集中精力在编码器上:

    1. 该类是负责编码 MemachedRequest 为一系列字节
    2. 转换的 key 和实际请求的 body 到字节数组
    3. 计算 body 大小
    4. 写幻数到 ByteBuf 字节
    5. 写 opCode 作为字节
    6. 写 key 长度z作为 short
    7. 编写额外的长度作为字节
    8. 为保留字节写为 short ,后面的 Memcached 版本可能使用
    9. 写 body 的大小作为 long
    10. 写 opaque 作为 int
    11. 写 cas 作为 long。这个是头文件的最后部分,在 body 的开始
    12. 编写额外的 flag 和到期时间为 int
    13. 写 key
    14. 这个请求完成后 写 body。

    总结,编码器 使用 Netty 的 ByteBuf 处理请求,编码 MemcachedRequest 成一套正确排序的字节。详细步骤为:

    • 写幻数字节。
    • 写 opcode 字节。
    • 写 key 长度(2字节)。
    • 写额外的长度(1字节)。
    • 写数据类型(1字节)。
    • 为保留字节写 null 字节(2字节)。
    • 写 body 长度(4字节- 32位整数)。
    • 写 opaque(4个字节,一个32位整数在响应中返回)。
    • 写 CAS(8个字节)。
    • 写 额外的(flag 和 到期,4字节)= 8个字节
    • 写 key
    • 写 值

    无论你放入什么到输出缓冲区( 调用 ByteBuf) Netty 的将向服务器发送被写入请求。下一节将展示如何进行反向通过解码器工作。

    实现 Memcached 解码器

    将 MemcachedRequest 对象转为字节序列,Memcached 仅需将字节转到响应对象返回即可。

    先见一个 POJO:

    Listing 14.7 Implementation of a MemcachedResponse

    1. 幻数
    2. opCode,这反映了创建操作的响应
    3. 数据类型,这表明这个是基于二进制还是文本
    4. 响应的状态,这表明如果请求是成功的
    5. 惟一的 id
    6. compare-and-set 值
    7. 使用额外的 flag
    8. 表示该值存储的一个有效期
    9. 响应创建的 key
    10. 实际数据

    下面为 MemcachedResponseDecoder, 使用了 ByteToMessageDecoder 基类,用于将 字节序列转为 MemcachedResponse

    Listing 14.4 MemcachedResponseDecoder class

    1. 类负责创建的 MemcachedResponse 读取字节
    2. 代表当前解析状态,这意味着我们需要解析的头或 body
    3. 根据解析状态切换
    4. 如果不是至少24个字节是可读的,它不可能读整个头部,所以返回这里,等待再通知一次数据准备阅读
    5. 阅读所有头的字段
    6. 检查是否足够的数据是可读用来读取完整的响应的 body。长度是从头读取
    7. 检查如果有任何额外的 flag 用于读,如果是这样做
    8. 检查如果响应包含一个 expire 字段,有就读它
    9. 检查响应是否包含一个 key ,有就读它
    10. 从前面读取字段和数据构造一个新的 MemachedResponse

    当我们解码整个消息,我们创建一个 MemcachedResponse 并将其添加到输出列表。任何对象添加到该列表将被转发到下一个ChannelInboundHandler 在 ChannelPipeline,因此允许处理。