Netty 系列六(编解码器).
一、概念
网络传输的单位是字节,如何将应用程序的数据转换为字节,以及将字节转换为应用程序的数据,就要说到到我们该篇介绍的编码器和解码器。
将应用程序的数据转换为网络格式,以及将网络格式转换为应用程序的数据的组件分别叫作编码器和解码器,同时具有这两种功能的单一组件叫作编解码器。 Netty 提供了一系列用来创建所有这些编码器、解码器以及编解码器的工具,还可以按需定制通用的消息转换编解码器。
Netty 的编(解)码器实现了 ChannelHandlerAdapter,也是一种特殊的 ChannelHandler,所以依赖于 ChannelPipeline,可以将多个编(解)码器链接在一起,以实现复杂的转换逻辑。
对于编码器和解码器来说,其过程也是相当的简单:一旦消息被编码或者解码,它就会被 ReferenceCountUtil.release(message)调用自动释放。如果你需要保留引用以便稍后使用,那么你可以调用 ReferenceCountUtil.retain(message)方法。这将会增加该引用计数,从而防止该消息被释放。
二、编码器
编码器将应用程序的数据转换为网络格式,出站消息时被调用,主要有两类:
1、将消息编码为字节:MessageToByteEncoder
public abstract class MessageToByteEncoder<I> extends ChannelHandlerAdapter{}
public class ShortToByteEncoder extends MessageToByteEncoder<Short> {
/**
* 1、类型为 I 的出站消息被编码为 ByteBuf
* 2、该 ByteBuf 随后将会被转发给 ChannelPipeline中的下一个 ChannelOutboundHandler。
*/
@Override
protected void encode(ChannelHandlerContext channelHandlerContext, Short aShort, ByteBuf byteBuf) throws Exception {
byteBuf.writeShort(aShort);
}
}
2、将消息编码为消息:MessageToMessageEncoder
public abstract class MessageToMessageEncoder<I> extends ChannelHandlerAdapter{}
public class IntegerToStringEncoder extends MessageToMessageEncoder<Integer> {
/**
* 1、类型为 I 的出站消息被编码为目标类型 存入List 中
* 2、该 List 随后将会被转发给 ChannelPipeline中的下一个 ChannelOutboundHandler。
*/
@Override
protected void encode(ChannelHandlerContext ctx, Integer msg, List<Object> out) throws Exception {
out.add(String.valueOf(msg));
}
}
三、解码器
解码器将网络格式转换为应用程序的数据,入站消息时被调用。
解码器比编码器多了一个 decodeLast 方法,原因是解码器通常需要在 Channel 关闭之后产生最后一个消息。这显然不适用于编码器的场景 —— 在连接被关闭之后仍然产生一个消息是毫无意义的。所以,当 Channel 的状态变为非活动时,这个方法将会被调用一次。可以重写该方法以提供特殊的处理。
解码器主要有两类:
1、将字节解码为消息:ByteToMessageDecoder 和 ReplayingDecoder
public abstract class ByteToMessageDecoder extends ChannelHandlerAdapter {}
public class MyDecoder extends ByteToMessageDecoder {
private static final int MAX_FRAME_SIZE = 1024;
/**
* 1、该方法被调用时,将会传入一个包含了传入数据的 ByteBuf,以及一个用来添加解码消息的 List.
* 2、对该方法的调用将会重复进行,直到确定没有新的元素被添加到该 List,或者Butebuf 没有更多可读取的字节为止。
* 3、List 的内容将会被传递给 ChannelPipeline 中的下一个 ChannelInboundHandler。
*/
@Override
protected void decode(ChannelHandlerContext channelHandlerContext, ByteBuf byteBuf, List<Object> list) throws Exception {
int readableBytes = byteBuf.readableBytes();
//不能让解码器缓冲大量的数据以致于耗尽可用的内存
if (readableBytes > MAX_FRAME_SIZE){
//跳过所有的可读字节
byteBuf.skipBytes(readableBytes);
throw new TooLongFrameException("数据超过可缓存字节...");
}
//假设需要解析 int 类型的消息(int 4个字节)
if (readableBytes > 4){
list.add(byteBuf.readInt());
}
}
}
----分割线----
//类型参数 S 指定了用于状态管理的类型,其中 Void 代表不需要状态管理。
public abstract class ReplayingDecoder<S> extends ByteToMessageDecoder{}
public class MyReplayingDecoder extends ReplayingDecoder<Void> {
/**
* 1、ReplayingDecoder 扩展了 ByteToMessageDecoder,并且自定义了 ByteBuf 的实现 ReplayingDecoderByteBuf。
* 2、ReplayingDecoderByteBuf 对要转换的消息的字节数进行内部管理,如果没有足够的字节使用,将会抛出一个 Signal,由ReplayingDecoder进行处理。
*
* @param byteBuf 传入的 ByteBuf 实际上是 ReplayingDecoderByteBuf*/
@Override
protected void decode(ChannelHandlerContext channelHandlerContext, ByteBuf byteBuf, List<Object> list) throws Exception {
list.add(byteBuf.readInt());
}
}
继承 ByteToMessageDecoder 和继承 ReplayingDecoder 有什么区别呢?ReplayingDecoder 唯一的好处就是解码的时候不用进行字节数的判断,如上 ,因为它交由自定义的 ReplayingDecoderByteBuf 去处理了。但是 ReplayingDecoder 的效率稍慢于ByteToMessageDecoder。一般我们在这两个解码器中进行抉择的准则是:如果使用 ByteToMessageDecoder 不会引入太多的复杂性,那么请使用它;否则,请使用 ReplayingDecoder!
2、将消息解码为消息:MessageToMessageDecoder
public abstract class MessageToMessageEncoder<I> extends ChannelHandlerAdapter{}
public class IntegerToStringDecoder extends MessageToMessageEncoder<Integer> {
/**
* 1、对于每个需要被解码为另一种格式的入站消息来说,该方法将会被调用。
* 2、解码消息随后会被传递给 ChannelPipeline 中的下一个 ChannelInboundHandler
*/
@Override
protected void encode(ChannelHandlerContext channelHandlerContext, Integer integer, List<Object> list) throws Exception {
list.add(String.valueOf(integer));
}
}
四、编解码器
Netty 的抽象编解码器类捆绑一个解码器/编码器对,主要用于在同一个类中管理入站和出站数据和消息的转换。
个人觉得这个编解码器略显鸡肋呀,还是喜欢将编码器和解码器分开来写。因为 Netty 设计的一个基本准则就是:尽可能地将两种功能(编码器、解码器)分开,最大化代码的可重用性和可扩展性。
编解码器也主要有两类:
1、字节消息编解码器:ByteToMessageCodec
public abstract class ByteToMessageCodec<I> extends ChannelHandlerAdapter {}
public class MyBytetoMessageCodec extends ByteToMessageCodec<Short> {
@Override
protected void decode(ChannelHandlerContext ctx, ByteBuf in, List out) throws Exception {
while (in.readableBytes() > 4){
out.add(in.readInt());
}
}
@Override
protected void encode(ChannelHandlerContext ctx, Short msg, ByteBuf out) throws Exception {
out.writeShort(msg);
}
}
2、消息转换编解码器:MessageToMessageCodec
public abstract class MessageToMessageCodec<INBOUND_IN, OUTBOUND_IN> extends ChannelHandlerAdapter {}
public class WebSocketConvertHandler extends MessageToMessageCodec<WebSocketFrame, WebSocketConvertHandler.MyWebSocketFrame> {
/**
* 1、对于每个 OUTBOUND_IN 类型的消息,这个方法都会被调用。
* 2、这个消息将会被编码为 INBOUND_IN 类型的消息。
* 3、然后被转发给 ChannelPipeline 中的下一个 ChannelOutboundHandler
*/
@Override
protected void encode(ChannelHandlerContext ctx, MyWebSocketFrame msg, List<Object> out) throws Exception {
ByteBuf byteBuf = msg.getByteBuf().duplicate().retain();
switch (msg.getFrameType()) {
case BINARY:
out.add(new BinaryWebSocketFrame(byteBuf));
break;
case TEXT:
out.add(new TextWebSocketFrame(byteBuf));
break;
case CLOSE:
out.add(new CloseWebSocketFrame(true, 0, byteBuf));
break;
case CONTINUATION:
out.add(new ContinuationWebSocketFrame(byteBuf));
break;
case PONG:
out.add(new PongWebSocketFrame(byteBuf));
break;
case PING:
out.add(new PingWebSocketFrame(byteBuf));
default:
break;
}
}
/**
* 1、传入 INBOUND_IN 类型的消息,该方法会被调用。
* 2、这个消息会被解码为 OUTBOUND_IN 类型的消息。
* 3、然后被转发给 ChannelPipeline 中的下一个 ChannelInboundHandler
*/
@Override
protected void decode(ChannelHandlerContext ctx, WebSocketFrame msg, List<Object> out) throws Exception {
ByteBuf byteBuf = msg.content().duplicate().retain();
if (msg instanceof BinaryWebSocketFrame) {
out.add(new MyWebSocketFrame(MyWebSocketFrame.FrameType.BINARY, byteBuf));
} else if (msg instanceof CloseWebSocketFrame) {
out.add(new MyWebSocketFrame(MyWebSocketFrame.FrameType.CLOSE, byteBuf));
} else if (msg instanceof TextWebSocketFrame) {
out.add(new MyWebSocketFrame(MyWebSocketFrame.FrameType.TEXT, byteBuf));
} else if (msg instanceof PingWebSocketFrame) {
out.add(new MyWebSocketFrame(MyWebSocketFrame.FrameType.PING, byteBuf));
} else if (msg instanceof PongWebSocketFrame) {
out.add(new MyWebSocketFrame(MyWebSocketFrame.FrameType.PONG, byteBuf));
} else if (msg instanceof ContinuationWebSocketFrame) {
out.add(new MyWebSocketFrame(MyWebSocketFrame.FrameType.CONTINUATION, byteBuf));
} 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 frameType;
private final ByteBuf byteBuf;
public MyWebSocketFrame(FrameType frameType, ByteBuf byteBuf) {
this.frameType = frameType;
this.byteBuf = byteBuf;
}
public FrameType getFrameType() {
return frameType;
}
public ByteBuf getByteBuf() {
return byteBuf;
}
}
}
这个类写的还是很棒的~~
参考资料:《Netty IN ACTION》
演示源代码:https://github.com/JMCuixy/NettyDemo
Netty 系列六(编解码器).的更多相关文章
- Netty4.x中文教程系列(六) 从头开始Bootstrap
Netty4.x中文教程系列(六) 从头开始Bootstrap 其实自从中文教程系列(五)一直不知道自己到底想些什么.加上忙着工作上出现了一些问题.本来想就这么放弃维护了.没想到有朋友和我说百度搜索推 ...
- Netty4.x中文教程系列(五)编解码器Codec
Netty4.x中文教程系列(五)编解码器Codec 上一篇文章详细解释了ChannelHandler的相关构架设计,版本和设计逻辑变更等等. 这篇文章主要在于讲述Handler里面的Codec,也就 ...
- 6. 彤哥说netty系列之Java NIO核心组件之Buffer
--日拱一卒,不期而至! 你好,我是彤哥,本篇是netty系列的第六篇. 简介 上一章我们一起学习了Java NIO的核心组件Channel,它可以看作是实体与实体之间的连接,而且需要与Buffer交 ...
- Netty系列之源码解析(一)
本文首发于微信公众号[猿灯塔],转载引用请说明出处 接下来的时间灯塔君持续更新Netty系列一共九篇 当前:Netty 源码解析(一)开始 Netty 源码解析(二): Netty 的 Channel ...
- CSS 魔法系列:纯 CSS 绘制各种图形《系列六》
我们的网页因为 CSS 而呈现千变万化的风格.这一看似简单的样式语言在使用中非常灵活,只要你发挥创意就能实现很多比人想象不到的效果.特别是随着 CSS3 的广泛使用,更多新奇的 CSS 作品涌现出来. ...
- WCF编程系列(六)以编程方式配置终结点
WCF编程系列(六)以编程方式配置终结点 示例一中我们的宿主程序非常简单:只是简单的实例化了一个ServiceHost对象,然后调用open方法来启动服务.而关于终结点的配置我们都是通过配置文件来 ...
- SQL Server 2008空间数据应用系列六:基于SQLCRL的空间数据可编程性
原文:SQL Server 2008空间数据应用系列六:基于SQLCRL的空间数据可编程性 友情提示,您阅读本篇博文的先决条件如下: 1.本文示例基于Microsoft SQL Server 2008 ...
- R语言数据分析系列六
R语言数据分析系列六 -- by comaple.zhang 上一节讲了R语言作图,本节来讲讲当你拿到一个数据集的时候怎样下手分析,数据分析的第一步.探索性数据分析. 统计量,即统计学里面关注的数据集 ...
- java基础解析系列(六)---深入注解原理及使用
java基础解析系列(六)---注解原理及使用 java基础解析系列(一)---String.StringBuffer.StringBuilder java基础解析系列(二)---Integer ja ...
随机推荐
- Jenkins关闭和重启实现方式.
1.关闭Jenkins 只需要在访问jenkins服务器的网址url地址后加上exit.例如我jenkins的地址http://localhost:8080/,那么我只需要在浏览器地址栏上敲下http ...
- IM群聊消息究竟是存1份(即扩散读)还是存多份(即扩散写)?
1.前言 IM的群聊消息,究竟存1份(即扩散读方式)还是存多份(即扩散写方式)? 上一篇文章<IM群聊消息的已读回执功能该怎么实现?>是说,“很容易想到,是存一份”,被网友们骂了,大家争论 ...
- Kali学习笔记19:NESSUS安装及使用
Nessus 百度百科:Nessus 是目前全世界最多人使用的系统漏洞扫描与分析软件.总共有超过75,000个机构使用Nessus 作为扫描该机构电脑系统的软件. 就我而言:漏洞扫描方面最强大的工具之 ...
- 应用监控CAT之cat-consumer源码阅读(二)
之前讲了 cat-client 进行cat埋点上报,那么上报给谁呢?以及后续故事如何?让我们来看看 cat-consumer 是如何接收处理的? 由cat-client发送数据,cat-consume ...
- HTML百宝箱(1从0开始)
标准格式(XHTML) l 元素必须正确嵌套 l 元素必须始终关闭 l 元素名和属性名必须小写 l 文档必须有且仅有一个根元素 l 属性值必须使用双引号括起来 l 声明文档为标 ...
- HC-06蓝牙模块的使用
HC-06蓝牙模块与HC-05的AT指令变化还是挺大的,在模块上电后红灯闪烁表示未连接成功,常亮表示连接成功,期间只要红灯处于闪烁即是进入了AT模式,可发送AT指令,灯常亮使用AT指令无效.下面是常用 ...
- Java-jsoup-解析HTML
/** * jsoup 是一款 Java 的HTML 解析器,可直接解析某个URL地址.HTML文本内容.它提供了一套非常省力的API,可通过DOM,CSS以及类似于JQuery的操作方法来取出和操 ...
- 使用ES6新数组方法(象C# Lambda表达式一样写查询语句)
let people = [ {id: 1, name: "a", age: 12}, {id: 2, name: "b", age: 13}, {id: 3, ...
- Elasticsearch 学习总结 - 相关配置补充说明
一. Elasticsearch的基本概念 term索引词,在elasticsearch中索引词(term)是一个能够被索引的精确值.foo,Foo Foo几个单词是不相同的索引词.索引词(ter ...
- 流式大数据计算实践(1)----Hadoop单机模式
一.前言 1.从今天开始进行流式大数据计算的实践之路,需要完成一个车辆实时热力图 2.技术选型:HBase作为数据仓库,Storm作为流式计算框架,ECharts作为热力图的展示 3.计划使用两台虚拟 ...