最近在写一个应用监控的项目,使用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
个人主页: http://www.howardliu.cn
个人博文: 中文字节长度引起的数据丢失
CSDN主页: http://blog.csdn.net/liuxinghao
CSDN博文: 中文字节长度引起的数据丢失