Netty到底是個啥
Netty是什么?
?
Netty 是一個利用Java的高級網(wǎng)絡(luò)能力,隱藏其(Java API)背后的復雜性而提供一個易于使用的 NIO 客戶端/服務端框架。
它極大地簡化并優(yōu)化了 TCP 和 UDP 套接字服務器等網(wǎng)絡(luò)編程,并且性能以及安全性等很多方面甚至都要更好。
支持多種協(xié)議 如 FTP,SMTP,HTTP 以及各種二進制和基于文本的傳統(tǒng)協(xié)議。
?
用官方的總結(jié)就是:Netty 成功地找到了一種在不妥協(xié)可維護性和性能的情況下實現(xiàn)易于開發(fā),性能,穩(wěn)定性和靈活性的方法。
?
為什么要用Netty
?
Netty作為一款優(yōu)秀的網(wǎng)絡(luò)框架,自然有令人折服的特點:
?
設(shè)計:針對多種傳輸類型的同一接口。簡單但更強大的線程模型。真正的無連接的數(shù)據(jù)報套接字支持。鏈接邏輯復用。
?
性能:Netty的高性能是它被廣泛使用的一個重要的原因,我們可能都認為Java不太適合 編寫游戲服務端程序,但Netty的到來無疑是降低了懷疑的聲音。
?
較原生Java API有更好的吞吐量,較低的延時。資源消耗更少(共享池和重用)。減少內(nèi)存拷貝。
?
健壯性:原生NIO的客戶端/服務端程序編寫較為麻煩,如果某個地方處理的不好,可能會 導致一些意料之外的異常,如內(nèi)存溢出,死循環(huán)等等,而Netty則為我們簡化了原生API 的使用,這使得我們編寫出來的程序不那么容易出錯。
?
社區(qū):Netty快速發(fā)展的一個重要的原因就是它的社區(qū)非常活躍,這也使得采用它的開發(fā)者越來越多。
? ? ? ? ? ? ?
Netty的簡單使用

左邊是服務端代碼,右邊是客戶端代碼。
?
上面的代碼基本就是模板代碼,每次使用都是這一個套路,唯一需要我們開發(fā)的部分是 handler(…) 和 childHandler(…) 方法中指定的各個 handler,如 EchoServerHandler 和 EchoClientHandler,當然 Netty 源碼也給我們提供了很多的 handler,比如上面的 LoggingHandler,它就是 Netty 源碼中為我們提供的,需要的時候直接拿過來用就好了。
?
我們先來看一下上述代碼中涉及到的一些內(nèi)容:
?
ServerBootstrap 類用于創(chuàng)建服務端實例,Bootstrap 用于創(chuàng)建客戶端實例。
?
兩個 EventLoopGroup:bossGroup 和 workerGroup,它們涉及的是 Netty 的線程模型,可以看到服務端有兩個 group,而客戶端只有一個,它們就是 Netty 中的線程池。
?
Netty 中的 Channel,沒有直接使用 Java 原生的 ServerSocketChannel 和 SocketChannel,而是包裝了 NioServerSocketChannel 和 NioSocketChannel 與之對應。
?
當然,也有對其他協(xié)議的支持,如支持 UDP 協(xié)議的 NioDatagramChannel,本文只關(guān)心 TCP 相關(guān)的。
?
左邊 handler(…) 方法指定了一個 handler(LoggingHandler),這個 handler 是給服務端收到新的請求的時候處理用的。右邊 handler(...) 方法指定了客戶端處理請求過程中需要使用的 handlers。
?
如果你想在 EchoServer 中也指定多個 handler,也可以像右邊的 EchoClient 一樣使用 ChannelInitializer
?
左邊 childHandler(…) 指定了 childHandler,這邊的 handlers 是給新創(chuàng)建的連接用的,我們知道服務端 ServerSocketChannel 在 accept 一個連接以后,需要創(chuàng)建 SocketChannel 的實例,childHandler(…) 中設(shè)置的 handler 就是用于處理新創(chuàng)建的 SocketChannel 的,而不是用來處理 ServerSocketChannel 實例的。
?
pipeline:handler 可以指定多個(需要上面的 ChannelInitializer 類輔助),它們會組成了一個 pipeline,它們其實就類似攔截器的概念,現(xiàn)在只要記住一點,每個 NioSocketChannel 或 NioServerSocketChannel 實例內(nèi)部都會有一個 pipeline 實例。pipeline 中還涉及到 handler 的執(zhí)行順序。
?
ChannelFuture:這個涉及到 Netty 中的異步編程,和 JDK 中的 Future 接口類似。
?
Netty核心組件
?

