我們需要區分不同幀的首尾,通常需要在結尾設定特定分隔符或者在首部添加長度字段,分別稱為分隔符協議和基於長度的協議,本節講解 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 字節的長度,然后提取數據部分進行處理。