以前每天打交道的是汤姆卡特和杰蒂,现在变成了娜蒂。零零碎碎写些关于娜蒂的总结。
用Netty的同学,总是从定义或编写Message Decoder开始,在《Netty权威指南》里说了很多很多,但有些东西还是没有细说,要自己读一遍ByteToMessageDecoder的源码。
写Decoder又总是从解决TCP粘包开始,所谓TCP粘包,就是一条消息,比如100K大小的,需要拆分成多个TCP包发送到服务端,Netty必须收齐所有的包,把它们粘在一起,才能开始解码。
在Thrift里,解决粘包的方式很简单:每条消息是一个Frame,在每个Frame的前4个字节定义消息的长度。在客户端,为每个Frame调用一次flush发送消息。而在服务端,可以用灵活的LengthFieldBasedFrameDecoder,也可以自行编写更直接快速的一段。
1. ByteToMessageDecoder 是非线程安全的。
因为有 ByteBuf cumulation等成员变量,必须为每个Channel创建一个Decoder。
2. 底下的Decoder如果觉得当前的ByteBuf还不足以进行Frame解码,必须保证readerIndex没有变化。
这样ByteToMessageDecoder才会结束当前的 channelRead()函数,等待下一个TCP包输入。 所以上例当readerbleBytes()小于frameLength时,需要resetReaderIndex()。
3. 底下的Decoder完成Frame解码后,必须把当前ByteBuf的readerIndex升到最高,让buf.isReadable() 返回false。
ByteToMessageDecoder同时还看到out中插入了结果的话,才会认为解码完成了。所以上例要先readInt(),再readSlice(frameLength),把readerIndex升到最高,否则ByteToMessageDecoder会认为还有输入,继续调用你的decode()函数。
4. 解码完成后,ByteToMessageDecoder 才会主动调用ctx.fireChannelRead(out.get(i)),将结果发给Channel链里的下一个Handler。
Netty里的Handler链不是由外层框架自动循环遍历的,必须在每个Handler里主动调用ctx.fireChannelRead(msg),所以在读够数据前,业务的Handler都不会被调用。
5. 底下的Decoder如果用了slice() 从原ByteBuf中截取自己需要的部分,必须调用retain()让底下的原ByteBuf的refCount +1。
如果要截取部分内容,建议用slice()而不是copy()减少复制。但因为ByteToMessageDecoder会在解码结束后,调用原ByteBuf的release()释放它,所以上例调用了reatain()让它的refCount+1。
6. 在不断粘包过程中,其实是不断的把新输入的ByteBuf data writeBytes() 到 ByteBuf cumulation 里,cumulation 容量不足了还需要复制扩容,所以不要老记着“零复制”这句话,就以为世界会那么美好。
文章持续修订,转载请保留原链接: http://calvin1978.blogcn.com/articles/netty-decode.html