Netty实战十之编解码器框架
编码和解码,或者数据从一种特定协议的格式到另一种格式的转换。这些任务将由通常称为编解码器的组件来处理。Netty提供了多种组件,简化了为了支持广泛的协议而创建自定义的编解码器的过程。例如,如果你正在构建一个基于Netty的邮件服务器,那么你将会发现Netty对于编解码器的支持对于实现POP3、IMAP和SMTP协议来说是多么的宝贵。
1、什么是编解码器
每个网络应用程序都必须定义如何解析在两个节点之间来回传输的原始字节,以及如何将其和目标应用程序的数据格式做相互转换。这种转换逻辑由编解码器处理,编解码器有编码器和解码器组成,它们每种都可以将字节流从一种格式转换为另一种格式。
如果将消息看作是对于特定的应用程序具有具体含义的结构化的字节序列——它的数据。那么编码器是将消息转换为适合于传输的格式(最有可能的就是字节流);而对应的解码器则是将网络字节流转换回应用程序的消息格式。因此,编码器操作出站数据,而解码器处理入站数据。
2、解码器
——将字节解码为消息——ByteToMessageDecoder和ReplayingDecoder
——将一种消息类型解码为另一种——MessageToMessageDecoder
因为解码器是负责将入站数据从一种格式转换到另一种格式的,所以知道Netty的解码器实现了ChannelInboundHandler也不会让你感到意外。
每当需要为ChannelPipeline中的下一个ChannelInboundHandler转换入站数据时会用到。此外,得益于ChannelPipeline的设计,可以将多个解码器链接在一起,以实现任意复杂的转换逻辑,这也是Netty是如何支持代码的模块化以及复用的例子。
3、抽象类ByteToMessageDecoder
将字节解码为消息是一项如此常见的任务,以至于Netty为他提供了一个抽象的基类:ByteToMessageDecoder。由于你不可能知道远程节点是否会一次性地发送一个完整的消息,所以这个类会对入站数据进行缓冲。
下面举一个如何使用这个类的示例,假设你接收了一个包含简单int的字节流,每个int都需要被单独处理。在这种情况下,你需要从入站ByteBuf中读取每个int,并将它传递给ChannelPipeline中的下一个ChannelInboundHandler。为了解码这个字节流,你要扩展ByteToMessageDecoder类。(需要注意的是,原始类型int在被添加到List中时,会被自动装箱为Integer),如下设计图。
每次从入站ByteBuf中读取4字节,将其解码为一个Int,然后将它添加到一个List中。当没有更多的元素可以被添加到该List中时,它的内容将会被发送给下一个ChannelInboundHandler。
public class ToIntegerDecoder extends ByteToMessageDecoder{
@Override
protected void decode(ChannelHandlerContext channelHandlerContext,
ByteBuf in, List<Object> out) throws Exception {
//检查是否至少有4字节可读(一个int的字节长度)
if (in.readableBytes() >= 4){
//从入站ByteBuf中读取一个int,并将其添加到解码消息的List中
out.add(in.readInt());
}
}
}
虽然ByteToMessageDecoder使得可以很简单地实现这种模式,但是你可能会发现,在调用readint()方法前不得不验证所输入的ByteBuf是否具有足够的数据有点繁琐。
4、抽象类ReplayingDecoder
ReplayingDecoder扩展了ByteToMessageDecoder类,使得我们不必调用readableBytes()方法。它通过使用一个自定义的ByteBuf实现,ReplayingDecoderByteBuf,包装传入的ByteBuf实现了这一点,其将在内部执行该调用。
public abstract class ReplayingDecoder extends ByteToMessageDecoder
类型参数S指定了用于状态管理的类型,其中Void代表不需要状态管理。以下代码展示了基于ReplayingDecoder重新实现ToIntegerDecoder。
public class ToIntegerDecoder2 extends ReplayingDecoder<Void>{
@Override
protected void decode(ChannelHandlerContext channelHandlerContext,
ByteBuf in, List<Object> out) throws Exception {
out.add(in.readInt());
}
}
和之前一样,从ByteBuf中提取的int将会被添加到List中,如果没有足够的字节可用,这个readInt()方法的实现将会抛出一个Error,其将在基类中被捕获并处理。当有更多的数据可供读取时,该decode()方法将会被再次调用。
请注意ReplayingDecoderByteBuf的下面这些方面:
——并不是所有的ByteBuf操作都被支持,如果调用了一个不被支持的方法,将会抛出一个UnsupportedOperationException
——ReplayingDecoder稍慢于ByteToMessageDecoder
——如果使用ByteToMessageDecoder不会引入太多的复杂性,那么请使用它;否则,请使用ReplayingDecoder
5、抽象类MessageToMessageDecoder
public abstract class MessageToMessageDecoder extends ChannelInboundHandlerAdapter
类型参数I指定了decode()方法的输入参数msg的类型,它是你必须实现的唯一方法。
在这个示例中,我们将编写一个IntegerToStringDecoder解码器来扩展MessageToMessageDecoder。它的decode()方法会把Integer参数转换为它的String表示,并将拥有下列签名:
public void decode( ChannelHandlerContext ctx, Integer msg , List out) throws Exception
和之前一样,解码的String将被添加到传出的List中,并转发给下一个ChannelInboundHandler
public class IntegerToStringDecoder extends MessageToMessageDecoder<Integer>{
@Override
protected void decode(ChannelHandlerContext channelHandlerContext,
Integer msg, List<Object> out) throws Exception {
//将Integer消息转换为它的String表示,并将其添加到输出的List中
out.add(String.valueOf(msg));
}
}
6、TooLongFrameException类
由于Netty是一个异步框架,所以需要在字节可以解码之前在内存中缓冲他们,因此,不能让解码器缓冲大量的数据以至于耗尽可用的内存。为了解除这个常见的顾虑,Netty提供了TooLongFrameException类,其将由解码器在帧超出指定的大小限制时抛出。
为了避免这种情况,你可以设置一个最大字节数的伐值,如果超过,则会导致抛出一个TooLongFrameException,如何处理该异常则完全取决于解码器的用户。某些协议(HTTP)可能允许你返回一个特殊的响应,而在其他的情况下,唯一的选择可能就是关闭对应的连接。
以下代码展示了ByteToMessageDecoder是如何使用TooLongFrameException来通知ChannelPipeline中的其他ChannelHandler发生了帧大小溢出的。需要注意的是,如果你正在使用一个可变帧大小的协议,那么这种保护措施将是尤其重要的。
public class SafeByteToMessageDecoder extends ByteToMessageDecoder{
private static final int MAX_FRAME_SIZE = 1024;
@Override
protected void decode(ChannelHandlerContext channelHandlerContext,
ByteBuf in, List<Object> out) throws Exception {
int readable = in.readableBytes();
//检查缓冲区中是否有超过MAX_FRAME_SIZE个字节
if (readable > MAX_FRAME_SIZE){
//跳过所有的可读字节,抛出TooLongFrameException并通知ChannelHandler
in.skipBytes(readable);
throw new TooLongFrameException("Frame too big!");
}
//DO something
}
}
7、抽象类MessageToByteEncoder
这个类只有一个方法,而解码器有两个。原因是解码器通常需要在Channel关闭之后产生最后一个消息(decodeLast()方法),这显然不适用于编码器的场景——在连接关闭之后仍然产生一个消息是毫无意义的。
ShortToByteEncoder,其接受一个Short类型的实例作为消息,将它编码为Short的原始类型值,并将它写入ByteBuf中,其将随后被转发给ChannelPipeline中的下一个CHannelOutboundHandler。每个传出的Short值都将会占用ByteBuf中的2字节。
public class ShortToByteEncoder extends MessageToByteEncoder<Short>{
@Override
protected void encode(ChannelHandlerContext channelHandlerContext,
Short msg, ByteBuf out) throws Exception {
//将Short写入ByteBuf中
out.writeShort(msg);
}
}
Netty提供了一些专门化的MessageToByteEncoder,你可以基于他们实现自己的编码器。WebSocket08FrameEncoder类提供了一个很好的实例。你可以在io.netty.handler.codec.http.websocket包中找到它。
8、抽象类MessageToMessageEncoder
我们将展示对于出站数据将如何从一种消息编码为另一种。MessageToMessageEncoder类的encoder()方法提供了这种能力。
以下代码,编码器将每个出站Integer的String表示添加到了该List中。
public class IntegerToStringEncoder extends MessageToMessageEncoder<Integer>{
@Override
protected void encode(ChannelHandlerContext channelHandlerContext,
Integer msg, List<Object> out) throws Exception {
//将Integer转换为String,并将其添加到List中
out.add(String.valueOf(msg));
}
}
9、抽象的编解码器类
在同一个类中管理入站和出站数据和消息的转换是很有用的。Netty的抽象编解码器类正好用于这个目的,因为它们每个都将捆绑一个解码器/编码器对,以处理我们一直在学习的这两种类型的操作。
通过尽可能地将这两种功能分开,最大化了代码的可重用性和可扩展性,这是Netty设计的一个基本原则。
10、抽象类ByteToMessageCodec
场景:我们需要将字节编码为某种形式的消息,可能是POJO,随后再次对它进行编码。ByteToMessageCodec将为我们处理好了这一切,因为它结合了ByteToMessageDecoder以及他的逆向MessageToByteEncoder。
任何的请求/响应协议都可以作为使用ByteToMessageCodec的理想选择,例如,在某个SMTP的实现中,编解码器将读取传入字节,并将它们解码为一个自定义的消息类型,如SmtpRequest。而在接收端,当一个响应被创建时,将会产生一个SmtpResponse,其将被编码回字节以便进行传输。
11、CombinedChannelDuplexHandler类
结合一个解码器和编码器可能会对可重用性造成影响。但是,有一种方法即能够避免这种惩罚,又不会牺牲将一个解码器和一个编码器作为一个单独的单元部署所带来的的便利性。CombinedChannelDuplexHandler提供了这个解决方案,其声明为:
public class CombinedChannelDuplexHandler <I extends ChannelInboundHandler, O extends ChannelOutboundHandler>
这个类充当了ChannelInboundHandler和ChannelOutboundHandler(该类的类型参数I和O)的容器。通过提供分别继承了解码器类和编码器类的类型,我们可以实现一个编解码器,而又不必直接扩展抽象的编解码器类。
首先,让我们研究代码中的ByteToCharDecoder。注意,该实现扩展了ByteToMessageDecoder,因为它要从ByteBuf中读取字符。
public class ByteToCharDecoder extends ByteToMessageDecoder{
@Override
protected void decode(ChannelHandlerContext channelHandlerContext,
ByteBuf in, List<Object> out) throws Exception {
while (in.readableBytes() >= 2){
//将一个或者多个Character对象添加到传出的List中
out.add(in.readChar());
}
}
}
这里的decode()方法一次将从ByteBuf中提取2字节,并将它们作为char写入到List中,其将会被自动装箱为Character对象。
以下代码,包含了CharToByteEncoder,他能将Character转换回字节。这个类扩展了MessageToByteEncoder,因为它需要将char消息编码到ByteBuf中,这是通过直接写入ByteBuf做到的。
public class CharToByteEncoder extends MessageToByteEncoder<Character>{
@Override
protected void encode(ChannelHandlerContext channelHandlerContext,
Character msg, ByteBuf out) throws Exception {
//将Character解码为char,并将其写入到出站ByteBuf中
out.writeChar(msg);
}
}
既然我们有了解码器和编码器,我们将会结合它们来构建 一个编解码器。如以下代码所示。
在某些情况下,通过这种方式结合实现相对于使用编解码器类的方式来说可能更加的简单也更加的灵活。

