Netty入门3之----Decoder和Encoder
Netty强大的地方,是他能方便的实现自定义协议的网络传输。在上一篇文章中,通过使用Netty封装好的工具类,实现了简单的http服务器。在接下来的文章中,我们看看怎么使用他来搭建自定义协议的服务器。要做到这点,第一步要做的,就是要自定义编码器和解码器,这就是我们这一章主要讲的内容。
什么是Decoder和Encoder
在学习Decoder和Encoder之前,首先要了解他们在具体是个什么东西。在Netty里面,有四个核心概念,这个在第一篇文章提到的,他们的分别是:
Channel,一个客户端与服务器通信的通道
ChannelHandler,业务逻辑处理器,分为ChannelInboundHandler和ChannelOutboundHandler
- ChannelInboundHandler,输入数据处理器
- ChannelOutboundHandler,输出业务处理器
通常情况下,业务逻辑都是存在于ChannelHandler之中
ChannelPipeline,用于存放ChannelHandler的容器
ChannelContext,通信管道的上下文
他们之间的交流流程如下图:
Channel关系图
他们的交互流程是:
- 事件传递给 ChannelPipeline 的第一个 ChannelHandler
- ChannelHandler 通过关联的 ChannelHandlerContext 传递事件给 ChannelPipeline 中的 下一个
- ChannelHandler 通过关联的 ChannelHandlerContext 传递事件给 ChannelPipeline 中的 下一个
而我们本文所需要详细讲的Decoder和Encoder,他们分别就是ChannelInboundHandler和ChannelOutboundHandler,分别用于在数据流进来的时候将字节码转换为消息对象和数据流出去的时候将消息对象转换为字节码。
Encoder
Encoder最重要的实现类是MessageToByteEncoder<T>,这个类的作用就是将消息实体T从对象转换成byte,写入到ByteBuf,然后再丢给剩下的ChannelOutboundHandler传给客户端,流程图如下:
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());
}
}
}
- 继承ReplayingDecoder,泛型LiveState,用来表示当前读取的状态
- 描述LiveState,有读取长度和读取内容两个状态
- 初始化的时候设置为读取长度的状态
- 读取的时候通过state()方法来确定当前处于什么状态
- 如果读取出来的长度大于0,则设置为读取内容状态,下一次读取的时候则从这个位置开始
- 读取完成,往结果里面放解析好的数据
以上就是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的更多相关文章
- Netty入门教程——认识Netty
什么是Netty? Netty 是一个利用 Java 的高级网络的能力,隐藏其背后的复杂性而提供一个易于使用的 API 的客户端/服务器框架. Netty 是一个广泛使用的 Java 网络编程框架(N ...
- Netty入门
一.NIO Netty框架底层是对NIO的高度封装,所以想要更好的学习Netty之前,应先了解下什么是NIO - NIO是non-blocking的简称,在jdk1.4 里提供的新api,他的他的特性 ...
- Netty入门(三)之web服务器
Netty入门(三)之web服务器 阅读前请参考 Netty入门(一)之webSocket聊天室 Netty入门(二)之PC聊天室 有了前两篇的使用基础,学习本文也很简单!只需要在前两文的基础上稍微改 ...
- Netty入门(二)之PC聊天室
参看Netty入门(一):Netty入门(一)之webSocket聊天室 Netty4.X下载地址:http://netty.io/downloads.html 一:服务端 1.SimpleChatS ...
- Netty4 自定义Decoder,Encoder进行对象传递
首先我们必须知道Tcp粘包和拆包的,TCP是个“流”协议,所谓流,就是没有界限的一串数据,TCP底层并不了解上层业务数据的具体含义,它会根据TCP缓冲区的实际数据进行包的划分,一个完整的包可能会被拆分 ...
- Netty入门(八)构建Netty HTTP/HTTPS应用
HTTP/HTTPS 是最常见的一种协议,这节主要是看一下 Netty 提供的 ChannelHaandler. 一.HTTP Decoder,Encoder 和 Codec HTTP 是请求-响应模 ...
- Netty入门4之----如何实现长连接
前面三章介绍了Netty的一些基本用法,这一章介绍怎么使用Netty来实现一个简单的长连接demo. 关于长连接的背景知识,可以参考<如何使用Socket实现长连接> 一个简单的长 ...
- Netty入门之客户端与服务端通信(二)
Netty入门之客户端与服务端通信(二) 一.简介 在上一篇博文中笔者写了关于Netty入门级的Hello World程序.书接上回,本博文是关于客户端与服务端的通信,感觉也没什么好说的了,直接上代码 ...
- Netty入门之HelloWorld
Netty系列入门之HelloWorld(一) 一. 简介 Netty is a NIO client server framework which enables quick and easy de ...
随机推荐
- Linux I2C驱动--用户态驱动简单示例
1. Linux内核支持I2C通用设备驱动(用户态驱动:由应用层实现对硬件的控制可以称之为用户态驱动),实现文件位于drivers/i2c/i2c-dev.c,设备文件为/dev/i2c-0 2. I ...
- node 基础小结
<!DOCTYPE html> <html> <head> <meta charset="utf-8" /> <title&g ...
- Mac系统下Eclipse代码联想功能(代码助手,代码提示)快捷键
Eclipse for mac 代码提示(代码助手,代码联想)快捷键修改一.每次输入都自动提示点击Eclipse,使其成为第一响应者,preferences->Java->Editor-& ...
- 性能调优之vmstat命令(转)
vmstat是Virtual Meomory Statistics(虚拟内存统计)的缩写,可对操作系统的虚拟内存.进程.IO读写.CPU活动等进行监视.它是对系统的整体情况进行统计,不足之处是无法对某 ...
- 2019.04.17 读书笔记 checked与unchecked
在普通的编程中,我们是很容易去分析数据的大小,然后给出合理的类型,但是在很多数据库的累计中,缺存在很多隐患,特别是研发时,数据量小,求和也不会溢出,当程序运行几年后,再来一次大求和,隐形的BUG就出来 ...
- (转)nginx location在配置中的优先级
原文:https://www.bo56.com/nginx-location%E5%9C%A8%E9%85%8D%E7%BD%AE%E4%B8%AD%E7%9A%84%E4%BC%98%E5%85%8 ...
- (转)MySQL5.6主从复制技术
原文:http://www.cnblogs.com/ilifeilong/category/1014799.html MySQL5.6复制技术(4)-MySQL主从复制过滤参数 摘要: 复制的过滤主要 ...
- spark跑YARN模式或Client模式提交任务不成功(application state: ACCEPTED)
不多说,直接上干货! 问题详情 电脑8G,目前搭建3节点的spark集群,采用YARN模式. master分配2G,slave1分配1G,slave2分配1G.(在安装虚拟机时) export SPA ...
- JavaScript数组forEach循环
JavaScript数组forEach循环 今天写JavaScript代码把forEach循环数组忘记写法了,在此记录一下以防止未来忘记. let a = [1, 2, 3]; a.forEach(f ...
- 2018年javaee学习目标
我打算在本学期的学习中把java ee掌握并能熟练应用,如果可以的话还打算编写一个后台服务程序,增加自己的实战经验.