根據[Netty 官網](https://netty.io/)的介紹,它是**異步事件驅動的網路應用程式框架**,結合了NIO 技術以及異步事件驅動的設計模式,可以**快速開發高吞吐低延遲的協議伺服器和客戶端**,並且**不會損失應有的穩定性、可維護性和可擴展性**。 在開始學習Netty 前,建議先去認識**事件驅動設計模式**、**網路IO 模型**以及**Reactor 線程模型**,這樣會比較好理解Netty 底層的運作邏輯。 --- ## 簡易實作(Echo) Netty 對底層功能做了很好的封裝,讓我們不管是開發Server 還是Client 都只要簡單的**配置**跟**撰寫邏輯**即可,這邊先快速實作一個回聲(Echo)伺服器和客戶端。 ### 引入依賴 ```xml <dependency> <groupId>io.netty</groupId> <artifactId>netty-all</artifactId> <version>${netty.version}</version> </dependency> ``` ### Netty Server ```java public class EchoServerApplication { private static final int PORT = 9001; public static void main(String[] args) { EventLoopGroup bossGroup = new NioEventLoopGroup(); EventLoopGroup workerGroup = new NioEventLoopGroup(); try { ServerBootstrap serverBootstrap = new ServerBootstrap() .group(bossGroup, workerGroup) .channel(NioServerSocketChannel.class) .childHandler(new ChannelInitializer<SocketChannel>() { @Override protected void initChannel(SocketChannel socketChannel) throws Exception { ChannelPipeline channelPipeline = socketChannel.pipeline(); channelPipeline.addLast(new EchoChannelHandler()); } }); ChannelFuture future = serverBootstrap.bind(PORT).sync(); future.channel().closeFuture().sync(); } catch (InterruptedException e) { throw new RuntimeException(e); } finally { bossGroup.shutdownGracefully(); workerGroup.shutdownGracefully(); } } } @Slf4j class EchoChannelHandler extends ChannelInboundHandlerAdapter { @Override public void channelRead(ChannelHandlerContext channelHandlerContext, Object msg) throws Exception { ByteBuf message = (ByteBuf) msg; System.out.println("[" + LocalDateTime.now() + "] Message received: " + message.toString(StandardCharsets.UTF_8)); channelHandlerContext.writeAndFlush(msg); sleep(1000); } @Override public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception { cause.printStackTrace(); ctx.close(); } } ``` ### Netty Client ```java public class EchoClientApplication { private static final String HOST = "127.0.0.1"; private static final int PORT = 9001; public static void main(String[] args) { EventLoopGroup workerGroup = new NioEventLoopGroup(); try { Bootstrap bootstrap = new Bootstrap() .group(workerGroup) .channel(NioSocketChannel.class) .handler(new ChannelInitializer<SocketChannel>() { @Override protected void initChannel(SocketChannel socketChannel) throws Exception { ChannelPipeline channelPipeline = socketChannel.pipeline(); channelPipeline.addLast(new EchoChannelHandler()); } }); ChannelFuture future = bootstrap.connect(HOST, PORT).sync(); future.channel().closeFuture().sync(); } catch (InterruptedException e) { throw new RuntimeException(e); } finally { workerGroup.shutdownGracefully(); } } } @Slf4j class EchoChannelHandler extends ChannelInboundHandlerAdapter { @Override public void channelActive(ChannelHandlerContext ctx) throws Exception { System.out.println("[" + LocalDateTime.now() + "] Channel Active: {}" + ctx); ByteBuf message = Unpooled.copiedBuffer("Hello, Netty.", StandardCharsets.UTF_8); ctx.writeAndFlush(message); } @Override public void channelRead(ChannelHandlerContext channelHandlerContext, Object msg) throws Exception { ByteBuf message = (ByteBuf) msg; System.out.println("[" + LocalDateTime.now() + "] Message received: " + message.toString(StandardCharsets.UTF_8)); channelHandlerContext.writeAndFlush(msg); sleep(1000); } @Override public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception { cause.printStackTrace(); ctx.close(); } } ``` --- ## 核心元件 從上面的程式碼可以知道,不到百行的程式就可以建立一個Netty 應用程式,但這中間其實需要各種元件的相互合作才能達成,接下來就開始一一介紹吧~ ### AbstractBootstrap 這是`ServerBootstrap`和`Bootstrap`的父類別,包含了啟動Netty 應用程式需要使用的配置方法,不過我們不需要自己去實作它,只要用上述兩個類別配置即可。 - **ServerBootstrap**:Netty Server 的啟動類,配置必要參數、通道類型及屬性後,使用`bind()`綁定監聽端口就開始接收Client 連線請求。 - **Bootstrap**:Netty Client 的啟動累,配置必要參數、通道類型及屬性後,使用`connect()`與Netty Server 建立連線就開始運行。 ### ChannelFuture 在`ServerBootstrap`使用`bind()`或`Bootstrap`使用`connect()`後,兩個方法都是回傳一個`ChannelFuture`物件,而且會再搭配`sync()`方法。 這是因為在Netty 的IO 操作都是非阻塞的異步操作,它們返回的`ChannelFuture`代表**一個異步操作的未來結果**,它允許在執行異步的IO 操作時註冊一個監聽器,以便在操作完成、成功或失敗時得到通知。而使用`sync()`監聽 `ChannelFuture`,可以處理異步操作的結果,在操作完成後執行相應的操作,不管這個操作是成功還是失敗。 這種異步的設計模式允許在執行IO 操作時不會阻塞程式的運行,而是可以在操作完成後得到通知,從而提高了系統的效率和併發處理能力。 ### Channel 接下來要一次介紹三個元件,`Channel`、`ChannelPipeline`和`ChannelHandler`,它們一起介紹應該會比較好理解。 - **Channel**:在建立一個新的連線時會創建一個`Channel`物件,可以理解為Server 和Client 之間的通道,負責這兩端的IO 事件,是Netty 中所有IO 操作的通用介面。 - **SocketChannel**:用於TCP 通訊。 - **FileChannel**:用於文件IO 操作。 - **DatagramChannel**:用於UDP 通訊。 - **ChannelPipeline**:在`Channel`物件被建立時,會綁定一個`ChannelPipeline`,它包含了一系列與該`Channel`有關的`ChannelHandler`。 - **ChannelHandler**:負責處理`Channel`的IO 事件,主要撰寫業務邏輯的地方,概念就像Spring Security 的FilterChain,每個處理器都會執行自己的業務,處理完就往下個節點丟或釋放資源。 ### EventLoop `EventLoop`會維護多個`Channel`物件,在`Channel`被建立完成後,會被註冊到某個`EventLoop`上,該`Channel`後續的IO 事件都交由這個`EventLoop`執行。 - **Selector**:`EventLoop`中的`Selector`實現了IO 多路復用的概念,可以簡單將它的工作分為**監聽**和**通知**, - **監聽**:不斷輪詢`EventLoop`中的所有`Channel`,確認是否有IO 事件就緒。 - **通知**:當有事件就緒時`Selector`就會通知所屬的`EventLoop`,讓`EventLoop`接手後續的方法調用。 - **EventLoop**:它維護了一個**執行緒**和**事件隊列**,內部包含了一個`Selector`和多個`Channel`,當`Selector`通知有事件就緒時,`EventLoop`會從事件隊列中取出就緒的事件,然後根據事件類型調用對應`Channel`的`ChannelPipeline`處理該事件。 ### EventLoopGroup `EventLoopGroup`負責管理`EventLoop`的生命週期,可以理解為一個線程池,每個線程`EventLoop`負責處理多個`Channel`的IO 事件。 在Client 只會設定一個`EventLoopGroup`,不需要多作解釋,工作內容就跟上面講的一樣,需要額外再介紹的是Server 的`EventLoopGroup`。 - 在Server 通常會設定兩個`EventLoopGroup`,分為**bossGroup** 和**workerGroup**,或分為**parentGroup** 和**childGroup**。 - 這是因為在Netty Server 運用了**主從Reactors 多線程模型**的概念,由**bossGroup** 負責接收新的連線請求並將其註冊到**workerGroup** 中的某個`EventLoop`上進行後續處理,就像PM 接收需求然後叫PG 處理一樣,一個負責接收一個負責處理。 - 這樣的分層結構應用在伺服器上可以很好地利用多核CPU 資源和實現高效的NIO 處理,能夠允許Server 建立和管理多個連線,同時保持高效的事件處理和併發性能。**bossGroup 負責高效地接受連線請求**,而**workerGroup 負責實際處理這些連線上的IO 事件**。 --- ## 總結 我們從Server 的角度重新整理一下核心元件的運作流程: 1. `ServerBootstrap`在Server 端配置完畢,使用`bind()`監聽指定端口,準備接收連線請求。 2. `Bootstrap`在Client 端配置完畢,使用`connect()`發送連線請求到Server。 3. Server 接收到連線請求,**bossGroup** 輪詢時監聽到連線請求,執行以下步驟: 1. 與Client 建立連線。 2. 建立`Channel`物件。 3. 綁定`ChannelPipeline`。 4. 將Channel物件註冊到某個**workerGroup** 上的某個`EventLoop`。 4. **workerGroup** 上的Selector 不斷輪詢IO 讀寫事件。 以上就是Netty 核心元件及運作流程,它運用了多種概念以及層層封裝,讓我們只需要簡單幾行程式碼就可以建立Netty 應用程式,不過關於底層實現還是有學習的價值,尤其是一些關鍵的概念,了解過後可以運用在其他的專案上。 --- ## 參考資料 [Netty: Home](https://netty.io) [User guide for 4.x](https://netty.io/wiki/user-guide-for-4.x.html) [Netty入门看这一篇就够了](https://juejin.cn/post/6924528182313893896#heading-14) [netty到底是同步还是异步?](https://www.zhihu.com/question/469794520) [netty系列之:netty中的Channel详解](https://www.cnblogs.com/flydean/p/15106975.html) [深入理解 Netty-Channel架构体系](https://www.cnblogs.com/ZhuChangwu/p/11204057.html)
×
Sign in
Email
Password
Forgot password
or
By clicking below, you agree to our
terms of service
.
Sign in via Facebook
Sign in via Twitter
Sign in via GitHub
Sign in via Dropbox
Sign in with Wallet
Wallet (
)
Connect another wallet
New to HackMD?
Sign up