Netty入门(十)解码分隔符和基于长度的协议
我们需要区分不同帧的首尾,通常需要在结尾设定特定分隔符或者在首部添加长度字段,分别称为分隔符协议和基于长度的协议,本节讲解 Netty 如何解码这些协议。
一、分隔符协议
Netty 附带的解码器可以很容易的提取一些序列分隔:
下面显示了使用 “\r\n”分隔符的处理:
下面为 LineBaseFrameDecoder 的简单实现:
1 public class CmdHandlerInitializer extends ChannelInitializer<Channel> { 2 3 @Override 4 protected void initChannel(Channel ch) throws Exception { 5 ChannelPipeline pipeline = ch.pipeline(); 6 // 添加解码器, 7 pipeline.addLast(new CmdDecoder(65 * 1024)); 8 pipeline.addLast(new CmdHandler()); 9 } 10 11 public static final class Cmd { 12 private final ByteBuf name; // 名字 13 private final ByteBuf args; // 参数 14 15 public Cmd(ByteBuf name, ByteBuf args) { 16 this.name = name; 17 this.args = args; 18 } 19 20 public ByteBuf name() { 21 return name; 22 } 23 24 public ByteBuf args() { 25 return args; 26 } 27 } 28 29 /** 30 * 根据分隔符将消息解码成Cmd对象传给下一个处理器 31 */ 32 public static final class CmdDecoder extends LineBasedFrameDecoder { 33 34 public CmdDecoder(int maxLength) { 35 super(maxLength); 36 } 37 38 @Override 39 protected Object decode(ChannelHandlerContext ctx, ByteBuf buffer) throws Exception { 40 // 通过结束分隔符从 ByteBuf 提取帧 41 ByteBuf frame = (ByteBuf)super.decode(ctx, buffer); 42 if(frame == null) 43 return null; 44 int index = frame.indexOf(frame.readerIndex(), frame.writerIndex(), (byte)' '); 45 // 提取 Cmd 对象 46 return new Cmd(frame.slice(frame.readerIndex(), index), 47 frame.slice(index+1, frame.writerIndex())); 48 } 49 } 50 51 public static final class CmdHandler extends SimpleChannelInboundHandler<Cmd> { 52 53 @Override 54 protected void channelRead0(ChannelHandlerContext ctx, Cmd msg) throws Exception { 55 // 处理 Cmd 信息 56 } 57 58 } 59 }
上面的例子主要实现了利用换行符‘\n’分隔帧,然后将每行数据解码成一个 Cmd 实例。
二、基于长度的协议
基于长度的协议在帧头定义了一个帧编码的长度,而不是在结束位置用一个特殊的分隔符来标记。Netty 提供了两种编码器,用于处理这种类型的协议,如下:
FixedLengthFrameDecoder 的操作是提取固定长度每帧 8 字节,如下图所示:
但大部分时候,我们会把帧的大小编码在头部,这种情况可以使用 LengthFieldBaseFrameDecoder,它会提取帧的长度并根据长度读取帧的数据部分,如下:
下面是 LengthFieldBaseFrameDecoder 的一个简单应用:
1 /** 2 * 基于长度的协议 3 * LengthFieldBasedFrameDecoder 4 */ 5 public class LineBasedHandlerInitializer extends ChannelInitializer<Channel> { 6 7 @Override 8 protected void initChannel(Channel ch) throws Exception { 9 ChannelPipeline pipeline = ch.pipeline(); 10 // 用于提取基于帧编码长度8个字节的帧 11 pipeline.addLast(new LengthFieldBasedFrameDecoder(65*1024, 0, 8)); 12 pipeline.addLast(new FrameHandler()); 13 } 14 15 public static final class FrameHandler extends SimpleChannelInboundHandler<ByteBuf> { 16 17 @Override 18 protected void channelRead0(ChannelHandlerContext ctx, ByteBuf msg) throws Exception { 19 // TODO 数据处理 20 } 21 22 } 23 24 }
上面的例子主要实现了提取帧首部 8 字节的长度,然后提取数据部分进行处理。