Bytebuf(字節(jié)容器)
網(wǎng)絡(luò)通信最終都是通過字節(jié)流進行傳輸?shù)摹yteBuf 就是 Netty 提供的一個字節(jié)容器,其內(nèi)部是一個字節(jié)數(shù)組。當我們通過 Netty 傳輸數(shù)據(jù)的時候,就是通過 ByteBuf 進行的。
?
我們可以將 ByteBuf 看作是 Netty 對 Java NIO 提供了 ByteBuffer 字節(jié)容器的封裝和抽象。
?
有很多小伙伴可能就要問了 :為什么不直接使用 Java NIO 提供的 ByteBuffer 呢?
?
因為 ByteBuffer 這個類使用起來過于復雜和繁瑣。
?
Bootstrap 和 ServerBootstrap(啟動引導類)
Bootstrap 是客戶端的啟動引導類/輔助類,具體使用方法如下:
?
EventLoopGroup group = new NioEventLoopGroup();try {//創(chuàng)建客戶端啟動引導/輔助類:BootstrapBootstrap b = new Bootstrap();//指定線程模型b.group(group).......// 嘗試建立連接ChannelFuture f = b.connect(host, port).sync();f.channel().closeFuture().sync();} finally {// 優(yōu)雅關(guān)閉相關(guān)線程組資源group.shutdownGracefully();}
ServerBootstrap 客戶端的啟動引導類/輔助類,具體使用方法如下:
?
// 1.bossGroup 用于接收連接,workerGroup 用于具體的處理????? EventLoopGroup?bossGroup?=?new?NioEventLoopGroup(1);EventLoopGroup workerGroup = new NioEventLoopGroup();try {//2.創(chuàng)建服務端啟動引導/輔助類:ServerBootstrapServerBootstrap b = new ServerBootstrap();//3.給引導類配置兩大線程組,確定了線程模型b.group(bossGroup, workerGroup).......// 6.綁定端口ChannelFuture f = b.bind(port).sync();// 等待連接關(guān)閉f.channel().closeFuture().sync();} finally {//7.優(yōu)雅關(guān)閉相關(guān)線程組資源bossGroup.shutdownGracefully();workerGroup.shutdownGracefully();}}
從上面的示例中,我們可以看出:
?
Bootstrap 通常使用 connet() 方法連接到遠程的主機和端口,作為一個 Netty TCP 協(xié)議通信中的客戶端。另外,Bootstrap 也可以通過 bind() 方法綁定本地的一個端口,作為 UDP 協(xié)議通信中的一端。
ServerBootstrap通常使用 bind() 方法綁定本地的端口上,然后等待客戶端的連接。
Bootstrap 只需要配置一個線程組— EventLoopGroup ,而 ServerBootstrap需要配置兩個線程組— EventLoopGroup ,一個用于接收連接,一個用于具體的 IO 處理。
?
Channel(網(wǎng)絡(luò)操作抽象類)
Channel 接口是 Netty 對網(wǎng)絡(luò)操作抽象類。通過 Channel 我們可以進行 I/O 操作。
?
一旦客戶端成功連接服務端,就會新建一個 Channel 同該用戶端進行綁定,示例代碼如下:
?
// 通過 Bootstrap 的 connect 方法連接到服務端public Channel doConnect(InetSocketAddress inetSocketAddress) {CompletableFuturecompletableFuture = new CompletableFuture<>(); bootstrap.connect(inetSocketAddress).addListener((ChannelFutureListener) future -> {if (future.isSuccess()) {completableFuture.complete(future.channel());} else {throw new IllegalStateException();}});return completableFuture.get();}
比較常用的Channel接口實現(xiàn)類是 :
?
NioServerSocketChannel(服務端)
NioSocketChannel(客戶端)
這兩個 Channel 可以和 BIO 編程模型中的ServerSocket以及Socket兩個概念對應上。
? ? ? ??
EventLoop(事件循環(huán))
EventLoop 介紹
這么說吧!EventLoop(事件循環(huán))接口可以說是 Netty 中最核心的概念了!
?
《Netty 實戰(zhàn)》這本書是這樣介紹它的:
?
EventLoop 定義了 Netty 的核心抽象,用于處理連接的生命周期中所發(fā)生的事件。
?
是不是很難理解?說實話,我學習 Netty 的時候看到這句話是沒太能理解的。
?
說白了,EventLoop 的主要作用實際就是責監(jiān)聽網(wǎng)絡(luò)事件并調(diào)用事件處理器進行相關(guān) I/O 操作(讀寫)的處理。
? ? ? ? ? ? ??
?
Channel 和 EventLoop 的關(guān)系
那 Channel 和 EventLoop 直接有啥聯(lián)系呢?
?
Channel 為 Netty 網(wǎng)絡(luò)操作(讀寫等操作)抽象類,EventLoop 負責處理注冊到其上的Channel 的 I/O 操作,兩者配合進行 I/O 操作。
?
EventloopGroup 和 EventLoop 的關(guān)系
EventLoopGroup 包含多個 EventLoop(每一個 EventLoop 通常內(nèi)部包含一個線程),它管理著所有的 EventLoop 的生命周期。
?
并且,EventLoop 處理的 I/O 事件都將在它專有的 Thread 上被處理,即 Thread 和 EventLoop 屬于 1 : 1 的關(guān)系,從而保證線程安全。
?
下圖是 Netty NIO 模型對應的 EventLoop 模型。通過這個圖應該可以將EventloopGroup、EventLoop、 Channel三者聯(lián)系起來。

ChannelHandler(消息處理器) 和 ChannelPipeline(ChannelHandler 對象鏈表)
下面這段代碼使用過 Netty 的小伙伴應該不會陌生,我們指定了序列化編解碼器以及自定義的 ChannelHandler 處理消息。
b.group(eventLoopGroup).handler(new ChannelInitializer() { @Overrideprotected void initChannel(SocketChannel ch) {ch.pipeline().addLast(new NettyKryoDecoder(kryoSerializer, RpcResponse.class));ch.pipeline().addLast(new NettyKryoEncoder(kryoSerializer, RpcRequest.class));ch.pipeline().addLast(new KryoClientHandler());}});
ChannelHandler 是消息的具體處理器,主要負責處理客戶端/服務端接收和發(fā)送的數(shù)據(jù)。
?
當 Channel 被創(chuàng)建時,它會被自動地分配到它專屬的 ChannelPipeline。一個Channel包含一個 ChannelPipeline。ChannelPipeline 為 ChannelHandler 的鏈,一個 pipeline 上可以有多個 ChannelHandler。
?
我們可以在 ChannelPipeline 上通過 addLast() 方法添加一個或者多個ChannelHandler (一個數(shù)據(jù)或者事件可能會被多個 Handler 處理) 。當一個 ChannelHandler 處理完之后就將數(shù)據(jù)交給下一個 ChannelHandler 。
?
當 ChannelHandler 被添加到的 ChannelPipeline 它得到一個 ChannelHandlerContext,它代表一個 ChannelHandler 和 ChannelPipeline 之間的“綁定”。ChannelPipeline 通過 ChannelHandlerContext來間接管理 ChannelHandler 。

?
ChannelFuture(操作執(zhí)行結(jié)果)
public interface ChannelFuture extends Future{ Channel channel();ChannelFuture addListener(GenericFutureListener extends Future super Void>> var1);......ChannelFuture sync() throws InterruptedException;}
Netty 是異步非阻塞的,所有的 I/O 操作都為異步的。
Netty實際上是不支持異步io的,真正的異步io需要底層操作系統(tǒng)的支持,異步是說數(shù)據(jù)準備好之后由系統(tǒng)通知應用程序你可以來操作數(shù)據(jù)了,而netty所謂的異步是另起一個用戶線程等待數(shù)據(jù)就緒并通過回調(diào)處理,并不是真正意義上的異步io
?
因此,我們不能立刻得到操作是否執(zhí)行成功,但是,你可以通過 ChannelFuture 接口的 addListener() 方法注冊一個 ChannelFutureListener,當操作執(zhí)行成功或者失敗時,監(jiān)聽就會自動觸發(fā)返回結(jié)果。
ChannelFuture f = b.connect(host, port).addListener(future -> {if (future.isSuccess()) {System.out.println("連接成功!");} else {System.err.println("連接失敗!");}}).sync();
并且,你還可以通過ChannelFuture 的 channel() 方法獲取連接相關(guān)聯(lián)的Channel 。
Channel channel = f.channel();另外,我們還可以通過 ChannelFuture 接口的 sync()方法讓異步的操作編程同步的。
//bind()是異步的,但是,你可以通過?`sync()`方法將其變?yōu)橥健?/span>ChannelFuture f = b.bind(port).sync();
本文參考:https://www.javadoop.com/post/netty-part-1
本文參考:https://github.com/Snailclimb/netty-practical-tutorial
結(jié)束語
感謝大家能夠做我最初的讀者和傳播者,請大家相信,只要你給我一份愛,我終究會還你們一頁情的。
Captain會持續(xù)更新技術(shù)文章,和生活中的暴躁文章,歡迎大家關(guān)注
哦對了,后續(xù)所有的文章都會更新到這里
https://github.com/DayuMM2021/Java

