1.Channel、 EventLoop 和 ChannelFuture的關係
- EventLoop 定義了 Netty 的核心抽象, 用於處理連接的生命週期中所發生的事件
- 一個 EventLoopGroup 包含一個或者多個 EventLoop
- 一個 EventLoop 在它的生命週期內只和一個 Thread 綁定
- 所有由 EventLoop 處理的 I/O 事件都將在它專有的 Thread 上被處理
- 一個 Channel 在它的生命週期內只註冊於一個 EventLoop
- 一個 EventLoop 可能會被分配給一個或多個 Channel
- 一個給定 Channel 的 I/O 操作都是由相同的 Thread 執行的, 實際
上消除了對於同步的需要 - Netty 中所有的 I/O 操作都是異步的。因爲一個操作可能不會
立即返回,所以我們需要一種用於在之後的某個時間點確定其結果的方法。爲此, Netty 提供了
ChannelFuture 接口,其 addListener()方法註冊了一個 ChannelFutureListener,以
便在某個操作完成時(無論是否成功)得到通知
2. ChannelHandler的執行順序
- 由ChannelPipeline控的添加順序控制
bootstrap.handler(new ChannelInitializer<SocketChannel>() {//ChannelInitializer是用於配置通道
@Override
public void initChannel(SocketChannel ch) {
ch.pipeline().addLast(new SDataEncoder());//出站
ch.pipeline().addLast(new SDataDecoder());//入站
ch.pipeline().addLast(new SIdleStateHandler(0, 0, SIdleTime));//in
ch.pipeline().addLast(clientDataHandler);//in
}
});
如這裏代碼,入站的順序是SDataDecoder(解密)–》clientDataHandler(客戶端數據處理)
public class SDataDecoder extends ByteToMessageDecoder {
@Override
protected void decode(ChannelHandlerContext ctx, ByteBuf in, List<Object> out) throws Exception {
//在入棧解碼器中,需要將入的in數據,轉換成我們想要的數據對象outObject
//然後把這個數據添加到out數組裏面
out.add(outObject)
//完成後,調用,讓流程走向下個channelhandler
ctx.fireChannelReadComplete();
}
}
//SDataDecoder 的下個channelhandler,如下面這個,就需要指定爲解碼後的數據類型,
public class ClientDataHandlerextends SimpleChannelInboundHandler<outObject> {
//在這個方法中,就能處理我們想要服務器返回的入站的數據了
@Override
protected void channelRead0(ChannelHandlerContext ctx, SMessage s) {
}
3. 粘包、拼包原因
由服務器發送的消息可能會被分塊接收,也可能分塊發送。 也就是說,如果服務器發送了 5 字節, 那麼不
能保證這 5 字節會被一次性接收。 即使是對於這麼少量的數據, channelRead0()方法也可能
會被調用兩次,第一次使用一個持有 3 字節的 ByteBuf(Netty 的字節容器),第二次使用一個
持有 2 字節的 ByteBuf。作爲一個面向流的協議, TCP 保證了字節數組將會按照服務器發送它們的順序被接收。所以只要按照特定的長度或者分隔符處理,就能拼接到完整的數據包
設計時,不同的消息除了劃分不同的消息類型,還需要針對每個消息一個唯一的消息id,不然同類型消息,只按照消息類型接收,可能會出現發送一次請求,同時受到多條回覆(其他線程也發送了類似的消息)
4. ChannelHandler 的典型用途包括
- 將數據從一種格式轉換爲另一種格式;
- 提供異常的通知;
- 提供 Channel 變爲活動的或者非活動的通知;
- 提供當 Channel 註冊到 EventLoop 或者從 EventLoop 註銷時的通知;
- 提供有關用戶自定義事件的通知。
攔截過濾器 ChannelPipeline 實現了一種常見的設計模式—攔截過濾器(Intercepting Filter)。 UNIX 管道是另外一個熟悉的例子: 多個命令被鏈接在一起,其中一個命令的輸出端將連
接到命令行中下一個命令的輸入端。
5. chanel 的是線程安全的
Netty 的 Channel 實現是線程安全的,因此你可以存儲一個到 Channel 的引用,並且每當
你需要向遠程節點寫數據時,多個線程都使用這個 Channel寫數據,都是沒問題的
6. EventLoop的是線程管理
Netty線程模型的卓越性能取決於對於當前執行的Thread的身份的確定 ,確定它是否是分配給當前Channel以及它的EventLoop的那一個線程(因爲當前不同的線程會搶用cpu)。如果(當前)調用線程正是支撐 EventLoop 的線程, 那麼所提交的代碼塊將會被(直接)執行。否則,EventLoop 將調度該任務以便稍後執行,並將它放入到內部隊列中。當 EventLoop下次處理它的事件時, 它會執行隊列中的那些任務/事件。這也就解釋了任何的 Thread 是如何與 Channel 直接交互而無需在 ChannelHandler 中進行額外同步的
7. EventLoop/線程的分配
1. 異步傳輸
一旦一個 Channel 被分配給一個 EventLoop, 它將在它的整個生命週期中都使用這個EventLoop(以及相關聯的 Thread)。請牢記這一點,因爲它可以使你從擔憂你的 ChannelHandler 實現中的線程安全和同步問題中解脫出來。
2. 阻塞傳輸
但是, 正如同之前一樣, 得到的保證是每個 Channel 的 I/O 事件都將只會被一個 Thread(用於支撐該 Channel 的 EventLoop 的那個 Thread) 處理。
8. 引導
引導客戶端:
代碼示例:
引導服務器:
代碼示例:
9. 使用 Netty 的 ChannelOption 和屬性
10. 關閉
需要關閉 EventLoopGroup, 它將處理任何掛起的事件和任務,並且隨後
釋放所有活動的線程。這就是調用 EventLoopGroup.shutdownGracefully()方法的作用。這個方法調用將會返回一個 Future,這個 Future 將在關閉完成時接收到通知。需要注意的是,shutdownGracefully()方法也是一個異步的操作,所以你需要阻塞等待直到它完成,或者向所返回的 Future 註冊一個監聽器以在關閉完成時獲得通知
11. 解碼器
- 抽象類 ByteToMessageDecoder
雖然 ByteToMessageDecoder 使得可以很簡單地實現這種模式,但是你可能會發現,在調用 readInt()方法前不得不驗證所輸入的 ByteBuf 是否具有足夠的數據有點繁瑣。ReplayingDecoder,它是一個特殊的解碼器,以少量的開銷消除了這個步驟。
- 抽象類 ReplayingDecoder
和之前一樣,從ByteBuf中提取的int將會被添加到List中。如果沒有足夠的字節可用,這個readInt()方法的實現將會拋出一個Error,其將在基類中被捕獲並處理。當有更多的數據可供讀取時,該decode()方法將會被再次調用 - 抽象類 MessageToMessageDecoder
12. 編碼器
抽象類 MessageToByteEncoder
抽象類 MessageToMessageEncoder
13.空閒的連接和超時
14.解碼基於分隔符的協議和基於長度的協議
基於分隔符的(delimited) 消息協議使用定義的字符來標記的消息或者消息段(通常被稱爲幀)的開頭或者結尾。
如果你正在使用除了行尾符之外的分隔符分隔的幀,那麼你可以以類似的方式使用 DelimiterBasedFrameDecoder,只需要將特定的分隔符序列指定到其構造函數即可
基於長度的協議通過將它的長度編碼到幀的頭部來定義幀,而不是使用特殊的分隔符來標記
它的結束。
你將經常會遇到被編碼到消息頭部的幀大小不是固定值的協議。爲了處理這種變長幀,你可以使用 LengthFieldBasedFrameDecoder, 它將從頭部字段確定幀長,然後從數據流中提取指定的字節數