​ Netty强大的地方,是他能方便的实现自定义协议的网络传输。在上一篇文章中,通过使用Netty封装好的工具类,实现了简单的http服务器。在接下来的文章中,我们看看怎么使用他来搭建自定义协议的服务器。要做到这点,第一步要做的,就是要自定义编码器和解码器,这就是我们这一章主要讲的内容。

什么是Decoder和Encoder

​ 在学习Decoder和Encoder之前,首先要了解他们在具体是个什么东西。在Netty里面,有四个核心概念,这个在第一篇文章提到的,他们的分别是:

  • Channel,一个客户端与服务器通信的通道

  • ChannelHandler,业务逻辑处理器,分为ChannelInboundHandler和ChannelOutboundHandler

    • ChannelInboundHandler,输入数据处理器
    • ChannelOutboundHandler,输出业务处理器

    通常情况下,业务逻辑都是存在于ChannelHandler之中

  • ChannelPipeline,用于存放ChannelHandler的容器

  • ChannelContext,通信管道的上下文

    他们之间的交流流程如下图:

                                                                           Channel关系图

他们的交互流程是:

  1. 事件传递给 ChannelPipeline 的第一个 ChannelHandler
  2. ChannelHandler 通过关联的 ChannelHandlerContext 传递事件给 ChannelPipeline 中的 下一个
  3. ChannelHandler 通过关联的 ChannelHandlerContext 传递事件给 ChannelPipeline 中的 下一个

而我们本文所需要详细讲的Decoder和Encoder,他们分别就是ChannelInboundHandler和ChannelOutboundHandler,分别用于在数据流进来的时候将字节码转换为消息对象和数据流出去的时候将消息对象转换为字节码。

Encoder

​ Encoder最重要的实现类是MessageToByteEncoder<T>,这个类的作用就是将消息实体T从对象转换成byte,写入到ByteBuf,然后再丢给剩下的ChannelOutboundHandler传给客户端,流程图如下:

Encoder流程图

Table 7.3 MessageToByteEncoder API

方法名称 描述
encode The encode method is the only abstract method you need to implement. It is called with the outbound message, which this class will encodes to a ByteBuf. The ByteBuf is then forwarded to the next ChannelOutboundHandler in the ChannelPipeline.

encode方法是继承MessageToByteEncoder唯一需要重写的方法,可见其简单程度。也是因为Encoder相比于Decoder更为简单,在这里也不多做赘述,直接上代码:

public class ShortToByteEncoder extends
MessageToByteEncoder<Short> { //1
@Override
public void encode(ChannelHandlerContext ctx, Short msg, ByteBuf out)
throws Exception {
out.writeShort(msg); //2
}
}

Decoder

​ 和Encoder一样,decoder就是在服务端收到数据的时候,将字节流转换为实体对象Message。但是和Encoder的处理逻辑不一样,数据传到服务端有可能不是一次请求就能完成的,中间可能需要经过几次数据传输,并且每一次传输传多少数据也是不确定的,所以它有两个重要方法:

Table 7.1 ByteToMessageDecoder API

方法名称 描述
decode This is the only abstract method you need to implement. It is called with a ByteBuf having the incoming bytes and a List into which decoded messages are added. decode() is called repeatedly until the List is empty on return. The contents of the List are then passed to the next handler in the pipeline.
decodeLast The default implementation provided simply calls decode(). This method is called once, when the Channel goes inactive. Override to provide special

​ decode和decodeLast的不同之处,在于他们的调用时机不同,正如描述所说,decodeLast只有在Channel的生命周期结束之前会调用一次,默认是调用decode方法。

​ 同样是ToInteger的解码器,他的代码如下:

public class ToIntegerDecoder extends ByteToMessageDecoder {  //1

    @Override
public void decode(ChannelHandlerContext ctx, ByteBuf in, List<Object> out)
throws Exception {
if (in.readableBytes() >= 4) { //2
out.add(in.readInt()); //3
}
}
}

​ 从这段代码可以看出,因为不知道这次请求发过来多少数据,所以每次都要判断byte长度够不够4,如果你的数据长度更长,且不固定的话,这里的逻辑会变得非常复杂。所以在这里介绍另一个我们常用的解码器 :ReplayingDecoder。

