netty codec FrameDecoder ReplayingDecoder 编解码
Posted on 2016-06-03 11:14 bw_0927 阅读(1840) 评论(0) 编辑 收藏 举报http://bylijinnan.iteye.com/blog/1989319
http://bylijinnan.iteye.com/blog/1982618
https://github.com/bylijinnan/nettyLearn/tree/master/ljn-netty3-learn/src/main/java/com/ljn/handler/replay
LengthFieldBasedFrameDecoder: 以长度字段标记报文长度的报文,此种非常常见——报文本身的某个字段标识了报文的整体长度。
Netty也做了几个很实用的codec helper,这里给出简单的介绍。
1)FrameDecoder:FrameDecoder内部维护了一个 DynamicChannelBuffer成员来存储接收到的数据,它就像个抽象模板,把整个解码过程模板写好了,其子类只需实现decode函数即可。
FrameDecoder的直接实现类有两个:
(1)DelimiterBasedFrameDecoder是基于分割符 (比如\r\n)的解码器,可在构造函数中指定分割符。
(2)LengthFieldBasedFrameDecoder是基于长度字段的解码器。如果协 议 格式类似“内容长度”+内容、“固定头”+“内容长度”+动态内容这样的格式,就可以使用该解码器,其使用方法在API DOC上详尽的解释。
2)ReplayingDecoder: 它是FrameDecoder的一个变种子类,它相对于FrameDecoder是非阻塞解码。也就是说,使用 FrameDecoder时需要考虑到读到的数据有可能是不完整的,而使用ReplayingDecoder就可以假定读到了全部的数据。
3)ObjectEncoder 和ObjectDecoder:编码解码序列化的Java对象。
4)HttpRequestEncoder和 HttpRequestDecoder:http协议处理。
http://www.voidcn.com/blog/pentiumchen/article/p-2429513.html //博客里的其他netty文章都不错
http://qiankunli.github.io/2016/04/21/Java-Netty4.html
//提供一个对上层应用来说已经数据完整到达的抽象接口,上层应用不必关心完整性
//它是FrameDecoder的一个变种子类,它相对于FrameDecoder是非阻塞解码。也就是说,使用FrameDecoder时需要考虑到读到的数据有可能是不完整的,而使用ReplayingDecoder就可以假定读到了全部的数据。
//ReplayingDecoder 是抽象类,需要自己实现decode方法,return返回值,应该返回到下一个 decoder或者 handler里面,都需要自己写
ReplayingDecoderBuffer是对一个普通的ChannelBuffer的Wrapper,它的很多方法都是直接调用被wrap后的ChannelBuffer的方法,它们使用了几个checkIndex和checkReadableBytes函数:
public int readInt() {
checkReadableBytes(4);
return buffer.readInt();
}
private void checkIndex(int index) {
if (index > buffer.writerIndex()) { //不可读
throw replay;
}
}
当要读的数据还没有在这次处理中到达时,将抛出一个Replay Signal。沿着这个Replay Signal就能知道它的工作原理了。ReplayingDecoder部分源码如下
private final ReplayingDecoderBuffer replayable = new ReplayingDecoderBuffer(); // 装饰类
void callDecode(ChannelHandlerContext ctx, ByteBuf in, List<Object> out) {
// 由replayable代理对ByteBuf的操作
replayable.setCumulation(in);
int oldReaderIndex = checkpoint = in.readerIndex();
int outSize = out.size();
S oldState = state;
try {
decode(ctx, replayable, out);
} catch (ReplayError replay) { //检测到error后,恢复读指针
// Return to the checkpoint (or oldPosition) and retry.
int checkpoint = this.checkpoint;
if (checkpoint >= 0) {
cumulation.readerIndex(checkpoint);
} else {
// Called by cleanup() – no need to maintain the readerIndex
// anymore because the buffer has been released already.
}
}
}
假设decode方法中,buf没有四个字节,就调用了readint,decode方法会抛出Replay Signal,Replay Signal被ReplayingDecoder catch住。checkpoint两个目的:
- 处理buffer前(调用子类的decode方法前),事先记住当前buffer的readerIndex(学名叫checkpoint),假设数据没有四个字节,子类就调用了readint,子类的decode方法会抛出Replayerror,那么就当此次解析白干,恢复buffer的readerindex
- checkpoint还可以根据子类的状态做调整。第一点是数据到达不完全的情况,假设数据由header和body两部分组成,当header数据没有完全到达时,将header从buffer中清除掉(更新checkpoint成现在buf的readerIndex),那么在此之后收到消息的时候就不需要再处理header了,同时为了达到这个目的,解码器还需要保存当前的处理状态:记录当前是在处理header还是body,当处理完后,设置下一个状态。所以在Netty的Pipeline中,解码器也必须每一个channel一个,不能共用,因为它保存了处理的状态信息。
例子可以参见HttpObjectDecoder
分包传输
在TCP协议中,分包传输是非常,一份信息可能分几次达到目的地,在OIO中这是没有问题的,因为OIO是傻等式的,不读到完整的信息它是不会罢手的,但是NIO就不同了,它是基于事件的,只有有数据来了它才会去读取,那么问题来了,在读取到数据之后对数据进行业务解析时该如何处理?比如说我想解析一个整形数,但是当前只读取到了两个字节的数据,还有两个字节的数据在后面的传输包中,由于NIO的非阻塞性,业务数据的解析时机成了一个大问题,因为可能无法一次取到完整的数据。
基于上面这个问题,Netty框架设计了一个ReplayingDecoder来解决这种场景中的问题,ReplayingDecoder的核心原理是,当ReplayingDecoder在进行数据解析时,如果发现当前ByteBuf中所有可读数据并不完整,比如我想解析出一个整型数,但是ByteBuf中数据小于4个字节,那么此时会抛出一个Signal类型的Error,抛Error的操作在ReplayingDecoderBuffer中进行,一个ByteBuf的装饰器。在ReplayingDecoder会捕捉一个Error,捕捉到Signal之后会把ByteBuf中的读指针还原到之前的断点处(checkpoint,默认是ByteBuf的其实读位置),然后结束这次解析操作,等待下一次IO读事件。如果只是简单的整形数解析问题不大,但是如果数据解析逻辑复杂是,这种处理方式存在一个问题,在网络条件比较糟糕时,解析逻辑会反复执行多次,如果解析过程是一个耗CPU的操作,那么这对CPU是个大负担。可以通过ReplayingDecoder中的断点和状态机来解决这个问题,使用者可以在ReplayingDecoder中保存之前的解析结果、状态和读指针断点,举个例子,我要解析8个字节的数据,把前后四个字节都解析成整形数,并且把这两个数据相加当做解析结果,代码如下:
public class TowIntegerReplayingDecoder extends ReplayingDecoder<Integer> {
private static final int PARSE_1 = 1;
private static final int PARSE_2 = 2;
private int number1;
private int number2;
@Override
protected void decode(ChannelHandlerContext ctx, ByteBuf in,
List<Object> out) throws Exception {
switch (state()) {
case PARSE_1:
number1 = in.readInt(); //保存该状态解析出来的结果
checkpoint(PARSE_2); //本状态解析成功了(本例中表示第一个int解析成功了),设置下一步要进入的状态。
break;
case PARSE_2:
number2 = in.readInt();
checkpoint(PARSE_1);
out.add(number1 + number2);
break;
default:
break;
}
}
}
在代码中,把解析分成两个阶段,当一个阶段解析完成之后,记录第一个阶段的解析结果,并且更新解析状态和读指针,这样如果由于数据不完整导致第二阶段的解析无法完成,下次IO事件触发时,该解析器会直接进入第二阶段的解析,而不会重复第一阶段的解析,这样会减少重复解析大概率。基于这种设计,ReplayingDecoder必须是Channel独有的,它的实例不能被共享,每个Channel实例必须有个单独的ReplayingDecoder解析器实例,而且不能添加Sharable注解,因为它是有状态的,如果在多个Channel中共享了,那么状态就乱套了。
ReplayingDecoder是FrameDecoder的子类,不熟悉FrameDecoder的,可以先看看
http://bylijinnan.iteye.com/blog/1982618
API说,ReplayingDecoder简化了操作,比如:
FrameDecoder在decode时,需要判断数据是否接收完全:
- public class IntegerHeaderFrameDecoder extends FrameDecoder { //头是一个整数
- protected Object decode(ChannelHandlerContext ctx,
- Channel channel,
- ChannelBuffer buf) throws Exception {
- if (buf.readableBytes() < 4) { //可读大小小于int,头还没读满,return
- return null;
- }
- //头已经完整
- buf.markReaderIndex(); //在真正开始从buffer读取数据之前,都应该先调用markReaderIndex()设置回滚点if read fail
- int length = buf.readInt(); //从buffer中读出头的大小,这会使得readIndex前移
- if (buf.readableBytes() < length) { //剩余长度不够body体,reset 读指针
- buf.resetReaderIndex(); //读指针回滚到12行设置的mark处,没进行状态的保存
- return null;
- }
- return buf.readBytes(length);
- }
- }
这里会不停的有指针移动操作,效率有可能会不高,可参考/home/gitSrc/google_code/eddyserver/trunk/EddyServer/common/sdk/tcp_session.cc里保存一个下一次需要读取的大小
相当于把13行的length变成一个成员变量,在关联一个成员变量bool header_read_标志头是否读到了
¥_¥,
其实netty中已经有这种实现啦,就是下文的ReplayingDecoder里的checkpoint成员变量
而ReplayingDecoder则隐藏了这些判断,好像数据已经接收完全了一样:
- public class IntegerHeaderFrameDecoder
- extends ReplayingDecoder<VoidEnum> {
- protected Object decode(ChannelHandlerContext ctx,
- Channel channel,
- ChannelBuffer buf,
- VoidEnum state) throws Exception {
- int length = buf.readInt();
- return buf.readBytes(length);
- }
- }
可见,ReplayingDecoder使用起来要简单、直观
除此之外,
很多时候frame的结构不会是“length + content”这么简单,如果结构复杂了, FrameDecoder的if语句就会非常多,且先到达的sub-frame就会被多次decode 。
ReplayingDecoder允许用户自定义state,表示decode到哪一部分了,从而下一次数据到达时,直接从nextState开始decode
那ReplayingDecoder这是怎么做到“好像数据已经接收完全了一样”?
事实上它跟FrameDecoder的思路是一样的,只是它在数据接收不完全时,“悄悄地失败”。这主要是通过ReplayingDecoderBuffer来实现的:
- class ReplayingDecoderBuffer implements ChannelBuffer {
- private static final Error REPLAY = new ReplayError();
- private final ReplayingDecoder<?> parent;
- private boolean terminated;
- //ReplayingDecoder作为参数
- ReplayingDecoderBuffer(ReplayingDecoder<?> parent) {
- this.parent = parent;
- }
- /*ReplayingDecoderBuffer实际上是FrameDecoder里面的cumulation(类型为ChannelBuffer)
- FrameDecoder里面,对数据的累积接收,以及数据的读取,都是在cumulation, 而ReplayingDecoderBuffer对cumulation进行了封装,在数据不完全时,读取操作都会抛出Error
- */
- private ChannelBuffer buf() {
- return parent.internalBuffer();
- }
- public byte getByte(int index) {
- checkIndex(index);
- return buf().getByte(index);
- }
- //数据接收不完全时,抛出Error,在ReplayingDecoder里捕获
- private void checkIndex(int index) {
- if (index > buf().writerIndex()) {
- throw REPLAY;
- }
- }
- public abstract class ReplayingDecoder<T extends Enum<T>>
- extends FrameDecoder {
- //创建了ReplayingDecoderBuffer
- private final ReplayingDecoderBuffer replayable = new ReplayingDecoderBuffer(this);
- private T state;
- //标记着下一次数据读取的开始位置,也代表着已经decode到哪一部分了
- private int checkpoint;
- protected void checkpoint() {
- ChannelBuffer cumulation = this.cumulation;
- if (cumulation != null) {
- checkpoint = cumulation.readerIndex();
- } else {
- checkpoint = -1; // buffer not available (already cleaned up)
- }
- }
- protected void checkpoint(T state) {
- checkpoint();
- setState(state);
- }
- protected T setState(T newState) {
- T oldState = state;
- state = newState;
- return oldState;
- }
- //这个方法交由子类实现,子类可以定义更详细、语义更丰富的state
- protected abstract Object decode(ChannelHandlerContext ctx,
- Channel channel, ChannelBuffer buffer, T state) throws Exception;
- //重点在这个方法,注意对ReplayError的处理
- private void callDecode(
- ChannelHandlerContext context, Channel channel,
- ChannelBuffer input, ChannelBuffer replayableInput, SocketAddress remoteAddress) throws Exception {
- while (input.readable()) {
- int oldReaderIndex = checkpoint = input.readerIndex();
- Object result = null;
- T oldState = state;
- try {
- //如果数据接收还不完全,decode方法就会抛出ReplayError
- //因为decode方法会调用replayableInput(类型为ReplayingDecoderBuffer)的getXXX方法
- result = decode(context, channel, replayableInput, state);
- if (result == null) {
- //省略
- }
- } catch (ReplayError replay) {
- //数据不充足,回退到checkpoint,下一次messageReceived再试
- // Return to the checkpoint (or oldPosition) and retry.
- int checkpoint = this.checkpoint;
- if (checkpoint >= 0) {
- input.readerIndex(checkpoint);
- } else {
- // Called by cleanup() - no need to maintain the readerIndex
- // anymore because the buffer has been released already.
- }
- }
- if (result == null) {
- // Seems like more data is required.
- // Let us wait for the next notification.
- break;
- }
- // A successful decode
- unfoldAndFireMessageReceived(context, remoteAddress, result);
- }
- }
ReplayingDecoder的使用,API给出了一个简单的例子:
- public class IntegerHeaderFrameDecoder
- extends ReplayingDecoder<MyDecoderState> {
- private int length;
- public IntegerHeaderFrameDecoder() {
- // Set the initial state.
- super(MyDecoderState.READ_LENGTH);
- }
- protected Object decode(ChannelHandlerContext ctx,
- Channel channel,
- ChannelBuffer buf,
- MyDecoderState state) throws Exception {
- switch (state) {
- case READ_LENGTH:
- length = buf.readInt();
- //设置下一次的decode从哪一个state开始
- checkpoint(MyDecoderState.READ_CONTENT);
- case READ_CONTENT:
- ChannelBuffer frame = buf.readBytes(length);
- checkpoint(MyDecoderState.READ_LENGTH);
- return frame;
- default:
- throw new Error("Shouldn't reach here.");
- }
- }
- }
注意到上面的switch语句是不需要break的,因为case READ_LENGTH后:
1.如果数据不充足,那就会在buf.readBytes(length)里面抛出ReplayError,相当于有了break语句
2.如果数据充足,那就接着decode,最终成功并 return frame
除了API以外,下面这两篇文章写得非常好:
http://biasedbit.com/netty-tutorial-replaying-decoder/
http://biasedbit.com/an-enhanced-version-of-replayingdecoder-for-netty/
其中第二个例子,文章中给的代码不是很完整,我补全并做了一个简单的测试:
https://github.com/bylijinnan/nettyLearn/tree/master/ljn-netty3-learn/src/main/java/com/ljn/handler/replay
===========================
FrameDecoder
Netty 3.x的user guide里FrameDecoder的例子,有几个疑问:
1.文档说:FrameDecoder calls decode method with an internally maintained cumulative buffer whenever new data is received.
为什么每次有新数据到达时,都会调用decode方法?
2.Decoder与其他handler在pipeline的顺序是怎样的?谁先谁后?
3.所要接收的数据可能要经过多次才能接收完全,那这之前数据是如何保留?
先说结论:
1.因为每一次消息到达时都会触发pipeline的Upstream处理流程,最终会调用handler的messageReceived方法,而FrameDecoder的messageRecieved方法会调用decode方法
2.Decoder在前
3.FrameDecoder维护了一个ChannelBuffer(作为它的field)
文档中TimeDecoder的decode方法:
- protected Object decode(
- ChannelHandlerContext ctx, Channel channel, ChannelBuffer buffer)2 {
- if (buffer.readableBytes() < 4) {
- return null; 3
- }
- return buffer.readBytes(4);
- }
查看一下FrameDecoder 源码:
FrameDecoder继承自SimpleChannelUpstreamHandler,而SimpleChannelUpstreamHandler实现了ChannelUpstreamHandler接口
ChannelUpstreamHandler很简单,只定义了一个handleUpstream方法
-->ChannlPipeline 开始处理Upstream,会调用sendUpstream方法,
-->调用SimpleChannelUpstreamHandler(也就是FrameDecoder)的handleUpstream方法
而SimpleChannelUpstreamHandler的handleUpstream会触发messageReceived方法:
- public void handleUpstream(
- ChannelHandlerContext ctx, ChannelEvent e) throws Exception {
- if (e instanceof MessageEvent) {
- messageReceived(ctx, (MessageEvent) e);
- }
- /*omit others*/
-->FrameDecoder重写了messageReceived方法,在messageReceived里面,就会调用到文章开头提到的decode方法:
- public void messageReceived(
- ChannelHandlerContext ctx, MessageEvent e) {
- /*只保留关键代码*/
- ChannelBuffer input = (ChannelBuffer) e.getMessage();
- callDecode(ctx, e.getChannel(), input, e.getRemoteAddress());
- updateCumulation(ctx, input);
- }
而callDecode方法的关键代码是这样的:
- private void callDecode(
- ChannelHandlerContext context, Channel channel,
- ChannelBuffer cumulation, SocketAddress remoteAddress) throws Exception {
- while (cumulation.readable()) {
- Object frame = decode(context, channel, cumulation);
- unfoldAndFireMessageReceived(context, remoteAddress, frame);
- }
- }
在unfoldAndFireMessageReceived方法里面,会调用Channels.fireMessageReceived,最后会调用ctx.sendUpstream,事件会交给下一个Handler来处理
在示例中,下一个Handler就是TimeClientHandler
因此,如果TimeDecoder(extends FrameDecoder)的decode方法返回了UnixTime对象,那TimeClientHandler就可以直接拿到并强制转换成UnixTime对象:
TimeDecoder:
- protected Object decode(
- ChannelHandlerContext ctx, Channel channel, ChannelBuffer buffer) {
- if (buffer.readableBytes() < 4) {
- return null;
- }
- return new UnixTime(buffer.readInt());1
- }
TimeClientHandler:
- public void messageReceived(ChannelHandlerContext ctx, MessageEvent e) {
- UnixTime m = (UnixTime) e.getMessage();
- }
pipeline里面对Upstream的处理,在handler链表里面,是从head到tail,因此TimeDecoder应在TimeClientHandler之前:
- bootstrap.setPipelineFactory(new ChannelPipelineFactory() {
- public ChannelPipeline getPipeline() {
- return Channels.pipeline(
- new TimeDecoder(),
- new TimeClientHandler());
- }
- });
开发中,只需要重写FrameDecoder.decode方法就可以了
由于Netty接收到数据后,会不断触发整个Upstream的处理流程,像上面分析的那样,因此,decode方法就会不断被调用
最后一个问题,FrameDecoder是怎样保留之前接收到的数据呢?
原来FrameDecoder维护了一个ChannelBuffer(作为它的field),源码里命名为cumulation
cumulation会一直保留数据,并按需扩容,直到所需要的数据全部接收到达为止
看看源码:
- //decode得到的结果可能是一个数组或者集合,如果unfold=true,则让每一个元素(接收到的每一段数据)都触发一个fireMessageReceived事件, 即使数据还不完整,不能被解析出来
- private boolean unfold;
- protected ChannelBuffer cumulation; //将每次接收到的数据累积起来,直到数据接收完全
- private volatile ChannelHandlerContext ctx;
- //cumulation使用了CompositeChannelBuffer来避免“memory copy”(ChannelBuffers.wrappedBuffer)
- //cumulation是否需要开辟新内存空间并复制数据,取决于两个值:
- //1.copyThreshold是从cumulation的总大小来限制
- //2.maxCumulationBufferComponents是从cumulation的component的个数来限制
- //如果cumulation的大小超过此值,就把cumulation复制到一个新创建的ChannelBuffer里面
- private int copyThreshold;
- //如果cumulation中component的个数超过此值,则像上面那样进行复制
- private int maxCumulationBufferComponents;
- //这个方法体现了上面据说的第2个限制:maxCumulationBufferComponents
- protected ChannelBuffer appendToCumulation(ChannelBuffer input) {
- ChannelBuffer cumulation = this.cumulation;
- assert cumulation.readable();
- if (cumulation instanceof CompositeChannelBuffer) {
- // Make sure the resulting cumulation buffer has no more than the configured components.
- CompositeChannelBuffer composite = (CompositeChannelBuffer) cumulation;
- if (composite.numComponents() >= maxCumulationBufferComponents) {
- cumulation = composite.copy();
- }
- }
- this.cumulation = input = ChannelBuffers.wrappedBuffer(cumulation, input);
- return input;
- }
- //这个方法体现了上面据说的第1个限制:copyThreshold
- protected ChannelBuffer updateCumulation(ChannelHandlerContext ctx, ChannelBuffer input) {
- ChannelBuffer newCumulation;
- int readableBytes = input.readableBytes();
- if (readableBytes > 0) {
- int inputCapacity = input.capacity();
- //超限了
- if (readableBytes < inputCapacity && inputCapacity > copyThreshold) {
- //newCumulationBuffer方法新建一个指定大小的ChannelBuffer
- cumulation = newCumulation = newCumulationBuffer(ctx, input.readableBytes());
- cumulation.writeBytes(input);
- } else {
- if (input.readerIndex() != 0) {
- cumulation = newCumulation = input.slice();
- } else {
- cumulation = newCumulation = input;
- }
- }
- } else {
- cumulation = newCumulation = null;
- }
- return newCumulation;
- }
- //unfold的用途
- protected final void unfoldAndFireMessageReceived(
- ChannelHandlerContext context, SocketAddress remoteAddress, Object result) {
- if (unfold) {
- if (result instanceof Object[]) {
- for (Object r: (Object[]) result) {
- Channels.fireMessageReceived(context, r, remoteAddress);
- }
- } else if (result instanceof Iterable<?>) {
- for (Object r: (Iterable<?>) result) {
- Channels.fireMessageReceived(context, r, remoteAddress);
- }
- } else {
- Channels.fireMessageReceived(context, result, remoteAddress);
- }
- } else {
- Channels.fireMessageReceived(context, result, remoteAddress);
- }
- }
======================
http://jinjian.blog.com/2010/10/18/netty-%E4%B8%AD%E5%8D%8F%E8%AE%AE%E7%9A%84-%E7%BC%96%E7%A0%81-%E5%92%8C-%E8%A7%A3%E7%A0%81-%EF%BC%9A-replayingdecoder/
Netty 除了提供了一个基于Event进行IO异步处理的高性能平台外,还提供了Http协议的实现,而应用程序的性能除了与IO本身的处理方式有关外,对于具体协议的编解码也关系到程序的性能。尤其解码器更是需要根据所收集到的数据来工作,有可能需要一些数据,
但是这些数据却还没有到达,这个时候应该怎么处理了?
Netty的 HttpMessageDecoder 继承自 ReplayingDecoder。 而ReplayingDecoder类对数据是否全部到达这个问题进行了封装和屏蔽。它是如何工作的?它的优缺点有哪些了?
Netty中的数据都放置于ChannelBuffer中,当和数据相关的时候,要处理特殊的收据处理的情况下,当然也需要”特殊”的Buffer了,与之相关的有一个ReplayingDecoderBuffer类。这个类是对一个普通的ChannelBuffer的Wrapper,
它的很多方法都是直接调用被wrap的ChannelBuffer的方法,那么对于需要的数据不在Buffer中,如何处理了?它提供了几个checkIndex 函数例如:
private void checkIndex(int index) {
if (index > buffer.writerIndex()) {
throw REPLAY;
}
}
当要读的数据还没有在这次处理中到达时,将抛出一个ReplayError。沿着这个Error就能知道它的工作原理了。
需要搞清楚:1. Error 谁来处理? 以及 2. 如何处理? 第一个答案是:ReplayingDecoder,在处理收到的消息的方法体中(messageReceived),解码器调用callDecode来进行解码,解码器然后再调用decode方法,不同的协议需要实现该方法,当有error时:
} catch (ReplayError replay) {
// Return to the checkpoint (or oldPosition) and retry.
int checkpoint = this.checkpoint;
if (checkpoint >= 0) {
cumulation.readerIndex(checkpoint);
} else {
// Called by cleanup() – no need to maintain the readerIndex
// anymore because the buffer has been released already.
}
}
ReplayingDecoder 做的工作看上去很简单:检查checkpoint,如果设置了checkpoint那么将cumulation的读指针进行重新设置。
这里的checkpoint是做什么的了?当数据没有完全到达时,解码的时候就会有error抛出(特别是当网络情况比较差的时候),为了避免每次出错后,都需要重新进行再一次对收到的内容进行处理,可以通过checkpoint方法将已经读取的内容从buffer中清除掉,
例如当header读取完后,将header从buffer中清除掉,那么在此收到消息的时候就不需要再处理header了,同时为了达到这个目的,解码器还需要保存当前的处理状态:记录当前是在处理header还是content,当处理完后,设置下一个状态。
所以在Netty的Pipeline中,解码器也必须每一个channel一个,不能共用,因为它保存了处理的状态信息。这种处理方式有它的优点也有弱点:
优点:1.将数据的再收集进行了封装和对下(具体的协议实现)屏蔽 2.逻辑清晰,对于http协议来说,每处理完http消息的一个完整部分时,设置checkpoint。
缺点:1. 有可能有性能问题(当网路状况不好的时候) 2. 对buffer的有些操作进行了限制。