從一個(gè)Demo開始,揭開Netty的神秘面紗
本文轉(zhuǎn)載自微信公眾號(hào)「阿丸筆記」,作者阿丸筆記。轉(zhuǎn)載本文請(qǐng)聯(lián)系阿丸筆記公眾號(hào)。
上一篇文章我們對(duì)于I/O多路復(fù)用、Java NIO包 和 Netty 的關(guān)系有了全面的認(rèn)識(shí)。
到目前為止,我們已經(jīng)從I/O模型出發(fā),逐步接觸到了Netty框架。這個(gè)過程中,基本解答了Netty是什么、為什么使用Netty等前置問題。給我們學(xué)習(xí)Netty提供了最原始的背景知識(shí)。
有了這些做基礎(chǔ),下面我們可以開始慢慢去揭開Netty的神秘面紗了。
本文預(yù)計(jì)閱讀時(shí)間約 5分鐘,將重點(diǎn)圍繞以下幾個(gè)問題展開:
- 如何用Netty編寫一個(gè)Server端服務(wù)Demo
- 從Demo看Netty的邏輯架構(gòu),初識(shí)各個(gè)組件
1.編寫一個(gè)Server端Demo
1.1 基于主從Reactor模式的Demo實(shí)現(xiàn)
如果從來沒用過Netty,那么了解一下用Netty編寫的Server端Demo是必不可少的。
還記得我們上一篇說的 “主從Reactor模式” 嗎?可以構(gòu)建兩個(gè) Reactor,主 Reactor 單獨(dú)監(jiān)聽server socket,accept新連接,然后將建立的 SocketChannel 注冊給指定的從 Reactor,從Reactor再執(zhí)行事件的讀寫、分發(fā),把業(yè)務(wù)處理就扔給worker線程池完成。
我們就按照這個(gè)模式,用Netty編寫一個(gè)服務(wù)端程序吧。
直接上代碼!
一個(gè)簡單的自定義ChannelHandler類,用來自定義業(yè)務(wù)處理邏輯:
一個(gè)包含Bootstrap的服務(wù)端啟動(dòng)類:
- public class EchoServer {
- private int port;
- public EchoServer(int port) {
- this.port = port;
- }
- public static void main(String[] args) throws Exception {
- new EchoServer(8833).start();
- }
- public void start() throws Exception {
- //1.Reactor模型的主、從多線程
- EventLoopGroup mainGroup = new NioEventLoopGroup();
- EventLoopGroup childGroup = new NioEventLoopGroup();
- try {
- //2.構(gòu)造引導(dǎo)器實(shí)例ServerBootstrap
- ServerBootstrap b = new ServerBootstrap();
- b.group(mainGroup, childGroup)
- .channel(NioServerSocketChannel.class) //2.1 設(shè)置NIO的channel
- .localAddress(new InetSocketAddress(port)) //2.2 配置本地監(jiān)聽端口
- .childHandler(new ChannelInitializer<SocketChannel>() { //2.3 初始化channel的時(shí)候,配置Handler
- @Override
- protected void initChannel(final SocketChannel socketChannel) {
- socketChannel.pipeline()
- .addLast("codec", new HttpServerCodec())
- .addLast("compressor", new HttpContentCompressor())
- .addLast("aggregator", new HttpObjectAggregator(65536))
- .addLast("handler", new EchoServerHandler()); //2.4 加入自定義業(yè)務(wù)邏輯ChannelHandler
- }
- });
- ChannelFuture f = b.bind().sync(); //3.啟動(dòng)監(jiān)聽
- System.out.println("Http Server started, Listening on " + port);
- f.channel().closeFuture().sync();
- } finally {
- mainGroup.shutdownGracefully().sync();
- childGroup.shutdownGracefully().sync();
- }
- }
- }
啟動(dòng)后,通過curl調(diào)用,得到響應(yīng)。
Demo完成了!
對(duì)于之前覺得用Java NIO包實(shí)現(xiàn)起來很復(fù)雜的的 “主從Reactor模式” ,用Netty簡簡單單就完成了。
只需要?jiǎng)?chuàng)建兩個(gè)EventLoopGroup,然后綁定到引導(dǎo)器ServerBootstrap上就好了.
mainGroup 是主 Reactor,childGroup 是從 Reactor。它們分別使用不同的 NioEventLoopGroup,主 Reactor 負(fù)責(zé)處理 Accept,然后把 Channel 注冊到從 Reactor 上,從 Reactor 主要負(fù)責(zé) Channel 生命周期內(nèi)的所有 I/O 事件。
1.2 Demo分析
從上面的Demo代碼可以看出,對(duì)于所有用Netty編寫的服務(wù)端程序,至少需要兩個(gè)部分:
- 至少一個(gè)ChannelHandler
- Bootstrapping
1)ChannelHandler
這個(gè)組件用來實(shí)現(xiàn)對(duì)客戶端發(fā)送過來的數(shù)據(jù)進(jìn)行處理,可能包括編解碼、自定義業(yè)務(wù)邏輯處理等等。
對(duì)于ChannelHandler來說,有非常多的實(shí)現(xiàn)。在Demo中我們簡單使用了幾個(gè)Netty自帶的Handler,包括HttpServerCodec、HttpContentCompressor、HttpObjectAggregator,也使用了一個(gè)自定義的EchoServerHandler。
可以看到,對(duì)于Handler的使用,是非常重要也是非常方便的一個(gè)環(huán)節(jié)。我們會(huì)在以后的文章中詳細(xì)展開。
2)Bootstrapping
啟動(dòng)代碼部分。用來配置服務(wù)端的啟動(dòng)參數(shù),包括監(jiān)聽端口、服務(wù)端線程池配置、網(wǎng)絡(luò)連接屬性配置、ChannelHandler配置等等。
結(jié)合Demo來看,主要分為這幾個(gè)步驟:
- 創(chuàng)建一個(gè)ServerBootstrap實(shí)例,用來引導(dǎo)啟動(dòng)。
- 創(chuàng)建一個(gè)(當(dāng)我們使用主從Reactor模式時(shí),需要?jiǎng)?chuàng)建兩個(gè))NioEventLoopGroup實(shí)例來處理事件, 比如接受一個(gè)新的客戶端連接、讀寫數(shù)據(jù)等。
- 指定一個(gè)端口,用來作為服務(wù)端的監(jiān)聽端口。
- 使用一系列channelHandler來初始化每個(gè)Channel,包括自定義業(yè)務(wù)邏輯實(shí)現(xiàn)的channelHandler。
- 調(diào)用ServerBootstrap.bind() 來真正觸發(fā)啟動(dòng)。
2. Netty的邏輯架構(gòu)
通過上面的Demo演示,我們對(duì) Netty 的使用已經(jīng)有了一個(gè)大概的印象。
下面,我們根據(jù)Demo中使用的幾個(gè)組件,一起梳理一下 Netty 的邏輯架構(gòu)。
結(jié)合我們的Demo和這個(gè)邏輯架構(gòu)圖,我們梳理下各個(gè)組件的流轉(zhuǎn)過程:
- 服務(wù)端利用ServerBootstrap進(jìn)行啟動(dòng)引導(dǎo),綁定監(jiān)聽端口
- 啟動(dòng)初始化時(shí)有 main EventLoopGroup 和 child EventLoopGroup 兩個(gè)組件,其中 main EventLoopGroup負(fù)責(zé)監(jiān)聽網(wǎng)絡(luò)連接事件。當(dāng)有新的網(wǎng)絡(luò)連接時(shí),就將 Channel 注冊到 child EventLoopGroup。
- child EventLoopGroup 會(huì)被分配一個(gè) EventLoop 負(fù)責(zé)處理該 Channel 的讀寫事件。
- 當(dāng)客戶端發(fā)起 I/O 讀寫事件時(shí),服務(wù)端 EventLoop 會(huì)進(jìn)行數(shù)據(jù)的讀取,然后通過 ChannelPipeline 依次有序觸發(fā)各種ChannelHandler進(jìn)行數(shù)據(jù)處理。
- 客戶端數(shù)據(jù)會(huì)被依次傳遞到 ChannelPipeline 的 ChannelInboundHandler 中,在一個(gè)handler中處理完后就會(huì)傳入下一個(gè)handler。
- 當(dāng)數(shù)據(jù)寫回客戶端時(shí),會(huì)將處理結(jié)果依次傳遞到 ChannelPipeline 的 ChannelOutboundHandler 中,在一個(gè)handler中處理完后就會(huì)傳入下一個(gè)handler,最后返回客戶端。
以上便是 Netty 各個(gè)組件的邏輯架構(gòu),我們暫時(shí)只需要了解個(gè)大致框架即可,后面我們會(huì)詳細(xì)介紹各個(gè)組件。
有幾個(gè)比較常見的問題在這里總結(jié)下:
1)什么是Channel
Channel 的字面意思是“通道”,它是網(wǎng)絡(luò)通信的載體,提供了基本的 API 用于網(wǎng)絡(luò) I/O 操作,如 register、bind、connect、read、write、flush 等。
Netty 實(shí)現(xiàn)的 Channel 是以 JDK NIO Channel 為基礎(chǔ)的,提供了更高層次的抽象,屏蔽了底層 Socket。
2)什么是ChannleHandler和ChannelPipeline
ChannelHandler實(shí)現(xiàn)對(duì)客戶端發(fā)送過來的數(shù)據(jù)進(jìn)行處理,可能包括編解碼、自定義業(yè)務(wù)邏輯處理等等。
ChannelPipeline 負(fù)責(zé)組裝各種 ChannelHandler,當(dāng) I/O 讀寫事件觸發(fā)時(shí),ChannelPipeline 會(huì)依次調(diào)用 ChannelHandler 列表對(duì) Channel 的數(shù)據(jù)進(jìn)行攔截和處理。
3)什么是EventLoopGroup?
EventLoopGroup 本質(zhì)是一個(gè)線程池, 是 Netty Reactor 線程模型的具體實(shí)現(xiàn)方式,主要負(fù)責(zé)接收 I/O 請(qǐng)求,并分配線程執(zhí)行處理請(qǐng)求。我們在demo中使用了它的實(shí)現(xiàn)類 NioEventLoopGroup,也是 Netty 中最被推薦使用的線程模型。
我們還通過構(gòu)建main EventLoopGroup 和 child EventLoopGroup 實(shí)現(xiàn)了 “主從Reactor模式”。
4)EventLoopGroup、EventLoop、Channel有什么關(guān)系?
一個(gè) EventLoopGroup 往往包含一個(gè)或者多個(gè) EventLoop。
EventLoop 用于處理 Channel 生命周期內(nèi)的所有 I/O 事件,如 accept、connect、read、write 等 I/O 事件。
EventLoop 同一時(shí)間會(huì)與一個(gè)線程綁定,每個(gè) EventLoop 負(fù)責(zé)處理多個(gè) Channel。
參考書目:
《Netty in Action》