ReplayingDecoder

​ ReplayingDecoder 是 byte-to-message 解码的一种特殊的抽象基类,读取缓冲区的数据之前需要检查缓冲区是否有足够的字节,使用ReplayingDecoder就无需自己检查;若ByteBuf中有足够的字节,则会正常读取;若没有足够的字节则会停止解码。

​ RelayingDecoder在使用的时候需要搞清楚的两个方法是checkpoint(S s)和state(),其中checkpoint的参数S,代表的是ReplayingDecoder所处的状态,一般是枚举类型。RelayingDecoder是一个有状态的Handler,状态表示的是它目前读取到了哪一步,checkpoint(S s)是设置当前的状态,state()是获取当前的状态。

​ 在这里我们模拟一个简单的Decoder,假设每个包包含length:int和content:String两个数据,其中length可以为0,代表一个空包,大于0的时候代表content的长度。代码如下:

public class LiveDecoder extends ReplayingDecoder<LiveDecoder.LiveState> { //1

    public enum LiveState { //2
LENGTH,
CONTENT
} private LiveMessage message = new LiveMessage(); public LiveDecoder() {
super(LiveState.LENGTH); // 3
} @Override
protected void decode(ChannelHandlerContext channelHandlerContext, ByteBuf byteBuf, List<Object> list) throws Exception {
switch (state()) { // 4
case LENGTH:
int length = byteBuf.readInt();
if (length > 0) {
checkpoint(LiveState.CONTENT); // 5
} else {
list.add(message); // 6
}
break;
case CONTENT:
byte[] bytes = new byte[message.getLength()];
byteBuf.readBytes(bytes);
String content = new String(bytes);
message.setContent(content);
list.add(message);
break;
default:
throw new IllegalStateException("invalid state:" + state());
}
}
}
  1. 继承ReplayingDecoder,泛型LiveState,用来表示当前读取的状态
  2. 描述LiveState,有读取长度和读取内容两个状态
  3. 初始化的时候设置为读取长度的状态
  4. 读取的时候通过state()方法来确定当前处于什么状态
  5. 如果读取出来的长度大于0,则设置为读取内容状态,下一次读取的时候则从这个位置开始
  6. 读取完成,往结果里面放解析好的数据

​ 以上就是ReplayingDecoder的使用方法,他比ByteToMessageDecoder更加灵活,能够通过巧妙的方式来处理复杂的业务逻辑,但是也是因为这个原因,使得ReplayingDecoder带有一定的局限性:

  • 不是所有的标准 ByteBuf 操作都被支持,如果调用一个不支持的操作会抛出 UnreplayableOperationException

  • ReplayingDecoder 略慢于 ByteToMessageDecoder

    所以,如果不引入过多的复杂性 使用 ByteToMessageDecoder 。否则,使用ReplayingDecoder。

MessageToMessage

​ Encoder和Decoder除了能完成Byte和Message的相互转换之外,为了处理复杂的业务逻辑,还能帮助使用者完成Message和Message的相互转换,我们熟悉的Http协议的处理,其中就用到了很多MessageToMessage的派生类。

​ 因为使用方法和以上的Decoder/Encoder类似,在这里就不多做赘述了。

​ 以上是我在学习Netty过程中的一些笔记,其中部分内容源自Netty实战精髓,如有理解不当之处,欢迎指出,一起讨论。

