《Netty权威指南》里没有说到的Decoder编写细节

以前每天打交道的是汤姆卡特和杰蒂,现在变成了娜蒂。零零碎碎写些关于娜蒂的总结。

用Netty的同学,总是从编写Decoder开始,在《Netty权威指南》里说了很多很多,但有些细节还是没有说到。带着疑问,昨晚读了一遍ByteToMessageDecoder的源码。

写Decoder又总是从解决TCP粘包开始,所谓TCP粘包,就是一条消息,比如100K的,会拆成多个TCP包发送到服务端,Netty必须收齐所有的包,把它们粘在一起,才能开始解码。

在Thrift里,解决粘包的方式很简单:每条消息是一个Frame,在每个Frame的前4个字节定义消息的长度。在客户端,为每个Frame调用一次flush发送消息。而在服务端,可以用自带的灵活的LengthFieldBasedFrameDecoder,也可以自行编写更直接明了的一段。

1. ByteToMessageDecoder 是非线程安全的。

因为有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()函数,期待你继续给out插入结果。

4. 解码完成后,ByteToMessageDecoder才会主动调用ctx.fireChannelRead(out.get(i)),将结果发给Handler链里的下一位。

Netty里的Handler链不是由外层框架自动循环遍历的,必须在每个Handler里主动调用ctx.fireChannelRead(msg),所以在读够数据前,业务的Handler都不会被调用。

5. 底下的Decoder如果用了slice() 从原ByteBuf中截取自己需要的部分,必须调用retain()让底下的原ByteBuf的refCount +1。

如果要截取部分内容,建议用slice()而不是copy()减少复制。但因为ByteToMessageDecoder会在解码结束后,调用原ByteBuf的release()释放它,所以上例调用了reatain()来刀下留人。

6. 在不断粘包过程中,其实是不断的把新输入的ByteBuf data writeBytes() 到 ByteBuf cumulation 里,cumulation 容量不足了还需要复制扩容,所以不要老记着“零复制”这句话,就以为世界会那么美好。

7. 顺便说一下 MessageToByteEncoder 和 MessageToMessageEncoder,前者会创建一个初始256 bytes的output buf给子类的encode()函数去输出,而后者则让encode()函数自行的创建对象或bytebuf,放入out中。两者都会将原始的Message release掉。