根據[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)