博客园  :: 首页  :: 新随笔  :: 联系 :: 订阅 订阅  :: 管理

netty codec FrameDecoder ReplayingDecoder 编解码

Posted on 2016-06-03 11:14  bw_0927  阅读(1836)  评论(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两个目的:

  1. 处理buffer前(调用子类的decode方法前),事先记住当前buffer的readerIndex(学名叫checkpoint),假设数据没有四个字节,子类就调用了readint,子类的decode方法会抛出Replayerror,那么就当此次解析白干,恢复buffer的readerindex
  2. 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时,需要判断数据是否接收完全: 

Java代码  收藏代码
  1. public class IntegerHeaderFrameDecoder extends FrameDecoder {    //头是一个整数
  2.   
  3.    protected Object decode(ChannelHandlerContext ctx,  
  4.                            Channel channel,  
  5.                            ChannelBuffer buf) throws Exception {  
  6.   
  7.      if (buf.readableBytes() < 4) {    //可读大小小于int,头还没读满,return
  8.         return null;  
  9.      }  
  10.   
  11.      //头已经完整
  12.      buf.markReaderIndex();    //在真正开始从buffer读取数据之前,都应该先调用markReaderIndex()设置回滚点if read fail
  13.      int length = buf.readInt();    //从buffer中读出头的大小,这会使得readIndex前移
  14.   
  15.      if (buf.readableBytes() < length) {    //剩余长度不够body体,reset 读指针
  16.         buf.resetReaderIndex();  //读指针回滚到12行设置的mark处,没进行状态的保存
  17.         return null;  
  18.      }  
  19.   
  20.      return buf.readBytes(length);  
  21.    }  
  22.  }  

 

这里会不停的有指针移动操作,效率有可能会不高,可参考/home/gitSrc/google_code/eddyserver/trunk/EddyServer/common/sdk/tcp_session.cc里保存一个下一次需要读取的大小

相当于把13行的length变成一个成员变量,在关联一个成员变量bool header_read_标志头是否读到了

¥_¥,

其实netty中已经有这种实现啦,就是下文的ReplayingDecoder里的checkpoint成员变量



ReplayingDecoder则隐藏了这些判断,好像数据已经接收完全了一样: 

Java代码  收藏代码
  1. public class IntegerHeaderFrameDecoder  
  2.      extends ReplayingDecoder<VoidEnum> {  
  3.   
  4.   protected Object decode(ChannelHandlerContext ctx,  
  5.                           Channel channel,  
  6.                           ChannelBuffer buf,  
  7.                           VoidEnum state) throws Exception {  
  8.  int length = buf.readInt();  
  9.     return buf.readBytes(length);  
  10.   }  
  11. }  



可见,ReplayingDecoder使用起来要简单、直观 

除此之外, 
很多时候frame的结构不会是“length + content”这么简单,如果结构复杂了, FrameDecoder的if语句就会非常多,且先到达的sub-frame就会被多次decode 。
ReplayingDecoder允许用户自定义state,表示decode到哪一部分了,从而下一次数据到达时,直接从nextState开始decode 

那ReplayingDecoder这是怎么做到“好像数据已经接收完全了一样”? 
事实上它跟FrameDecoder的思路是一样的,只是它在数据接收不完全时,“悄悄地失败”。这主要是通过ReplayingDecoderBuffer来实现的: 

Java代码  收藏代码
  1. class ReplayingDecoderBuffer implements ChannelBuffer {  
  2.   
  3.    private static final Error REPLAY = new ReplayError();  
  4.   
  5.    private final ReplayingDecoder<?> parent;  
  6.    private boolean terminated;  
  7.   
  8. //ReplayingDecoder作为参数  
  9.    ReplayingDecoderBuffer(ReplayingDecoder<?> parent) {  
  10.        this.parent = parent;  
  11.    }  
  12.   
  13. /*ReplayingDecoderBuffer实际上是FrameDecoder里面的cumulation(类型为ChannelBuffer) 
  14. FrameDecoder里面,对数据的累积接收,以及数据的读取,都是在cumulation, 而ReplayingDecoderBuffer对cumulation进行了封装,在数据不完全时,读取操作都会抛出Error 
  15. */  
  16.    private ChannelBuffer buf() {  
  17.        return parent.internalBuffer();  
  18.    }  
  19.   
  20. public byte getByte(int index) {  
  21.        checkIndex(index);  
  22.        return buf().getByte(index);  
  23.    }  
  24.   
  25. //数据接收不完全时,抛出Error,在ReplayingDecoder里捕获  
  26. private void checkIndex(int index) {  
  27.        if (index > buf().writerIndex()) {  
  28.            throw REPLAY;  
  29.        }  
  30.    }  



Java代码  收藏代码
  1. public abstract class ReplayingDecoder<T extends Enum<T>>  
  2.        extends FrameDecoder {  
  3.   
  4. //创建了ReplayingDecoderBuffer  
  5.    private final ReplayingDecoderBuffer replayable = new ReplayingDecoderBuffer(this);  
  6.    private T state;   
  7.   
  8. //标记着下一次数据读取的开始位置,也代表着已经decode到哪一部分了  
  9.    private int checkpoint;  
  10.   
  11. protected void checkpoint() {  
  12.        ChannelBuffer cumulation = this.cumulation;  
  13.        if (cumulation != null) {  
  14.            checkpoint = cumulation.readerIndex();  
  15.        } else {  
  16.            checkpoint = -1; // buffer not available (already cleaned up)  
  17.        }  
  18.    }  
  19.   
  20.    protected void checkpoint(T state) {  
  21.        checkpoint();  
  22.        setState(state);  
  23.    }  
  24.   
  25. protected T setState(T newState) {  
  26.        T oldState = state;  
  27.        state = newState;  
  28.        return oldState;  
  29.    }  
  30.   
  31. //这个方法交由子类实现,子类可以定义更详细、语义更丰富的state  
  32.  protected abstract Object decode(ChannelHandlerContext ctx,  
  33.            Channel channel, ChannelBuffer buffer, T state) throws Exception;  
  34.           
  35. //重点在这个方法,注意对ReplayError的处理  
  36. private void callDecode(  
  37.            ChannelHandlerContext context, Channel channel,  
  38.            ChannelBuffer input, ChannelBuffer replayableInput, SocketAddress remoteAddress) throws Exception {  
  39.        while (input.readable()) {  
  40.            int oldReaderIndex = checkpoint = input.readerIndex();  
  41.            Object result = null;  
  42.            T oldState = state;  
  43.            try {  
  44.           
  45.             //如果数据接收还不完全,decode方法就会抛出ReplayError  
  46.             //因为decode方法会调用replayableInput(类型为ReplayingDecoderBuffer)的getXXX方法  
  47.                result = decode(context, channel, replayableInput, state);  
  48.                if (result == null) {  
  49.                 //省略  
  50.                }  
  51.            } catch (ReplayError replay) {  
  52.           
  53.             //数据不充足,回退到checkpoint,下一次messageReceived再试  
  54.                // Return to the checkpoint (or oldPosition) and retry.  
  55.                int checkpoint = this.checkpoint;  
  56.                if (checkpoint >= 0) {  
  57.                    input.readerIndex(checkpoint);  
  58.                } else {  
  59.                    // Called by cleanup() - no need to maintain the readerIndex  
  60.                    // anymore because the buffer has been released already.  
  61.                }  
  62.            }  
  63.   
  64.            if (result == null) {  
  65.                // Seems like more data is required.  
  66.                // Let us wait for the next notification.  
  67.                break;  
  68.            }  
  69.   
  70.            // A successful decode  
  71.            unfoldAndFireMessageReceived(context, remoteAddress, result);  
  72.        }  
  73.    }  



ReplayingDecoder的使用,API给出了一个简单的例子: 

Java代码  收藏代码
  1.  public class IntegerHeaderFrameDecoder  
  2.      extends ReplayingDecoder<MyDecoderState> {  
  3.   
  4.   private int length;  
  5.   
  6.   public IntegerHeaderFrameDecoder() {  
  7.     // Set the initial state.  
  8.     super(MyDecoderState.READ_LENGTH);  
  9.   }  
  10.   
  11.   protected Object decode(ChannelHandlerContext ctx,  
  12.                           Channel channel,  
  13.                           ChannelBuffer buf,  
  14.                           MyDecoderState state) throws Exception {  
  15.     switch (state) {  
  16.     case READ_LENGTH:  
  17.       length = buf.readInt();  
  18.      
  19.    //设置下一次的decode从哪一个state开始  
  20.       checkpoint(MyDecoderState.READ_CONTENT);  
  21.     case READ_CONTENT:  
  22.       ChannelBuffer frame = buf.readBytes(length);  
  23.       checkpoint(MyDecoderState.READ_LENGTH);  
  24.       return frame;  
  25.     default:  
  26.       throw new Error("Shouldn't reach here.");  
  27.     }  
  28.   }  
  29. }  



注意到上面的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方法: 

Java代码  收藏代码
  1. protected Object decode(  
  2.             ChannelHandlerContext ctx, Channel channel, ChannelBuffer buffer)2 {  
  3.               
  4.         if (buffer.readableBytes() < 4) {  
  5.             return null; 3  
  6.         }  
  7.           
  8.         return buffer.readBytes(4);  
  9.     }  





查看一下FrameDecoder 源码: 

FrameDecoder继承自SimpleChannelUpstreamHandler,而SimpleChannelUpstreamHandler实现了ChannelUpstreamHandler接口 
ChannelUpstreamHandler很简单,只定义了一个handleUpstream方法 


-->ChannlPipeline 开始处理Upstream,会调用sendUpstream方法, 
-->调用SimpleChannelUpstreamHandler(也就是FrameDecoder)的handleUpstream方法 
而SimpleChannelUpstreamHandler的handleUpstream会触发messageReceived方法: 

Java代码  收藏代码
  1. public void handleUpstream(  
  2.                 ChannelHandlerContext ctx, ChannelEvent e) throws Exception {  
  3.   
  4.             if (e instanceof MessageEvent) {  
  5.                 messageReceived(ctx, (MessageEvent) e);  
  6.             }   
  7.             /*omit others*/  



-->FrameDecoder重写了messageReceived方法,在messageReceived里面,就会调用到文章开头提到的decode方法: 

Java代码  收藏代码
  1. public void messageReceived(  
  2.             ChannelHandlerContext ctx, MessageEvent e) {  
  3.                   
  4.                 /*只保留关键代码*/  
  5.                 ChannelBuffer input = (ChannelBuffer) e.getMessage();  
  6.                 callDecode(ctx, e.getChannel(), input, e.getRemoteAddress());  
  7.                 updateCumulation(ctx, input);  
  8.         }  
  9.       



而callDecode方法的关键代码是这样的: 

Java代码  收藏代码
  1. private void callDecode(  
  2.             ChannelHandlerContext context, Channel channel,  
  3.             ChannelBuffer cumulation, SocketAddress remoteAddress) throws Exception {  
  4.   
  5.             while (cumulation.readable()) {  
  6.                 Object frame = decode(context, channel, cumulation);  
  7.                 unfoldAndFireMessageReceived(context, remoteAddress, frame);  
  8.             }  
  9.         }  



在unfoldAndFireMessageReceived方法里面,会调用Channels.fireMessageReceived,最后会调用ctx.sendUpstream,事件会交给下一个Handler来处理 
在示例中,下一个Handler就是TimeClientHandler 
因此,如果TimeDecoder(extends FrameDecoder)的decode方法返回了UnixTime对象,那TimeClientHandler就可以直接拿到并强制转换成UnixTime对象: 
TimeDecoder: 

Java代码  收藏代码
  1. protected Object decode(  
  2.                 ChannelHandlerContext ctx, Channel channel, ChannelBuffer buffer) {  
  3.             if (buffer.readableBytes() < 4) {  
  4.                 return null;  
  5.             }  
  6.             return new UnixTime(buffer.readInt());1  
  7.         }  



TimeClientHandler: 

Java代码  收藏代码
  1. public void messageReceived(ChannelHandlerContext ctx, MessageEvent e) {  
  2.             UnixTime m = (UnixTime) e.getMessage();  
  3.         }  




pipeline里面对Upstream的处理,在handler链表里面,是从head到tail,因此TimeDecoder应在TimeClientHandler之前: 

Java代码  收藏代码
  1. bootstrap.setPipelineFactory(new ChannelPipelineFactory() {  
  2.             public ChannelPipeline getPipeline() {  
  3.                 return Channels.pipeline(  
  4.                         new TimeDecoder(),  
  5.                         new TimeClientHandler());  
  6.             }  
  7.         });  




开发中,只需要重写FrameDecoder.decode方法就可以了 
由于Netty接收到数据后,会不断触发整个Upstream的处理流程,像上面分析的那样,因此,decode方法就会不断被调用 


最后一个问题,FrameDecoder是怎样保留之前接收到的数据呢? 
原来FrameDecoder维护了一个ChannelBuffer(作为它的field),源码里命名为cumulation 
cumulation会一直保留数据,并按需扩容,直到所需要的数据全部接收到达为止 

看看源码: 

Java代码  收藏代码
  1. //decode得到的结果可能是一个数组或者集合,如果unfold=true,则让每一个元素(接收到的每一段数据)都触发一个fireMessageReceived事件, 即使数据还不完整,不能被解析出来
  2.    private boolean unfold;  
  3.    protected ChannelBuffer cumulation;  //将每次接收到的数据累积起来,直到数据接收完全  
  4.    private volatile ChannelHandlerContext ctx;  
  5.   
  6. //cumulation使用了CompositeChannelBuffer来避免“memory copy”(ChannelBuffers.wrappedBuffer)  
  7. //cumulation是否需要开辟新内存空间并复制数据,取决于两个值:  
  8. //1.copyThreshold是从cumulation的总大小来限制  
  9. //2.maxCumulationBufferComponents是从cumulation的component的个数来限制  
  10.   
  11. //如果cumulation的大小超过此值,就把cumulation复制到一个新创建的ChannelBuffer里面  
  12.    private int copyThreshold;         
  13.   
  14. //如果cumulation中component的个数超过此值,则像上面那样进行复制  
  15.    private int maxCumulationBufferComponents;  
  16.   
  17. //这个方法体现了上面据说的第2个限制:maxCumulationBufferComponents  
  18.    protected ChannelBuffer appendToCumulation(ChannelBuffer input) {  
  19.        ChannelBuffer cumulation = this.cumulation;  
  20.        assert cumulation.readable();  
  21.        if (cumulation instanceof CompositeChannelBuffer) {  
  22.            // Make sure the resulting cumulation buffer has no more than the configured components.  
  23.            CompositeChannelBuffer composite = (CompositeChannelBuffer) cumulation;  
  24.            if (composite.numComponents() >= maxCumulationBufferComponents) {  
  25.                cumulation = composite.copy();  
  26.            }  
  27.        }  
  28.   
  29.        this.cumulation = input = ChannelBuffers.wrappedBuffer(cumulation, input);  
  30.        return input;  
  31.    }  
  32.   
  33. //这个方法体现了上面据说的第1个限制:copyThreshold  
  34.    protected ChannelBuffer updateCumulation(ChannelHandlerContext ctx, ChannelBuffer input) {  
  35.        ChannelBuffer newCumulation;  
  36.        int readableBytes = input.readableBytes();  
  37.        if (readableBytes > 0) {  
  38.            int inputCapacity = input.capacity();  
  39.   
  40.         //超限了  
  41.            if (readableBytes < inputCapacity && inputCapacity > copyThreshold) {  
  42.           
  43.             //newCumulationBuffer方法新建一个指定大小的ChannelBuffer  
  44.                cumulation = newCumulation = newCumulationBuffer(ctx, input.readableBytes());  
  45.                cumulation.writeBytes(input);  
  46.            } else {  
  47.                if (input.readerIndex() != 0) {  
  48.                    cumulation = newCumulation = input.slice();  
  49.                } else {  
  50.                    cumulation = newCumulation = input;  
  51.                }  
  52.            }  
  53.        } else {  
  54.            cumulation = newCumulation = null;  
  55.        }  
  56.        return newCumulation;  
  57.    }  
  58.   
  59. //unfold的用途  
  60. protected final void unfoldAndFireMessageReceived(  
  61.            ChannelHandlerContext context, SocketAddress remoteAddress, Object result) {  
  62.        if (unfold) {  
  63.            if (result instanceof Object[]) {  
  64.                for (Object r: (Object[]) result) {  
  65.                    Channels.fireMessageReceived(context, r, remoteAddress);  
  66.                }  
  67.            } else if (result instanceof Iterable<?>) {  
  68.                for (Object r: (Iterable<?>) result) {  
  69.                    Channels.fireMessageReceived(context, r, remoteAddress);  
  70.                }  
  71.            } else {  
  72.                Channels.fireMessageReceived(context, result, remoteAddress);  
  73.            }  
  74.        } else {  
  75.            Channels.fireMessageReceived(context, result, remoteAddress);  
  76.        }  
  77.    }  



======================

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的有些操作进行了限制。