Netty实战十之编解码器框架的更多相关文章
- Netty实战十四之案例研究(一)
1.Droplr——构建移动服务 Bruno de Carvalho,首席架构师 在Droplr,我们在我的基础设施的核心部分.从我们的API服务器到辅助服务的各个部分都使用了Netty. 这是一个关 ...
- Netty实战十二之WebSocket
如果你有跟进Web技术的最新进展,你很可能就遇到过“实时Web”这个短语,这里并不是指所谓的硬实时服务质量(QoS),硬实时服务质量是保证计算结果将在指定的时间间隔内被递交.仅HTTP的请求/响应模式 ...
- 应用程序框架实战十五:DDD分层架构之领域实体(验证篇)
在应用程序框架实战十四:DDD分层架构之领域实体(基础篇)一文中,我介绍了领域实体的基础,包括标识.相等性比较.输出实体状态等.本文将介绍领域实体的一个核心内容——验证,它是应用程序健壮性的基石.为了 ...
- Netty实战:设计一个IM框架
来源:逅弈逐码 bitchat 是一个基于 Netty 的 IM 即时通讯框架 项目地址:https://github.com/all4you/bitchat 快速开始 bitchat-example ...
- 1、Netty 实战入门详解
一.Netty 简介 Netty 是基于 Java NIO 的异步事件驱动的网络应用框架,使用 Netty 可以快速开发网络应用,Netty 提供了高层次的抽象来简化 TCP 和 UDP 服务器的编程 ...
- Netty实战入门详解——让你彻底记住什么是Netty(看不懂你来找我)
一.Netty 简介 Netty 是基于 Java NIO 的异步事件驱动的网络应用框架,使用 Netty 可以快速开发网络应用,Netty 提供了高层次的抽象来简化 TCP 和 UDP 服务器的编程 ...
- 重磅!阿里P8费心整理Netty实战+指南+项目白皮书PDF,总计1.08G
前言 Netty是一款用于快速开发高性能的网络应用程序的Java框架.它封装了网络编程的复杂性,使网络编程和Web技术的最新进展能够被比以往更广泛的开发人员接触到. Netty不只是一个接口和类的集合 ...
- 1.Netty 实战前言
1.参考文档:Netty实战精髓篇 2.Netty介绍: Netty是基于Java NIO的网络应用框架. Netty是一个NIO client-server(客户端服务器)框架,使用Nett ...
- Netty学习摘记 —— 初识编解码器
本文参考 本篇文章是对<Netty In Action>一书第十章"编解码器框架"的学习摘记,主要内容为解码器和编码器 编解码器实际上是一种特殊的ChannelHand ...
随机推荐
- linux 值安装yum包
1. 创建文件,挂载 rhel7-repo-iso [root@rhel7 ~]# mkdir /media/rhel7-repo-iso [root@rhel7 ~]# mount /dev/cd ...
- jsp页面时间戳转换为时间格式
jstl中格式化时间戳 在jsp页面中使用jstl标签将long型的时间戳转换为格式化后的时间字符串 1.通过<jsp:useBean /> 导入java.util.Date类2.通过 ...
- 大叔学ML第一:梯度下降
目录 原理 实践一:求\(y = x^2 - 4x + 1\)的最小值 实践二:求\(z = x^2 + y^2 + 5\)的最小值 问答时间 原理 梯度下降是一个很常见的通过迭代求解函数极值的方法, ...
- iis发布后模板字体不能加载的解决方案
在使用ace模板的过程中就曾遇到过图标不显示的情况, 1.在iis和vs运行都不能显示图标,添加缺失的字体库后可以访问 2.把项目签入到阿里云时再一次失效,解决方法是添加Mime类型 .woff a ...
- system libzip must be upgraded to version >= 0.11
PHP当前最新版本是PHP7.3,今天在尝试安装的过程中报如下错误: system libzip must be upgraded to version >= 0.11, 根据提示我们可以清楚的 ...
- Kali学习笔记20:缓冲区溢出实验环境准备
在前几篇的博客中:我介绍了OpenVAS和Nessus这两个强大的自动化漏洞扫描器 但是,在计算机领域中有种叫做0day漏洞:没有公开只掌握在某些人手中 那么,这些0day漏洞是如何被发现的呢? 接下 ...
- [原创]CobaltStrike & Metasploit Shellcode一键免杀工具
CobaltStrike & Metasploit Shellcode一键免杀工具 作者: K8哥哥 图片 1个月前该工具生成的exe免杀所有杀软,现在未测应该还能过90%的杀软吧. 可选. ...
- mongo in和not in查询
执行语句 db.getCollection("A表").find( { id:{ $in:[1,2]} } ) 作用:查询A表中id字段等于1和等于2 的记录 改成 id:{ $ ...
- 动态dp初探
动态dp初探 动态区间最大子段和问题 给出长度为\(n\)的序列和\(m\)次操作,每次修改一个元素的值或查询区间的最大字段和(SP1714 GSS3). 设\(f[i]\)为以下标\(i\)结尾的最 ...
- javascript数组的属性、方法和清空-最全!!!(必看)
今天经理要我从新看一遍js,当我再看<精通js和jquery>这本书时,发现关于数组的这章节讲的很少,于是想自己总结一下数组的常用方法. 定义数组: var arr = new Array ...