使用 Netty 時會遇到需要解碼以分隔符和長度為基礎的協議,本節講解Netty 如何解碼這些協議。
### [](https://github.com/waylau/essential-netty-in-action/blob/master/CORE%20FUNCTIONS/Decoding%20delimited%20and%20length-based%20protocols.md#分隔符協議)分隔符協議
經常需要處理分隔符協議或創建基于它們的協議,例如[SMTP](http://www.ietf.org/rfc/rfc2821.txt)、[POP3](http://www.ietf.org/rfc/rfc1939.txt)、[IMAP](http://tools.ietf.org/html/rfc3501)、[Telnet](http://tools.ietf.org/search/rfc854)等等。Netty 附帶的解碼器可以很容易的提取一些序列分隔:
Table 8.5 Decoders for handling delimited and length-based protocols
| 名稱 | 描述 |
| --- | --- |
| DelimiterBasedFrameDecoder | 接收ByteBuf由一個或多個分隔符拆分,如NUL或換行符 |
| LineBasedFrameDecoder | 接收ByteBuf以分割線結束,如"\n"和"\r\n" |
下圖顯示了使用"\r\n"分隔符的處理:
[](https://github.com/waylau/essential-netty-in-action/blob/master/images/Figure%208.5%20Handling%20delimited%20frames.jpg)
1. 字節流
2. 第一幀
3. 第二幀
Figure 8.5 Handling delimited frames
下面展示了如何用 LineBasedFrameDecoder 處理
Listing 8.8 Handling line-delimited frames
~~~
public class LineBasedHandlerInitializer extends ChannelInitializer<Channel> {
@Override
protected void initChannel(Channel ch) throws Exception {
ChannelPipeline pipeline = ch.pipeline();
pipeline.addLast(new LineBasedFrameDecoder(65 * 1024)); //1
pipeline.addLast(new FrameHandler()); //2
}
public static final class FrameHandler extends SimpleChannelInboundHandler<ByteBuf> {
@Override
public void channelRead0(ChannelHandlerContext ctx, ByteBuf msg) throws Exception { //3
// Do something with the frame
}
}
}
~~~
1. 添加一個 LineBasedFrameDecoder 用于提取幀并把數據包轉發到下一個管道中的處理程序,在這種情況下就是 FrameHandler
2. 添加 FrameHandler 用于接收幀
3. 每次調用都需要傳遞一個單幀的內容
使用 DelimiterBasedFrameDecoder 可以方便處理特定分隔符作為數據結構體的這類情況。如下:
* 傳入的數據流是一系列的幀,每個由換行(“\n”)分隔
* 每幀包括一系列項目,每個由單個空格字符分隔
* 一幀的內容代表一個“命令”:一個名字后跟一些變量參數
清單8.9中顯示了的實現的方式。定義以下類:
* 類 Cmd - 存儲幀的內容,其中一個 ByteBuf 用于存名字,另外一個存參數
* 類 CmdDecoder - 從重寫方法 decode() 中檢索一行,并從其內容中構建一個 Cmd 的實例
* 類 CmdHandler - 從 CmdDecoder 接收解碼 Cmd 對象和對它的一些處理。
所以關鍵的解碼器是擴展了 LineBasedFrameDecoder
Listing 8.9 Decoder for the command and the handler
~~~
public class CmdHandlerInitializer extends ChannelInitializer<Channel> {
@Override
protected void initChannel(Channel ch) throws Exception {
ChannelPipeline pipeline = ch.pipeline();
pipeline.addLast(new CmdDecoder(65 * 1024));//1
pipeline.addLast(new CmdHandler()); //2
}
public static final class Cmd { //3
private final ByteBuf name;
private final ByteBuf args;
public Cmd(ByteBuf name, ByteBuf args) {
this.name = name;
this.args = args;
}
public ByteBuf name() {
return name;
}
public ByteBuf args() {
return args;
}
}
public static final class CmdDecoder extends LineBasedFrameDecoder {
public CmdDecoder(int maxLength) {
super(maxLength);
}
@Override
protected Object decode(ChannelHandlerContext ctx, ByteBuf buffer) throws Exception {
ByteBuf frame = (ByteBuf) super.decode(ctx, buffer); //4
if (frame == null) {
return null; //5
}
int index = frame.indexOf(frame.readerIndex(), frame.writerIndex(), (byte) ' '); //6
return new Cmd(frame.slice(frame.readerIndex(), index), frame.slice(index +1, frame.writerIndex())); //7
}
}
public static final class CmdHandler extends SimpleChannelInboundHandler<Cmd> {
@Override
public void channelRead0(ChannelHandlerContext ctx, Cmd msg) throws Exception {
// Do something with the command //8
}
}
}
~~~
1. 添加一個 CmdDecoder 到管道;將提取 Cmd 對象和轉發到在管道中的下一個處理器
2. 添加 CmdHandler 將接收和處理 Cmd 對象
3. 命令也是 POJO
4. super.decode() 通過結束分隔從 ByteBuf 提取幀
5. frame 是空時,則返回 null
6. 找到第一個空字符的索引。首先是它的命令名;接下來是參數的順序
7. 從幀先于索引以及它之后的片段中實例化一個新的 Cmd 對象
8. 處理通過管道的 Cmd 對象
### [](https://github.com/waylau/essential-netty-in-action/blob/master/CORE%20FUNCTIONS/Decoding%20delimited%20and%20length-based%20protocols.md#基于長度的協議)基于長度的協議
基于長度的協議協議在幀頭文件里定義了一個幀編碼的長度,而不是結束位置用一個特殊的分隔符來標記。表8.6列出了 Netty 提供的兩個解碼器,用于處理這種類型的協議。
Table 8.6 Decoders for length-based protocols
| 名稱 | 描述 |
| --- | --- |
| FixedLengthFrameDecoder | 提取固定長度 |
| LengthFieldBasedFrameDecoder | 讀取頭部長度并提取幀的長度 |
如下圖所示,FixedLengthFrameDecoder 的操作是提取固定長度每幀8字節
[](https://github.com/waylau/essential-netty-in-action/blob/master/images/Figure%208.6%20Decoding%20a%20frame%20length%20of%208%20bytes.jpg)
1. 字節流 stream
2. 4個幀,每個幀8個字節
大部分時候幀的大小被編碼在頭部,這種情況可以使用LengthFieldBasedFrameDecoder,它會讀取頭部長度并提取幀的長度。下圖顯示了它是如何工作的:
[](https://github.com/waylau/essential-netty-in-action/blob/master/images/Figure%208.7%20Message%20that%20has%20frame%20size%20encoded%20in%20the%20header.jpg)
1. 長度 "0x000C" (12) 被編碼在幀的前兩個字節
2. 后面的12個字節就是內容
3. 提取沒有頭文件的幀內容
Figure 8.7 Message that has frame size encoded in the header
LengthFieldBasedFrameDecoder 提供了幾個構造函數覆蓋各種各樣的頭長字段配置情況。清單8.10顯示了使用三個參數的構造函數是maxFrameLength,lengthFieldOffset lengthFieldLength。在這 情況下,幀的長度被編碼在幀的前8個字節。
Listing 8.10 Decoder for the command and the handler
~~~
public class LineBasedHandlerInitializer extends ChannelInitializer<Channel> {
@Override
protected void initChannel(Channel ch) throws Exception {
ChannelPipeline pipeline = ch.pipeline();
pipeline.addLast(new LineBasedFrameDecoder(65 * 1024)); //1
pipeline.addLast(new FrameHandler()); //2
}
public static final class FrameHandler extends SimpleChannelInboundHandler<ByteBuf> {
@Override
public void channelRead0(ChannelHandlerContext ctx, ByteBuf msg) throws Exception {
// Do something with the frame //3
}
}
}
~~~
1. 添加一個 LengthFieldBasedFrameDecoder ,用于提取基于幀編碼長度8個字節的幀。
2. 添加一個 FrameHandler 用來處理每幀
3. 處理幀數據
總而言之,本部分探討了 Netty 提供的編解碼器支持協議,包括定義特定的分隔符的字節流的結構或協議幀的長度。這些編解碼器非常有用。
- Introduction
- 開始
- Netty-異步和數據驅動
- Netty 介紹
- 構成部分
- 關于本書
- 第一個 Netty 應用
- 設置開發環境
- Netty 客戶端/服務端 總覽
- 寫一個 echo 服務器
- 寫一個 echo 客戶端
- 編譯和運行 Echo 服務器和客戶端
- 總結
- Netty 總覽
- Netty 快速入門
- Channel, Event 和 I/O
- 什么是 Bootstrapping 為什么要用
- ChannelHandler 和 ChannelPipeline
- 近距離觀察 ChannelHandler
- 總結
- 核心功能
- Transport(傳輸)
- 案例研究:Transport 的遷移
- Transport API
- 包含的 Transport
- Transport 使用情況
- 總結
- Buffer(緩沖)
- Buffer API
- ByteBuf - 字節數據的容器
- 字節級別的操作
- ByteBufHolder
- ByteBuf 分配
- 總結
- ChannelHandler 和 ChannelPipeline
- ChannelHandler 家族
- ChannelPipeline
- ChannelHandlerContext
- 總結
- Codec 框架
- 什么是 Codec
- Decoder(解碼器)
- Encoder(編碼器)
- 抽象 Codec(編解碼器)類
- 總結
- 提供了的 ChannelHandler 和 Codec
- 使用 SSL/TLS 加密 Netty 程序
- 構建 Netty HTTP/HTTPS 應用
- 空閑連接以及超時
- 解碼分隔符和基于長度的協議
- 編寫大型數據
- 序列化數據
- 總結
- Bootstrap 類型
- 引導客戶端和無連接協議
- 引導服務器
- 從 Channel 引導客戶端
- 在一個引導中添加多個 ChannelHandler
- 使用Netty 的 ChannelOption 和屬性
- 關閉之前已經引導的客戶端或服務器
- 總結
- 引導
- Bootstrap 類型
- 引導客戶端和無連接協議
- 引導服務器
- 從 Channel 引導客戶端
- 在一個引導中添加多個 ChannelHandler
- 使用Netty 的 ChannelOption 和屬性
- 關閉之前已經引導的客戶端或服務器
- 總結
- NETTY BY EXAMPLE
- 單元測試
- 總覽
- 測試 ChannelHandler
- 測試異常處理
- 總結
- WebSocket
- WebSocket 程序示例
- 添加 WebSocket 支持
- 測試程序
- 總結
- SPDY
- SPDY 背景
- 示例程序
- 實現
- 啟動 SpdyServer 并測試
- 總結
- 通過 UDP 廣播事件
- UDP 基礎
- UDP 廣播
- UDP 示例
- EventLog 的 POJO
- 寫廣播器
- 寫監視器
- 運行 LogEventBroadcaster 和 LogEventMonitor
- 總結
- 高級主題
- 實現自定義的編解碼器
- 編解碼器的范圍
- 實現 Memcached 編解碼器
- 了解 Memcached 二進制協議
- Netty 編碼器和解碼器
- 測試編解碼器
- EventLoop 和線程模型
- 線程模型的總覽
- EventLoop
- EventLoop
- I/O EventLoop/Thread 分配細節
- 總結
- 用例1:Droplr Firebase 和 Urban Airship
- 用例2:Facebook 和 Twitter