目錄
Netty 是什么?
Netty 的目標
Netty 實戰案例 DiscardServer
服務端程序 NettyDiscardServer
業務處理器 NettyDiscardHandler
配置類?NettyDemoConfig
回顧 Reactor 模式中的 IO 事件處理流程
Netty 中的 Channel
Netty 中的 Reactor
Netty 中的 Handler
Netty 中的 Pipeline
Netty 是什么?
????????Netty 是一個基于 Java NIO 的客戶端/服務器端通信框架,旨在簡化網絡編程的開發過程。它不僅支持高并發、高擴展性,還具備良好的可維護性,非常適合用于構建高性能的網絡應用程序。Netty 的核心實現基于 Reactor 模式,封裝了底層復雜的事件驅動機制,使開發者能夠專注于業務邏輯而無需處理底層通信細節。
????????通過使用 Netty,開發者可以輕松實現 TCP、UDP、HTTP 等常見通信協議的支持,從而大大簡化了底層通信的編程工作。無論是 TCP、UDP 這樣的低層傳輸協議,還是 FTP、SMTP、HTTP 等應用層協議,Netty 都能提供統一且高效的編程模型,幫助開發者快速搭建穩定的網絡服務端或客戶端應用。
Netty 的目標
????????Netty 的設計目標是幫助開發者快速而輕松地構建各類通信應用程序。它通過抽象底層復雜的網絡通信細節,提供了清晰且統一的編程接口,使得網絡程序的開發更加高效和簡潔。
????????在實現高性能通信方面,Netty 支持多種通信協議,能夠處理包括 TCP、UDP 在內的傳輸層協議,以及 HTTP、FTP 等應用層協議。同時,Netty 基于異步非阻塞的通信機制,結合事件驅動的架構,有效提升了系統在高并發場景下的響應能力和吞吐量。
????????為了實現靈活的通信控制,Netty 提供了一系列核心組件,包括用于數據傳輸的 Channel、用于處理通信邏輯的 Handler,以及用于執行 I/O 事件循環的 EventLoopGroup 等。這些組件相互協作,構成了 Netty 強大而靈活的通信框架核心。
Netty 實戰案例 DiscardServer
什么是 DiscardServer?
一個丟棄消息的服務器:接收客戶端消息后不做任何處理直接丟棄,相當于 Netty 的 “HelloWorld”
服務端程序 NettyDiscardServer
下面的代碼很多地方是新知識,先大體了解代碼功能,后續會進行詳細講解
服務端核心步驟
1. 創建反應器線程組(EventLoopGroup)
2.?配置 ServerBootstrap
3.?啟動服務器
????????bind().sync() 同步綁定。
????????closeFuture().sync() 同步關閉監聽。
????????shutdownGracefully() 優雅關閉線程組。
// 定義一個Netty丟棄服務器類,功能是接收數據并直接丟棄
public class NettyDiscardServer {// 服務器監聽的端口號private final int serverPort;// Netty的服務端啟動類,用于配置和啟動服務器ServerBootstrap b = new ServerBootstrap();// 構造函數,傳入服務器端口public NettyDiscardServer(int port) {this.serverPort = port;}// 運行服務器的方法public void runServer() {// 創建boss事件循環組,處理連接請求,參數1表示使用1個線程EventLoopGroup bossLoopGroup = new NioEventLoopGroup(1);// 創建worker事件循環組,處理IO操作,不指定參數則使用默認線程數EventLoopGroup workerLoopGroup = new NioEventLoopGroup();try {// 配置服務器引導類b.group(bossLoopGroup, workerLoopGroup); // 設置事件循環組b.channel(NioServerSocketChannel.class); // 指定使用NIO傳輸通道b.localAddress(serverPort); // 設置服務器監聽地址和端口b.option(ChannelOption.SO_KEEPALIVE, true); // 設置TCP保持連接選項// 配置子通道的處理器b.childHandler(new ChannelInitializer<SocketChannel>() {// 初始化通道時調用protected void initChannel(SocketChannel ch) {// 向管道添加自定義的處理器ch.pipeline().addLast(new NettyDiscardHandler());}});// 綁定服務器并同步等待綁定完成ChannelFuture channelFuture = b.bind().sync();// 打印服務器啟動成功信息System.out.println("服務器啟動成功,監聽端口: " +channelFuture.channel().localAddress());// 獲取通道關閉的FutureChannelFuture closeFuture = channelFuture.channel().closeFuture();// 同步等待服務器通道關閉closeFuture.sync();} catch (Exception e) {e.printStackTrace(); // 打印異常堆棧} finally {// 優雅關閉事件循環組workerLoopGroup.shutdownGracefully();bossLoopGroup.shutdownGracefully();}}// 主方法,程序入口public static void main(String[] args) {// 從配置類獲取端口號int port = NettyDemoConfig.SOCKET_SERVER_PORT;// 創建服務器實例并運行new NettyDiscardServer(port).runServer();}
}
關鍵步驟:
1. 創建反應器線程組
EventLoopGroup bossLoopGroup = new NioEventLoopGroup(1); ?// 負責接收客戶端連接
EventLoopGroup workerLoopGroup = new NioEventLoopGroup(); // 負責處理 IO 讀寫
bossGroup:只負責 accept 新連接
workerGroup:負責處理每個連接的讀寫事件(比如數據收發)
2. 配置啟動器 ServerBootstrap
b.group(bossLoopGroup, workerLoopGroup);
b.channel(NioServerSocketChannel.class); // 使用 NIO 的 ServerSocketChannel
b.localAddress(serverPort); ? ? ? ? ? ? ?// 綁定監聽端口
b.option(ChannelOption.SO_KEEPALIVE, true); // 保持長連接
channel():指定使用 NIO 網絡模型
option():配置服務器通道參數,如是否保持連接
NioServerSocketChannel 是 Netty 中專門用于 NIO 模式下的 TCP 服務器端通道,對應底層的 ServerSocketChannel(Java NIO 中的 TCP 服務器套接字通道),專門處理 TCP 協議的連接請求
ChannelOption.SO_KEEPALIVE 是 TCP 協議的保活機制選項,用于設置是否開啟 TCP 連接的保活功能。
當該選項設置為 true 時:
如果客戶端和服務器長時間沒有數據交互(默認通常是 2 小時左右,具體時間取決于系統配置),TCP 協議會自動發送探測數據包,檢測對方是否還在線。
如果對方正常響應,說明連接仍然有效,繼續保持連接;
如果多次探測無響應,會判定連接已失效,自動關閉連接,釋放資源。
3. 設置子通道處理器
b.childHandler(new ChannelInitializer<SocketChannel>() {protected void initChannel(SocketChannel ch) {ch.pipeline().addLast(new NettyDiscardHandler());}
});
每當有客戶端連接成功,就會初始化它的通道(SocketChannel)
向 pipeline 添加處理器 NettyDiscardHandler
業務處理器 NettyDiscardHandler
NettyDiscardHandler 是服務端通道的自定義處理器,用于讀取并丟棄客戶端發送的數據。
// 自定義的Netty通道處理器,繼承自ChannelInboundHandlerAdapter
public class NettyDiscardHandler extends ChannelInboundHandlerAdapter {// 當通道有數據可讀時調用@Overridepublic void channelRead(ChannelHandlerContext ctx, Object msg) {// 將消息轉換為ByteBuf類型ByteBuf in = (ByteBuf) msg;try {System.out.println("收到消息,丟棄如下:");// 循環讀取ByteBuf中的可讀字節while (in.isReadable()) {// 逐個字節讀取并打印為字符System.out.print((char) in.readByte());}System.out.println(); // 打印換行} finally {// 釋放消息資源,防止內存泄漏ReferenceCountUtil.release(msg);}}
}
步驟 | 說明 |
---|---|
channelRead() | 當通道收到客戶端數據時觸發 |
msg → ByteBuf | 將消息強制轉換為 ByteBuf 類型 |
while (in.isReadable()) | 遍歷 ByteBuf?中所有可讀字節 |
readByte() | 逐字節讀取并打印內容 |
ReferenceCountUtil.release() | 釋放 ByteBuf 引用,防止內存泄漏 |
為什么需要手動釋放 ByteBuf?
Netty 使用引用計數機制管理內存,ByteBuf 在讀取后必須顯示釋放,否則會導致內存泄漏。ReferenceCountUtil.release(msg) 等價于調用 ((ByteBuf) msg).release(),但更加通用安全。
在 Netty 中,基于引用計數機制(ReferenceCounted 接口)管理的對象(如 ByteBuf),需要開發者顯式調用釋放方法來回收資源,這就是手動釋放的含義 —— 不像 JVM 堆內存那樣由垃圾回收器自動回收,而是需要代碼中主動觸發釋放。
ReferenceCountUtil.release(msg) 的作用是:
1. 檢查 msg 是否是引用計數對象(實現了 ReferenceCounted 接口,比如 ByteBuf)
2. 如果是,則調用其 release() 方法減少引用計數,當計數減為 0 時,對象會被釋放(回收內存)
3. 如果不是引用計數對象,該方法會忽略操作,避免報錯。
配置類?NettyDemoConfig
// Netty演示配置類
public class NettyDemoConfig {// 定義服務器端口常量public static final int SOCKET_SERVER_PORT = 12345;
}
如果沒有客戶端發送數據,只啟動服務器無法驗證效果。
所以可以使用之前的 NioDiscardClient 客戶端,在下面鏈接有講
詳解 NIO Selector_java nio selectors 工作原理-CSDN博客
下面是完整的客戶端代碼,運行時需要先啟動服務端,再啟動客戶端
public class NioDiscardClient {// 靜態的 Logger 實例private static final Logger logger = LoggerFactory.getLogger(NioDiscardClient.class);public static void startClient() throws IOException {// 1. 配置服務器地址和端口InetSocketAddress address = new InetSocketAddress("127.0.0.1", 12345);// 2. 獲取 SocketChannel 并連接到服務器SocketChannel socketChannel = SocketChannel.open(address);// 3. 設置為非阻塞模式socketChannel.configureBlocking(false);// 4. 等待連接建立完成while (!socketChannel.finishConnect()) {// 自旋等待連接完成}logger.info("客戶端連接成功");// 5. 準備數據緩沖區ByteBuffer byteBuffer = ByteBuffer.allocate(1024);byteBuffer.put("hello world".getBytes());byteBuffer.flip();// 6. 將數據寫入通道(發送到服務端)socketChannel.write(byteBuffer);// 7. 關閉輸出流并斷開連接socketChannel.shutdownOutput();socketChannel.close();}public static void main(String[] args) throws IOException {startClient();}
}
如何驗證消息被丟棄?
啟動客戶端程序,向服務器發消息。
服務端打印接收到的內容,但不會返回任何響應。
回顧 Reactor 模式中的 IO 事件處理流程
單線程 Reactor 模式-CSDN博客
多線程 Reactor 模式-CSDN博客
如果不了解 Reactor 模式,可點擊鏈接跳轉學習
一個 IO 操作從內核產生到業務代碼執行,會經歷 4 個階段:
階段 | 說明 |
---|---|
第 1 步:注冊 | IO 事件通過 Channel 注冊到 Selector |
第 2 步:查詢 | Reactor(反應器)通過線程輪詢 Selector,檢查是否有事件就緒 |
第 3 步:分發 | 如果發現事件,交給綁定的 Handler 處理 |
第 4 步:處理 | 真正的 IO 讀寫邏輯或業務邏輯由 Handler 完成 |
第 1、2 步由 Java NIO 提供機制,第 3、4 步為 Reactor 核心。
Netty 中的 Channel
????????在 Netty 中,Channel 是進行所有 IO 操作的核心載體。它是對 Java NIO 中 Channel 的封裝和擴展,提供了更加靈活和統一的接口,使得網絡通信編程變得更為簡單高效。Channel 表示一個網絡連接,既可以代表客戶端的連接通道,也可以代表服務端的監聽通道。
????????針對不同的通信協議,例如 TCP、UDP 和 SCTP,Netty 提供了對應的 Channel 實現類。同時,為了支持不同的 IO 模型,Netty 為每種協議都提供了基于 NIO(非阻塞 IO)和 OIO(阻塞 IO)的兩種實現版本。例如,針對 TCP 協議,Netty 提供了 NioSocketChannel 和 OioSocketChannel。
????????通過這種方式,Netty 不僅屏蔽了底層 IO 實現的復雜性,還為用戶提供了豐富且統一的通信編程模型。無論是同步通信還是異步通信,開發者都可以通過 Channel 接口輕松完成底層數據傳輸的操作。
常見 Channel 類型
協議類型 | Channel 實現 |
---|---|
TCP 傳輸 | NioSocketChannel, OioSocketChannel |
TCP 監聽 | NioServerSocketChannel, OioServerSocketChannel |
UDP 傳輸 | NioDatagramChannel, OioDatagramChannel |
SCTP 傳輸 | NioSctpChannel, OioSctpChannel |
SCTP 監聽 | NioSctpServerChannel, OioSctpServerChannel |
Netty 的 Channel 封裝關系
????????在 Netty 中,Channel 的實現并不是直接使用 Java NIO 的 Channel 接口,而是在其基礎上進行了多層封裝,以提供更高層次的抽象和更強的功能擴展。以常用的 NioSocketChannel 為例,它的繼承結構遵循了一個清晰的層級體系:NioSocketChannel 繼承自 AbstractNioByteChannel,而后者又繼承自 AbstractNioChannel,再向上是 AbstractChannel,最終實現了最頂層的 Channel 接口。
????????這種層層封裝的設計,使得 Netty 能夠在不同層級中分離出通用的邏輯與具體的實現細節,從而提升代碼的復用性與擴展性。同時,盡管 Netty 封裝了自己的 Channel 體系,但其最底層依然是依賴于 Java NIO 中的 SelectableChannel,也就是說,Netty 的所有 IO 操作最終都會落地到 NIO 提供的底層通道上執行。因此,Netty 在提升開發體驗的同時,也保留了 NIO 高性能的核心優勢。
頂層接口(Channel):定義通用行為,與具體實現無關
抽象類(AbstractChannel 等):封裝通用邏輯,沉淀復用代碼
具體實現類(NioSocketChannel 等):專注于特定協議和 IO 模型的細節
SelectableChannel 是 Java NIO 中的一個抽象類,是所有支持多路復用(Selector)的通道(Channel)的父類。它的核心作用是允許通道被注冊到 Selector 上,通過 Selector 實現對多個通道的 IO 事件(讀就緒、寫就緒、連接就緒等)進行統一監聽和管理。
Netty 中的 Reactor
????????在 Netty 中,Reactor 模式的核心組件之一是 NioEventLoop,負責事件輪詢與處理調度。NioEventLoop 是 Netty 對 Java NIO 中事件驅動機制的封裝,其本質是一個反應器線程,用于不斷輪詢底層的 Selector,監聽是否有 IO 事件發生,并在事件就緒后將其分發給對應的處理器(Handler)進行處理。
????????每一個 NioEventLoop 都擁有一個獨立的線程,并且綁定了一個 Selector 實例。它們之間形成一一對應的關系,保證事件處理的線程安全性和高效性。此外,Netty 設計中允許多個 Channel 被注冊到同一個 NioEventLoop 上,從而實現一對多的高效事件管理機制。這種設計不僅充分利用了線程資源,還有效支持了高并發場景下的網絡通信。
NioEventLoop 的繼承結構:
????????在 Netty 的架構中,NioEventLoop 是實現事件驅動模型的核心類之一,它的繼承結構體現了 Netty 對事件處理流程的高度抽象與組織。具體來說,NioEventLoop 繼承自 SingleThreadEventLoop,而 SingleThreadEventLoop 又繼承自 SingleThreadEventExecutor,最終實現了頂層的 Executor 接口。
????????這一層層的繼承關系體現了職責的逐步細化:從通用的任務執行器(Executor),到專用于單線程執行的事件循環體(SingleThreadEventExecutor),再到真正綁定了 IO 事件處理能力的 NioEventLoop。通過這樣的設計,Netty 將事件處理的通用性與專業性有機結合,使得每一個 EventLoop 既具備強大的調度能力,又能專注于單線程下的高效執行。
????????在實現細節上,NioEventLoop 擁有兩個至關重要的成員屬性:其一是 Thread,即事件處理線程;其二是 Selector,用于監聽和輪詢注冊的 IO 事件。這兩個核心組件配合使用,使得 NioEventLoop 成為支撐 Netty 高性能異步 IO 的關鍵支點。
EventLoop 與 Channel 的關系
一個 EventLoop 可以管理成千上萬個 Channel。
屬于一對多的綁定結構。
Netty 中的 Handler
IO 事件類型:
類型 | 對應 SelectionKey |
---|---|
讀就緒 | OP_READ |
寫就緒 | OP_WRITE |
連接完成 | OP_CONNECT |
接收連接 | OP_ACCEPT |
Handler 分類:
類型 | 說明 |
---|---|
ChannelInboundHandler | 入站事件處理器(如讀數據) |
ChannelOutboundHandler | 出站事件處理器(如寫數據) |
????????在 Netty 中,Handler 是承載業務邏輯處理的核心組件。它用于響應和處理各種 IO 事件,是連接數據與業務處理之間的橋梁。根據事件的方向不同,Handler 被分為兩類:入站處理器和出站處理器。
????????ChannelInboundHandler 是處理入站事件的處理器,負責響應例如客戶端連接建立、數據讀取等事件,典型方法如 channelRead()、channelActive()。而 ChannelOutboundHandler 則用于處理出站事件,主要負責將數據寫出到網絡或進行編碼轉換等操作,典型方法如 write()、flush()。
????????每個 Channel 在創建時,都會綁定一個 ChannelPipeline,這個 Pipeline 中可以包含多個 Handler。通過 Pipeline 的鏈式結構,Netty 能夠靈活地控制事件在多個 Handler 之間的流轉,從而實現復雜、可擴展的網絡業務處理邏輯。
Netty 中的 Pipeline
????????在 Netty 中,ChannelPipeline 是每個 Channel 所獨有的組件,它是一個雙向鏈表結構,專門用于管理和組織多個 Handler 的執行順序。可以理解為,Pipeline 就是一條事件處理的通道,所有與該 Channel 相關的 IO 事件都會按照這條通道中 Handler 的順序依次傳遞與處理。
????????通過 ChannelPipeline,Netty 實現了對事件處理器(Handler)的鏈式管理,不僅支持事件的入站處理(Inbound),也支持事件的出站處理(Outbound)。事件在 Pipeline 中的傳遞方向由事件類型決定:入站事件會按照添加順序正向傳遞,出站事件則會反向傳遞。
????????這種設計帶來了高度的靈活性,使開發者能夠按需在不同位置添加、替換或刪除 Handler,從而精細控制事件處理流程,實現清晰、模塊化的通信邏輯。簡而言之,ChannelPipeline 是 Netty 中連接 Channel 與業務處理器之間的橋梁,也是事件流動和處理的主通道。
普通 Reactor 模式是事件 → 單個處理器(內部包含所有步驟),而 Netty 是事件 → 流水線(多個獨立 Handler 按順序協作)
入站/出站執行順序
1. IO 事件進入 Pipeline。
2. 沿著 Handler 鏈處理:
????????入站事件按前向順序觸發 InboundHandler。
????????出站事件按后向順序觸發 OutboundHandler。
尚未完結