最近在寫一個應用監控的項目,使用netty作為資料傳輸。因為剛開始寫,沒有使用Protobuf之類的作為編碼工具,隻是使用的是netty自帶的LengthFieldBasedFrameDecoder作為封包解析工具,自定義編碼解碼類,實作資料傳輸。
本來一切正常,結果在昨天測試的過程中,傳輸的資料體總是少16個字元,甚是奇怪。
翻來覆去查問題,後來仔細檢視封包内容,才發現封包中有8個漢字。這才想到,中文位元組長度不能使用java.lang.String的length()方法擷取。應該使用的是getBytes()方法轉成位元組數組,在通過數組的length屬性擷取長度。
比如:
"abcd".length()的結果是:4
"abcd".getBytes().length的結果是:4
"中國威武".length()的結果是:4
"中國威武".getBytes().length的結果是:12
"中國v5".length()的結果是:4
"中國v5".getBytes().length的結果是:8
例子簡單,但也能說明問題。在這裡每個中文位元組長度是3,英文字母、數字、英文标點是1。
是以在我的測試代碼中,存在的8個漢字使用length()方法擷取的長度是8,比getBytes()方法的位元組數組長度少了16,是以在傳輸過程中總是少了16個字元(英文字元長度是1)。
總的來說,在對中文進行轉換位元組的時候一定要注意,千萬不要想當然的使用length()方法。還是要根據具體情況多試試。特立文标記此錯誤
下面附上代碼:
編碼器:MessageEncoder
import cn.howardliu.monitor.cynomys.net.struct.Message;
import io.netty.buffer.ByteBuf;
import io.netty.channel.ChannelHandlerContext;
import io.netty.handler.codec.MessageToByteEncoder;
import io.netty.util.CharsetUtil;
public class MessageEncoder extends MessageToByteEncoder<Message> {
@Override
protected void encode(ChannelHandlerContext ctx, Message msg, ByteBuf out) throws Exception {
if (msg == null || msg.getHeader() == null) {
throw new IllegalArgumentException("the encode message is null.");
}
out.writeInt(msg.getHeader().getCrcCode());
out.writeInt(msg.getHeader().getLength());
out.writeInt(msg.getHeader().getOpaque());
out.writeInt(msg.getHeader().getTag().length());
out.writeCharSequence(msg.getHeader().getTag(), CharsetUtil.UTF_8);
out.writeInt(msg.getHeader().getSysName().length());
out.writeCharSequence(msg.getHeader().getSysName(), CharsetUtil.UTF_8);
out.writeInt(msg.getHeader().getSysCode().length());
out.writeCharSequence(msg.getHeader().getSysCode(), CharsetUtil.UTF_8);
out.writeByte(msg.getHeader().getType());
out.writeByte(msg.getHeader().getCode());
out.writeByte(msg.getHeader().getFlagPath());
if (msg.getBody() == null) {
out.writeInt(0);
} else {
out.writeInt(msg.getBody().getBytes().length);
out.writeCharSequence(msg.getBody(), CharsetUtil.UTF_8);
}
out.setInt(4, out.readableBytes() - 8);
}
}
解碼器:MessageDecoder
import cn.howardliu.monitor.cynomys.net.struct.Header;
import cn.howardliu.monitor.cynomys.net.struct.Message;
import io.netty.buffer.ByteBuf;
import io.netty.channel.ChannelHandlerContext;
import io.netty.handler.codec.LengthFieldBasedFrameDecoder;
import io.netty.util.CharsetUtil;
public class MessageDecoder extends LengthFieldBasedFrameDecoder {
public MessageDecoder(int maxFrameLength, int lengthFieldOffset, int lengthFieldLength) {
super(maxFrameLength, lengthFieldOffset, lengthFieldLength);
}
@Override
protected Object decode(ChannelHandlerContext ctx, ByteBuf in) throws Exception {
ByteBuf frame = (ByteBuf) super.decode(ctx, in);
if (frame == null) {
return null;
}
Message message = new Message()
.setHeader(
new Header()
.setCrcCode(frame.readInt())
.setLength(frame.readInt())
.setOpaque(frame.readInt())
.setTag(frame.readCharSequence(frame.readInt(), CharsetUtil.UTF_8).toString())
.setSysName(frame.readCharSequence(frame.readInt(), CharsetUtil.UTF_8).toString())
.setSysCode(frame.readCharSequence(frame.readInt(), CharsetUtil.UTF_8).toString())
.setType(frame.readByte())
.setCode(frame.readByte())
.setFlagPath(frame.readByte())
);
if (frame.readableBytes() > 4) {
message.setBody(frame.readCharSequence(frame.readInt(), CharsetUtil.UTF_8).toString());
}
return message;
}
}
解碼器使用方式是new MessageDecoder(1024 * 1024 * 100, 4, 4)。
厚顔的貼上這個項目位址,歡迎star、fork和吐槽:
項目名稱:cynomys
項目位址:
https://github.com/howardliu-cn/cynomys