Netty in action—codec框架
什么是codec
每个网络应用程序必须定义在机器之间传输的原始字节如何被解析然后转换为目标程序的数据格式。该转换逻辑由编解码器(codec,编码解码器,下文中我直接用codec表示)处理,codec由编码器和编码器组成,每个编码器或解码器负责将字节流从一种格式转换到另一种格式。那么该如何区分编码器和解码器呢?
将消息视为具有特定含义的字节的结构化序列-数据。 编码器将该消息转换成适合于传输的格式(很可能是字节流); 相应的解码器将字节流转回程序的消息格式。 编码器处理输出数据而解码器处理输入数据。
我们来看看Netty为实现这两种组件而提供的类。
解码器(decoder)
这一节中我们会研究Netty的解码器类,然后提供具体的例子来告诉你何时以及如何使用它们。这些类含有两个不同的用例:
- 将字节解码为消息—ByteToMessageDecoder和ReplayingDecoder
- 将一种格式的消息转换(解码)为另一种—MessageToMessageDecoder
什么时候使用解码器呢?当你需要为下一个ChannelInboundHandler转换输入数据时。此外,由于ChannelPipeline的设计,你可以将多个解码器链接到一起来实现任意复杂的转换逻辑
ByteToMessageDecoder抽象类
从字节到消息(或到另一个字节序列)的解码是常见的任务,Netty为其提供了一个抽象基类:ByteToMessageDecoder。 因为你不知道远程机器是否会一次性发送完整的消息,这个类缓冲输入数据,直到它可以被处理。
下表解释了它的两个最重要的方法
方法 | 描述 |
---|---|
decode(ChannelHandlerContext ctx,ByteBuf in,List out) | 这是你必须实现的唯一抽象方法。参数ByteBuf包含输入数据,List用来保存解码了的数据。decode()方法会被重复调用直到确定没有任何新数据被加到List中或在ByteBuf中没有更多的可读字节。那么,如果这个List不为空,其内容传递给下一个handler |
decodeLast(ChannelHandlerContext ctx,ByteBuf in,List out) | Netty提供的默认实现,它只是简单地调用decode()。当channel变成inactive时调用这个方法。可以覆盖这个方法来提供特殊的处理 |
有关如何使用此类的示例,假设你收到包含简单int的字节流,每个int都要单独处理。 在这种情况下,你将从输入ByteBuf中读取每个int并将其传递给下一个ChannelInboundHandler。为了解码字节流,需要继承ByteToMessageDecoder。 (注意,当原始int被添加到List时,它将被自动装箱为整型)。如下图所示:
一次从输入ByteBuf中读取4字节,解码成一个int,然后添加到List中。当没有更多的数据加到List中时,List中的内容会被送到下一个ChannelInboundHandler。
首先给出ToIntegerDecoder的代码:
public class ToIntegerDecoder extends ByteToMessageDecoder {@Overrideprotected void decode(ChannelHandlerContext ctx, ByteBuf in, List<Object> out) throws Exception {if( in.readableBytes() >= 4 ){//检查是否有4字节可读out.add(in.readInt());//将它添加到out中}}
}
你发现必须验证输入的ByteBuf是否有足够的数据,这有点麻烦。在下一节中我们会讨论ReplayDecoder,它可以消除这个检查的步骤,只需少量的开销。
codec中的引用计数 一旦消息被编码或解码,它将通过调用ReferenceCountUtil.release(message)自动释放消息。如果你需要保持一个消息引用,你可以调用ReferenceCountUtil.retain(message)。 这将增加引用计数,从而防止消息被释放。
ReplayingDecoder抽象类
ReplayDecoder继承了ByteToMessageDecoder,使我们无需调用readableBytes()。它通过一个自定义的ByteBuf实现(ReplayingDecoderBuffer,它会内部调用readableBytes())来包裹传入的ByteBuf。
这个类的定义如下:
public abstract class ReplayingDecoder<S> extends ByteToMessageDecoder
参数S指定要用于状态管理的类型,其中Void表示不执行任何操作。 以下代码显示了基于ReplayingDecoder重新实现的ToIntegerDecoder。
public class ToIntegerDecoder2 extends ReplayingDecoder<Void> {//传入的ByteBuf是一个ReplayingDecoderBuffer@Overridepublic void decode(ChannelHandlerContext ctx, ByteBuf in,List<Object> out) throws Exception {out.add(in.readInt());}
}
如果没有足够的字节,in.readInt()会抛出一个Error,这个Error会被捕获然后在基类中处理。当有更多的数据可读时,会再调用decode()方法。
请注意ReplayingDecoderBuffer的以下几点:
- 不支持所有ByteBuf操作。 如果调用了不支持的方法,将抛出UnsupportedOperationException。
- ReplayDecoder比ByteToMessageDecoder略慢。
如果不会引入过多的复杂性时使用ByteToMessageDecoder; 否则使用ReplayDecoder。
MessageToMessageDecoder抽象类
这一节会解释如何通过MessageToMessageDecoder类在两种消息格式之间转换(比如,从一种POJO类转换为另一种)
它定义如下:
public abstract class MessageToMessageDecoder<I> extends ChannelInboundHandlerAdapter
参数I指定decode()方法参数msg的类型,该方法是唯一需要实现的方法。
方法 | 描述 |
---|---|
decode(ChannelHandlerContext ctx,I msg,List out) | 为每个输入消息调用来解码为另一种格式。 然后解码的消息传递给下一个ChannelInboundHandler |
在这个例子中,我们将编写一个继承MessageToMessageDecoder<Integer>
的IntegerToStringDecoder
解码器。 它的decode()方法将转换Integer参数为String,并将具有以下签名:
public void decode( ChannelHandlerContext ctx,Integer msg, List<Object> out ) throws Exception
和前面一样,解码的String会加到List然后传递到下个handler,它的设计如下所示:
下面给出IntegerToStringDecoder的实现:
public class IntegerToStringDecoder extends MessageToMessageDecoder<Integer> {@Overridepublic void decode(ChannelHandlerContext ctx, Integer msgList<Object> out) throws Exception {out.add(String.valueOf(msg));}
}
TooLongFrameException类
由于Netty是一个异步框架,所以你需要将字节缓存到内存中,直到你可以对它们进行解码。 因此,你不能让你的解码器缓冲过多的数据以致消耗了太多的内存。 为了解决这个共同的问题,Netty提供了TooLongFrameException,如果一个帧(frame)的大小超过了限制则抛出这个异常。异常会被ChannelHandler.exceptionCaught()方法捕获,然后由用户决定如何处理这个异常。
下面的代码展示了ByteToMessageDecoder如何通过TooLongFrameException来通知ChannelHandler出现了帧大小溢出的问题:
public class SafeByteToMessageDecoder extends ByteToMessageDecoder {private static final int MAX_FRAME_SIZE = 1024;@Overridepublic void decode(ChannelHandlerContext ctx, ByteBuf in,List<Object> out) throws Exception {int readable = in.readableBytes();if (readable > MAX_FRAME_SIZE) {in.skipBytes(readable);//跳过这些字节然后抛出异常throw new TooLongFrameException("Frame too big!");}// do something...}}
介绍了解码器,下面开始介绍编码器,它将信息转换成一种适合输出的格式。
编码器(encoder)
编码器实现了ChannelOutboundHandler,将输出数据从一种格式转换为另一种。Netty提供了一些类来帮你实现以下功能:
- 将消息编码成字节
- 将消息进行格式转换
首先了解抽象基类MessageToByteEncoder
MessageToByteEncoder抽象类
下表显示了它的API:
方法 | 描述 |
---|---|
encode(ChannelHandlerContext ctx,I msg,ByteBuf out) | 将类型I的输出消息编码成一个ByteBuf,然后将这个ByteBuf传递给下一个 ChannelOutboundHandler |
你可能注意到了(除非您眼神不好)这个类只有一个方法,而解码器有两个方法。原因是解码器经常需要在Channel关闭后生成最后一条消息。对于编码器则没有这种需要,在连接关闭后产生一条消息没意义。
下图显示了一个ShortToByteEncoder接收一个Short实例作为消息,将它写入ByteBuf(只会占用这个ByteBuf中的两个字节)
代码很简单:
public class ShortToByteEncoder extends MessageToByteEncoder<Short> {@Overridepublic void encode(ChannelHandlerContext ctx, Short msg, ByteBuf out) throws Exception {out.writeShort(msg);}
}
Netty提供了几个特殊的MessageToByteEncoder,你可以基于它们实现你自己的逻辑。可以看io.netty.handler.codec.http.websocketx
包下的WebSocket08FrameEncoder
类,这是一个很好的例子。
MessageToMessageEncoder抽象类
MessageToMessageEncoder抽象类可以将一条消息从一种格式转换成另一种格式(用来输出)。
它的方法如下表所示:
名称 | 描述 |
---|---|
encode(ChannelHandlerContext ctx,I msg,List out) | 通过将write()方法传过来的消息 编码成一条或多条输出消息。然后将输出消息传递给下一个ChannelOutboundHandler |
下面给一个将整数编码成String的例子,先是它的设计图:
代码如下:
public class IntegerToStringEncoder extends MessageToMessageEncoder<Integer> {@Overridepublic void encode(ChannelHandlerContext ctx, Integer msg, List<Object> out) throws Exception {out.add(String.valueOf(msg));}
}
codec抽象类
虽然我们一直分为不同的类来讨论解码器和编码器。有时你会发现在一个类中同时管理输入和输出数据和消息非常有用,Netty的codec类可以实现这样的目的。每个codec类绑定了decoder/encoder来处理编码和解码操作。
那为什么我们不一直使用这些具有复合功能的类而不是分离的解码器和编码器呢? 因为尽可能保持两个功能分开可以最大化代码可重用性和可扩展性,这是Netty设计的基本原则。
ByteToMessageCodec抽象类
我们考虑一下这种场景,当我们需要将字节序列解码成某种消息,可能是POJO类,然后再对它进行编码。ByteToMessageCodec就能应用于这种场景。
ByteToMessageCodec类中重要的方法如下表所示:
方法名 | 描述 |
---|---|
decode(ChannelHandlerContext ctx,ByteBuf in,List) | 只要有可消耗的字节就会调用这个方法。它将输入ByteBuf转换成特定的消息格式 |
decodeLast(ChannelHandlerContext ctx,ByteBuf in,List out) | 它只会被调用一次,当Channel失效时,可以重写它来实现特殊处理 |
encode(ChannelHandlerContext ctx,I msg,ByteBuf out) | 将每条I类型的消息进行编码,然后写入一个输出ByteBuf |
任何请求/响应协议都可以考虑使用ByteToMessageCodec。 例如,在SMTP实现中,codec将读取
传入的字节并将其解码为自定义消息类型-SmtpRequest。 在接收方,当响应产生时,将产生一个SmtpResponse,它的内容会被编码成字节以便传输。
MessageToMessageCodec抽象类
MessageToMessageCodec定义如下:
public abstract class MessageToMessageCodec<INBOUND_IN,OUTBOUND_IN>
它的主要方法如下:
方法名 | 描述 |
---|---|
protected abstract decode(ChannelHandlerContext ctx,INBOUND_IN msg,List out) | 将INBOUND_IN类型的消息解码成OUTBOUND_IN类型,然后传递给下一个ChannelInboundHandler |
protected abstract encode(ChannelHandlerContext ctx,OUTBOUND_IN msg,List out) | 将类型OUTBOUND_IN的消息编码成INBOUND_IN类型 |
可以将INBOUND_IN类型的消息看成是通过网络发送的消息,OUTBOUND_IN消息看成是由应用产生的消息。
虽然这个codec似乎有些深奥,但它处理的用例是相当的通用:在两个不同的消息传递API之间来回转换数据。
WebSocket传输协议
下面关于MessageToMessageCodec的例子用到了WebSocket,实现浏览器和服务器之间完全双向通信
下面的代码显示了这样的对话是如何发生的。我们的WebSocketConvertHandler将MessageToMessageCodec参数化,带有一个INBOUND_IN类型的WebsocketFrame和一个OUTBOUND_IN类型的MyWebSocketFrame。
public class WebSocketConvertHandler extendsMessageToMessageCodec<WebSocketFrame,WebSocketConvertHandler.MyWebSocketFrame> {@Overrideprotected void encode(ChannelHandlerContext ctx,WebSocketConvertHandler.MyWebSocketFrame msg,List<Object> out) throws Exception {ByteBuf payload = msg.getData().duplicate().retain();switch (msg.getType()) {//实例化一个WebSocketFrame的特定子类case BINARY:out.add(new BinaryWebSocketFrame(payload));break;case TEXT:out.add(new TextWebSocketFrame(payload));break;case CLOSE:out.add(new CloseWebSocketFrame(true, 0, payload));break;case CONTINUATION:out.add(new ContinuationWebSocketFrame(payload));break;case PONG:out.add(new PongWebSocketFrame(payload));break;case PING:out.add(new PingWebSocketFrame(payload));break;default:throw new IllegalStateException("Unsupported websocket msg " + msg);}}//将一个WebSocketFrame解码成MyWebSocketFrame然后设置FrameType@Overrideprotected void decode(ChannelHandlerContext ctx, WebSocketFrame msg,List<Object> out) throws Exception {ByteBuf payload = msg.getData().duplicate().retain();if (msg instanceof BinaryWebSocketFrame) {out.add(new MyWebSocketFrame(MyWebSocketFrame.FrameType.BINARY, payload));} elseif (msg instanceof CloseWebSocketFrame) {out.add(new MyWebSocketFrame (MyWebSocketFrame.FrameType.CLOSE, payload));} elseif (msg instanceof PingWebSocketFrame) {out.add(new MyWebSocketFrame (MyWebSocketFrame.FrameType.PING, payload));} elseif (msg instanceof PongWebSocketFrame) {out.add(new MyWebSocketFrame (MyWebSocketFrame.FrameType.PONG, payload));} elseif (msg instanceof TextWebSocketFrame) {out.add(new MyWebSocketFrame (MyWebSocketFrame.FrameType.TEXT, payload));} elseif (msg instanceof ContinuationWebSocketFrame) {out.add(new MyWebSocketFrame (MyWebSocketFrame.FrameType.CONTINUATION, payload));} else{throw new IllegalStateException("Unsupported websocket msg " + msg);}}public static final class MyWebSocketFrame {public enum FrameType {BINARY,CLOSE,PING,PONG,TEXT,CONTINUATION}private final FrameType type;private final ByteBuf data;public WebSocketFrame(FrameType type, ByteBuf data) {this.type = type;this.data = data;}public FrameType getType() {return type;}public ByteBuf getData() {return data;}}
}
CombinedChannelDuplexHandler类
如前所述,组合解码器和编码器可能会对重用性产生影响。 然而,有一种方法来避免这种缺陷,而不牺牲将解码器和编码器部署在一起的方便。 该解决方案由CombinedChannelDuplexHandler提供,声明为
public class CombinedChannelDuplexHandler<I extends ChannelInboundHandler,O extends ChannelOutboundHandler>
此类作为ChannelInboundHandler和ChannelOutboundHandler(作为类参数I和O)的容器。 通过提供继承一个decoder类和一个encoder类,我们可以相应地实现codec而无需直接继承抽象codec类。 我们将在下面的例子中说明这一点。
public class ByteToCharDecoder extends ByteToMessageDecoder {@Overridepublic void decode(ChannelHandlerContext ctx, ByteBuf in,List<Object> out) throws Exception {while (in.readableBytes() >= 2) {out.add(in.readChar());}}
}
这里decode()从ByteBuf中提取2个字节,并将它们作为char写入List,字节将自动装箱为Character对象。
下面代码中的CharToByteEncoder,它将Character转换回字节。 这个类继承了MessageToByteEncoder,因为它需要将char消息编码为一个ByteBuf。 这是通过直接写入ByteBuf来实现的:
public class CharToByteEncoder extends MessageToByteEncoder<Character> {@Overridepublic void encode(ChannelHandlerContext ctx, Character msg,ByteBuf out) throws Exception {out.writeChar(msg);}
}
我们有一个decoder类和一个encoder类,然后将它们组合成一个codec。如下所示:
public class CombinedByteCharCodec extends CombinedChannelDuplexHandler<ByteToCharDecoder, CharToByteEncoder> {public CombinedByteCharCodec() {super(new ByteToCharDecoder(), new CharToByteEncoder());}
}
通过这种方式更简单且具有更多的灵活性。
Netty in action—codec框架相关推荐
- Netty In Action中文版
第一章:Netty介绍 本章介绍 Netty介绍 为什么要使用non-blocking IO(NIO) 阻塞IO(blocking IO)和非阻塞IO(non-blocking IO)对比 Java ...
- Netty in Action 读书
Netty in Action 作者: Norman Maurer / Marvin Allen Wolfthal 出版社:Manning Publications 出版年:2015-12-31 页数 ...
- 基于Netty手工实现springMVC框架-----两种方式加载控制器
1.手写springMVC框架 本篇我们通过两种方式来加载控制器,一种是配置文件的方式:另外一种是通过注解的形式. 1.配置文件方式 1.自定义Controller配置文件XML 我定义的格式如下: ...
- Netty In Action中文版 - 第十二章:SPDY
Netty In Action中文版 - 第十二章:SPDY 本章我将不会直接翻译Netty In Action书中的原文,感觉原书中本章讲的很多废话,我翻译起来也吃力.所以,本章内容我会根据其他资料 ...
- 《Netty IN ACTION》中文版《Netty实战》翻译手记——不负好时光
不负好时光--<Netty in Action>中文版<Netty实战>翻译手记 引子 "书中自有黄金屋,书中自有颜如玉",这句话从小我老爸就给我讲,当然那 ...
- [强烈推荐] 新手入门:目前为止最透彻的的Netty高性能原理和框架架构解析
新手入门:目前为止最透彻的的Netty高性能原理和框架架构解析 1.引言 Netty 是一个广受欢迎的异步事件驱动的Java开源网络应用程序框架,用于快速开发可维护的高性能协议服务器和客户端. 本文基 ...
- RPC系列之Netty实现自定义RPC框架
进行这个章节之前,需要去看一下RMI的实现哈,如果了解过的童鞋可以直接跳过,如果没有或者不知道RMI的童鞋,移驾到下面的链接看完之后再回来继续看这篇 RPC系列之入门_阿小冰的博客-CSDN博客RPC ...
- 还发愁项目经验吗?基于Netty实现分布式RPC框架[附完整代码]
写给大家的话 最近我收到很多读者的来信,对如何学习分布式.如何进行项目实践和提高编程能力,存在很多疑问. 分布式那么难,怎么学?为什么看了那么多书还是掌握不了? 开源的框架比如Dubbo代码太多了,完 ...
- netty编解码器与序列化框架分析
netty编解码器分析 编码(Encode)也称为序列化(serialization),它将对象序列化为字节数组,用于网络传输.数据持久化或者其它用途. 反之,解码(Decode)也称为反序列化(de ...
- 《Netty In Action》第二章:第一个Netty程序
2019独角兽企业重金招聘Python工程师标准>>> 源码地址: GitHub 2.3 编写一个应答服务器 写一个Netty服务器主要由两部分组成: 配置服务器功能,如线程.端口 ...
最新文章
- string与数值之间的转换
- 使用tmpfs缓存文件提高性能
- C++ 随机函数----谈rand() 和 srand() 体会
- python常用模块大全总结-Python模块汇总(常用第三方库)
- Python 条件语句 学习转载
- 项目信息追踪(Log)
- jenkins 使用xctool 爆出: line 6: xctool: command not found
- H3CSE园区-IRF
- 基于cpolar内网穿透工具ssh远程访问linux服务器
- win10系统mysql重新配置密码
- 使用U盘制做CentOS7.6安装盘并安装CentOS7.6系统
- tlc5620输出三角波流程图_基于TLC5620的数模转换器设计
- 关于ARMv8另外几个问题
- Ubuntu硬盘分区/格式化/挂载文件系统各种应用(转载)
- 小学计算机老师师德师风演讲稿,小学教师师德师风演讲稿五篇
- 易语言 用精易的网页_访问 请求https的时候返回不了数据
- 评价win10自带输入法——微软拼音输入法
- Android应用开发-数据存储和界面展现
- Vue warn]: Error compiling template:
- 2022-2028全球汽车低压连接器行业调研及趋势分析报告