Ps:此係列文章來源於Netty 入門與實戰:仿寫微信 IM 即時通訊系統,有能力的還請支持正版,一頓飯錢,絕對物有所值
*如何避免 else
氾濫
不管是服務端還是客戶端,處理流程大致分爲以下幾個步驟
把這三類邏輯都寫在一個類裏面,客戶端寫在 ClientHandler
,服務端寫在 ServerHandler
,如果要做功能的擴展(比如,我們要校驗 magic number,或者其他特殊邏輯),只能在一個類裏面去修改, 這個類就會變得越來越臃腫。
另外,我們注意到,每次發指令數據包都要手動調用編碼器編碼成 ByteBuf
,對於這類場景的編碼優化,我們能想到的辦法自然是模塊化處理,不同的邏輯放置到單獨的類來處理,最後將這些邏輯串聯起來,形成一個完整的邏輯處理鏈。
Netty 中的 pipeline 和 channelHandler 正是用來解決這個問題的:它通過責任鏈設計模式來組織代碼邏輯,並且能夠支持邏輯的動態添加和刪除 ,Netty 能夠支持各類協議的擴展,比如 HTTP,Websocket,Redis,靠的就是 pipeline 和 channelHandler。
*pipeline 與 channelHandler 的構成
無論是從服務端來看,還是客戶端來看,在 Netty 整個框架裏面,一條連接對應着一個 Channel,這條 Channel 所有的處理邏輯都在一個叫做 ChannelPipeline
的對象裏面,ChannelPipeline
是一個雙向鏈表結構,他和 Channel 之間是一對一的關係。
ChannelPipeline
裏面每個節點都是一個 ChannelHandlerContext
對象,這個對象能夠拿到和 Channel 相關的所有的上下文信息,然後這個對象包着一個重要的對象,那就是邏輯處理器 ChannelHandler
。
*channelHandler 的分類
可以看到 ChannelHandler
有兩大子接口:
第一個子接口是 ChannelInboundHandler
,從字面意思也可以猜到,他是處理讀數據的邏輯,比如,我們在一端讀到一段數據,首先要解析這段數據,然後對這些數據做一系列邏輯處理,最終把響應寫到對端, 在開始組裝響應之前的所有的邏輯,都可以放置在 ChannelInboundHandler
裏處理,它的一個最重要的方法就是 channelRead()
。讀者可以將 ChannelInboundHandler
的邏輯處理過程與 TCP 的七層協議的解析聯繫起來,收到的數據一層層從物理層上升到我們的應用層。
第二個子接口 ChannelOutBoundHandler
是處理寫數據的邏輯,它是定義我們一端在組裝完響應之後,把數據寫到對端的邏輯,比如,我們封裝好一個 response 對象,接下來我們有可能對這個 response 做一些其他的特殊邏輯,然後,再編碼成 ByteBuf,最終寫到對端,它裏面最核心的一個方法就是 write()
,讀者可以將 ChannelOutBoundHandler
的邏輯處理過程與 TCP 的七層協議的封裝過程聯繫起來,我們在應用層組裝響應之後,通過層層協議的封裝,直到最底層的物理層。
這兩個子接口分別有對應的默認實現,ChannelInboundHandlerAdapter
,和 ChanneloutBoundHandlerAdapter
,它們分別實現了兩大接口的所有功能,默認情況下會把讀寫事件傳播到下一個 handler。
*ChannelInboundHandler 的事件傳播
在服務端的 pipeline 添加三個 ChannelInboundHandler
NettyServer.java
serverBootstrap
.childHandler(new ChannelInitializer<NioSocketChannel>() {
protected void initChannel(NioSocketChannel ch) {
ch.pipeline().addLast(new InBoundHandlerA());
ch.pipeline().addLast(new InBoundHandlerB());
ch.pipeline().addLast(new InBoundHandlerC());
}
});
每個 inBoundHandler 都繼承自 ChannelInboundHandlerAdapter
,然後實現了 channelRead()
方法
public class InBoundHandlerA extends ChannelInboundHandlerAdapter {
@Override
public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
System.out.println("InBoundHandlerA: " + msg);
super.channelRead(ctx, msg);
}
}
public class InBoundHandlerB extends ChannelInboundHandlerAdapter {
@Override
public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
System.out.println("InBoundHandlerB: " + msg);
super.channelRead(ctx, msg);
}
}
public class InBoundHandlerC extends ChannelInboundHandlerAdapter {
@Override
public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
System.out.println("InBoundHandlerC: " + msg);
super.channelRead(ctx, msg);
}
}
在 channelRead()
方法裏面,我們打印當前 handler 的信息,然後調用父類的 channelRead()
方法,而這裏父類的 channelRead()
方法會自動調用到下一個 inBoundHandler 的 channelRead()
方法,並且會把當前 inBoundHandler 裏處理完畢的對象傳遞到下一個 inBoundHandler,我們例子中傳遞的對象都是同一個 msg。
我們通過 addLast()
方法來爲 pipeline 添加 inBoundHandler,inBoundHandler 的執行順序與我們通過 addLast()
方法 添加的順序保持一致。
*ChannelOutboundHandler 的事件傳播
在服務端的 pipeline 添加三個 ChanneloutBoundHandler
serverBootstrap
.childHandler(new ChannelInitializer<NioSocketChannel>() {
protected void initChannel(NioSocketChannel ch) {
// inBound,處理讀數據的邏輯鏈
ch.pipeline().addLast(new InBoundHandlerA());
ch.pipeline().addLast(new InBoundHandlerB());
ch.pipeline().addLast(new InBoundHandlerC());
// outBound,處理寫數據的邏輯鏈
ch.pipeline().addLast(new OutBoundHandlerA());
ch.pipeline().addLast(new OutBoundHandlerB());
ch.pipeline().addLast(new OutBoundHandlerC());
}
});
每個 outBoundHandler 都繼承自 ChanneloutBoundHandlerAdapter
,然後實現了 write()
方法
public class OutBoundHandlerA extends ChannelOutboundHandlerAdapter {
@Override
public void write(ChannelHandlerContext ctx, Object msg, ChannelPromise promise) throws Exception {
System.out.println("OutBoundHandlerA: " + msg);
super.write(ctx, msg, promise);
}
}
public class OutBoundHandlerB extends ChannelOutboundHandlerAdapter {
@Override
public void write(ChannelHandlerContext ctx, Object msg, ChannelPromise promise) throws Exception {
System.out.println("OutBoundHandlerB: " + msg);
super.write(ctx, msg, promise);
}
}
public class OutBoundHandlerC extends ChannelOutboundHandlerAdapter {
public void write(ChannelHandlerContext ctx, Object msg, ChannelPromise promise) throws Exception {
System.out.println("OutBoundHandlerC: " + msg);
super.write(ctx, msg, promise);
}
}
在 write()
方法裏面,我們打印當前 handler 的信息,然後調用父類的 write()
方法,而這裏父類的 write()
方法會自動調用到下一個 outBoundHandler 的 write()
方法,並且會把當前 outBoundHandler 裏處理完畢的對象傳遞到下一個 outBoundHandler。outBoundHandler 的執行順序與我們添加的順序相反
*pipeline 的結構
不管我們定義的是哪種類型的 handler, 最終它們都是以雙向鏈表的方式連接,這裏實際鏈表的節點是 ChannelHandlerContext
,這裏爲了讓結構清晰突出,可以直接把節點看作 ChannelHandlerContext
。
*pipeline 的執行順序
雖然兩種類型的 handler 在一個雙向鏈表裏,但是這兩類 handler 的分工是不一樣的,inBoundHandler 的事件通常只會傳播到下一個 inBoundHandler,outBoundHandler 的事件通常只會傳播到下一個 outBoundHandler,兩者相互不受干擾。
*總結
- 通過前面編寫客戶端服務端處理邏輯,引出了 pipeline 和 channelHandler 的概念。
- channelHandler 分爲 inBound 和 outBound 兩種類型的接口,分別是處理數據讀與數據寫的邏輯,可與 tcp 協議棧聯繫起來。
- 兩種類型的 handler 均有相應的默認實現,默認會把事件傳遞到下一個,這裏的傳遞事件其實說白了就是把本 handler 的處理結果傳遞到下一個 handler 繼續處理。
- inBoundHandler 的執行順序與我們實際的添加順序相同,而 outBoundHandler 則相反。