本節,將使用 EmbeddedChannel 來測試 ChannelHandler
### [](https://github.com/waylau/essential-netty-in-action/blob/master/NETTY%20BY%20EXAMPLE/Testing%20ChannelHandler.md#測試入站消息)測試入站消息
我們來編寫一個簡單的 ByteToMessageDecoder 實現,有足夠的數據可以讀取時將產生固定大小的包,如果沒有足夠的數據可以讀取,則會等待下一個數據塊并再次檢查是否可以產生一個完整包。
如圖所示,它可能會占用一個以上的“event”以獲取足夠的字節產生一個數據包,并將它傳遞到 ChannelPipeline 中的下一個 ChannelHandler,
[](https://github.com/waylau/essential-netty-in-action/blob/master/images/Figure%2010.2%20Decoding%20via%20FixedLengthFrameDecoder.jpg)
Figure 10.2 Decoding via FixedLengthFrameDecoder
實現如下:
Listing 10.1 FixedLengthFrameDecoder implementation
~~~
public class FixedLengthFrameDecoder extends ByteToMessageDecoder { //1
private final int frameLength;
public FixedLengthFrameDecoder(int frameLength) { //2
if (frameLength <= 0) {
throw new IllegalArgumentException(
"frameLength must be a positive integer: " + frameLength);
}
this.frameLength = frameLength;
}
@Override
protected void decode(ChannelHandlerContext ctx, ByteBuf in, List<Object> out) throws Exception {
if (in.readableBytes() >= frameLength) { //3
ByteBuf buf = in.readBytes(frameLength);//4
out.add(buf); //5
}
}
}
~~~
1. 繼承 ByteToMessageDecoder 用來處理入站的字節并將他們解碼為消息
2. 指定產出的幀的長度
3. 檢查是否有足夠的字節用于讀到下個幀
4. 從 ByteBuf 讀取新幀
5. 添加幀到解碼好的消息 List
下面是單元測試的例子,使用 EmbeddedChannel
Listing 10.2 Test the FixedLengthFrameDecoder
~~~
public class FixedLengthFrameDecoderTest {
@Test //1
public void testFramesDecoded() {
ByteBuf buf = Unpooled.buffer(); //2
for (int i = 0; i < 9; i++) {
buf.writeByte(i);
}
ByteBuf input = buf.duplicate();
EmbeddedChannel channel = new EmbeddedChannel(new FixedLengthFrameDecoder(3)); //3
Assert.assertFalse(channel.writeInbound(input.readBytes(2))); //4
Assert.assertTrue(channel.writeInbound(input.readBytes(7)));
Assert.assertTrue(channel.finish()); //5
ByteBuf read = (ByteBuf) channel.readInbound();
Assert.assertEquals(buf.readSlice(3), read);
read.release();
read = (ByteBuf) channel.readInbound();
Assert.assertEquals(buf.readSlice(3), read);
read.release();
read = (ByteBuf) channel.readInbound();
Assert.assertEquals(buf.readSlice(3), read);
read.release();
Assert.assertNull(channel.readInbound());
buf.release();
}
@Test
public void testFramesDecoded2() {
ByteBuf buf = Unpooled.buffer();
for (int i = 0; i < 9; i++) {
buf.writeByte(i);
}
ByteBuf input = buf.duplicate();
EmbeddedChannel channel = new EmbeddedChannel(new FixedLengthFrameDecoder(3));
Assert.assertFalse(channel.writeInbound(input.readBytes(2)));
Assert.assertTrue(channel.writeInbound(input.readBytes(7)));
Assert.assertTrue(channel.finish());
ByteBuf read = (ByteBuf) channel.readInbound();
Assert.assertEquals(buf.readSlice(3), read);
read.release();
read = (ByteBuf) channel.readInbound();
Assert.assertEquals(buf.readSlice(3), read);
read.release();
read = (ByteBuf) channel.readInbound();
Assert.assertEquals(buf.readSlice(3), read);
read.release();
Assert.assertNull(channel.readInbound());
buf.release();
}
}
~~~
1. 測試增加 @Test 注解
2. 新建 ByteBuf 并用字節填充它
3. 新增 EmbeddedChannel 并添加 FixedLengthFrameDecoder 用于測試
4. 寫數據到 EmbeddedChannel
5. 標記 channel 已經完成
6. 讀產生的消息并且校驗
如上面代碼,testFramesDecoded() 方法想測試一個 ByteBuf,這個ByteBuf 包含9個可讀字節,被解碼成包含了3個可讀字節的 ByteBuf。你可能注意到,它寫入9字節到通道是通過調用 writeInbound() 方法,之后再執行 finish() 來將 EmbeddedChannel 標記為已完成,最后調用readInbound() 方法來獲取 EmbeddedChannel 中的數據,直到沒有可讀字節。testFramesDecoded2() 方法采取同樣的方式,但有一個區別就是入站ByteBuf分兩步寫的,當調用 writeInbound(input.readBytes(2)) 后返回 false 時,FixedLengthFrameDecoder 值會產生輸出,至少有3個字節是可讀,testFramesDecoded2() 測試的工作相當于testFramesDecoded()。
### [](https://github.com/waylau/essential-netty-in-action/blob/master/NETTY%20BY%20EXAMPLE/Testing%20ChannelHandler.md#testing-outbound-messages)Testing outbound messages
測試的處理出站消息類似于我們剛才看到的一切。這個例子將使用的實現MessageToMessageEncoder:AbsIntegerEncoder。
* 當收到 flush() 它將從 ByteBuf 讀取4字節整數并給每個執行Math.abs()。
* 每個整數接著寫入 ChannelHandlerPipeline
圖10.3顯示了邏輯。
[](https://github.com/waylau/essential-netty-in-action/blob/master/images/10.3%20Encoding%20via%20AbsIntegerEncoder.jpg)
Figure 10.3 Encoding via AbsIntegerEncoder
示例如下:
Listing 10.3 AbsIntegerEncoder
~~~
public class AbsIntegerEncoder extends MessageToMessageEncoder<ByteBuf> { //1
@Override
protected void encode(ChannelHandlerContext channelHandlerContext, ByteBuf in, List<Object> out) throws Exception {
while (in.readableBytes() >= 4) { //2
int value = Math.abs(in.readInt());//3
out.add(value); //4
}
}
}
~~~
1. 繼承 MessageToMessageEncoder 用于編碼消息到另外一種格式
2. 檢查是否有足夠的字節用于編碼
3. 讀取下一個輸入 ByteBuf 產出的 int 值,并計算絕對值
4. 寫 int 到編碼的消息 List
在前面的示例中,我們將使用 EmbeddedChannel 測試代碼。清單10.4
Listing 10.4 Test the AbsIntegerEncoder
~~~
public class AbsIntegerEncoderTest {
@Test //1
public void testEncoded() {
ByteBuf buf = Unpooled.buffer(); //2
for (int i = 1; i < 10; i++) {
buf.writeInt(i * -1);
}
EmbeddedChannel channel = new EmbeddedChannel(new AbsIntegerEncoder()); //3
Assert.assertTrue(channel.writeOutbound(buf)); //4
Assert.assertTrue(channel.finish()); //5
for (int i = 1; i < 10; i++) {
Assert.assertEquals(i, channel.readOutbound()); //6
}
Assert.assertNull(channel.readOutbound());
}
}
~~~
1. 用 @Test 標記
2. 新建 ByteBuf 并寫入負整數
3. 新建 EmbeddedChannel 并安裝 AbsIntegerEncoder 來測試
4. 寫 ByteBuf 并預測 readOutbound() 產生的數據
5. 標記 channel 已經完成
6. 讀取產生到的消息,檢查負值已經編碼為絕對值
- 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