Netty入门3之----Decoder和Encoder的更多相关文章

  1. Netty入门教程——认识Netty

    什么是Netty? Netty 是一个利用 Java 的高级网络的能力,隐藏其背后的复杂性而提供一个易于使用的 API 的客户端/服务器框架. Netty 是一个广泛使用的 Java 网络编程框架(N ...

  2. Netty入门

    一.NIO Netty框架底层是对NIO的高度封装,所以想要更好的学习Netty之前,应先了解下什么是NIO - NIO是non-blocking的简称,在jdk1.4 里提供的新api,他的他的特性 ...

  3. Netty入门(三)之web服务器

    Netty入门(三)之web服务器 阅读前请参考 Netty入门(一)之webSocket聊天室 Netty入门(二)之PC聊天室 有了前两篇的使用基础,学习本文也很简单!只需要在前两文的基础上稍微改 ...

  4. Netty入门(二)之PC聊天室

    参看Netty入门(一):Netty入门(一)之webSocket聊天室 Netty4.X下载地址:http://netty.io/downloads.html 一:服务端 1.SimpleChatS ...

  5. Netty4 自定义Decoder,Encoder进行对象传递

    首先我们必须知道Tcp粘包和拆包的,TCP是个“流”协议,所谓流,就是没有界限的一串数据,TCP底层并不了解上层业务数据的具体含义,它会根据TCP缓冲区的实际数据进行包的划分,一个完整的包可能会被拆分 ...

  6. Netty入门(八)构建Netty HTTP/HTTPS应用

    HTTP/HTTPS 是最常见的一种协议,这节主要是看一下 Netty 提供的 ChannelHaandler. 一.HTTP Decoder,Encoder 和 Codec HTTP 是请求-响应模 ...

  7. Netty入门4之----如何实现长连接

    ​ 前面三章介绍了Netty的一些基本用法,这一章介绍怎么使用Netty来实现一个简单的长连接demo. 关于长连接的背景知识,可以参考<如何使用Socket实现长连接> ​ 一个简单的长 ...

  8. Netty入门之客户端与服务端通信(二)

    Netty入门之客户端与服务端通信(二) 一.简介 在上一篇博文中笔者写了关于Netty入门级的Hello World程序.书接上回,本博文是关于客户端与服务端的通信,感觉也没什么好说的了,直接上代码 ...

  9. Netty入门之HelloWorld

    Netty系列入门之HelloWorld(一) 一. 简介 Netty is a NIO client server framework which enables quick and easy de ...

随机推荐

  1. this小结

    this 对象是在运行时基于函数的执行环境绑定的: 全局函数中, this 等于 window 函数被作为某个对象的方法调用时, this 等于那个对象 匿名函数的执行环境具有全局性, this 指向 ...

  2. C语言字符串的操作

    C语言字符串操作函数 1. 字符串反转 - strRev2. 字符串复制 - strcpy3. 字符串转化为整数 - atoi4. 字符串求长 - strlen5. 字符串连接 - strcat6. ...

  3. Java 数组算法列题解析

    1.声明一个char类型的数组,  从键盘录入6个字符[1]遍历输出[2]排序[3]把char数组转化成一个逆序的数组 总结:分析问题时,先问自己,需要用到什么? 对于这题,第一步:先写一个main方 ...

  4. 计算机网络c++实现截断二进制指数退避算法

    #include<iostream> #include<vector> #include <stdio.h> #include<stdlib.h> // ...

  5. Java 数据表映射

    一对多映射 class Province { //每一个类就相当于数据库中的一个表: private int pid ; private String name ; private City citi ...

  6. unittest 几个重要概念

    unittest是一个python版本的junit,junit是java中的单元测试框架,unittest实现了很多junit中的概念,比如我们非常熟悉的test case, test suite等, ...

  7. 设置全局theme及读取theme方法

    在web.config中设置了默认的Theme,其部分如下的配置节点: <system.web> <pages theme="Default" controlRe ...

  8. 江铖:乳腺癌识别By AI

    欢迎大家前往腾讯云+社区,获取更多腾讯海量技术实践干货哦~ 本文由云加社区技术沙龙 发表于云+社区专栏 演讲嘉宾:江铖,腾讯觅影高级研究员.多年以来一直从事计算机视觉相关的研究.加入腾讯以后,负责腾讯 ...

  9. Hibernate各种查询操作(二)

    一.QBC的查询方式 使用QBC不在需要写hql语句,而是使用criteria对象的各种方法来实现. 1.查询所有 //使用QBC方式查询所有 @Test public void test11(){ ...

  10. python风味之list创建

    单重for循环 >>> [x * x for x in xrange(10)] [0, 1, 4, 9, 16, 25, 36, 49, 64, 81] 单重for循环+if条件 & ...