【Netty】Netty 入门案例分析 ( Netty 模型解析 | Netty 服务器端代码 | Netty 客户端代码 )
文章目錄
- 一、 Netty 模型代碼解析
- 二、 Netty 案例服務器端代碼
- 1 . 服務器主程序
- 2 . 服務器自定義 Handler 處理者
- 三、 Netty 案例客戶端代碼
- 1 . 客戶端主程序
- 2 . 客戶端自定義 Handler 處理者
- 四、 Netty 案例運行
一、 Netty 模型代碼解析
1 . 線程池 NioEventLoopGroup :
① NioEventLoopGroup 線程池使用場景 : Netty 模型中的 BossGroup 和 WorkerGroup 都是 NioEventLoopGroup 類型的線程池 ;
② NioEventLoopGroup 默認線程個數 : 系統默認每個線程池中的 NioEventLoop 線程數是 CPU 核數 ×\times× 2 , 下面的代碼可以獲取運行 Netty 程序的設備的 CPU 核數 ;
// 獲取設備的 CPU 核數 NettyRuntime.availableProcessors()③ 指定 NioEventLoopGroup 線程個數 : 如果不想使用 Netty 線程池的默認線程個數 , 可以在 NioEventLoopGroup 構造函數中子星設定線程數 ;
// BossGroup 線程池 : 負責客戶端的連接 // 指定線程個數 : 客戶端個數很少, 不用很多線程維護, 這里指定線程池中線程個數為 1 EventLoopGroup bossGroup = new NioEventLoopGroup(1);2 . NioEventLoopGroup 線程池線程分配 :
以客戶端連接完成后 , 數據讀寫場景舉例 ;
在 雙核 CPU 的服務器上 , NioEventLoopGroup 默認有 444 個線程 ; 按照順序循環分配 , 為第 nnn 個客戶端分配第 n%4n \% 4n%4 個 NioEventLoop 線程 ;
- 客戶端 000 與服務器進行數據交互在 NioEventLoop 000 線程中 ;
- 客戶端 111 與服務器進行數據交互在 NioEventLoop 111 線程中 ;
- 客戶端 222 與服務器進行數據交互在 NioEventLoop 222 線程中 ;
- 客戶端 333 與服務器進行數據交互在 NioEventLoop 333 線程中 ;
- 客戶端 444 與服務器進行數據交互在 NioEventLoop 000 線程中 ;
- 客戶端 555 與服務器進行數據交互在 NioEventLoop 111 線程中 ;
- 客戶端 666 與服務器進行數據交互在 NioEventLoop 222 線程中 ;
- 客戶端 777 與服務器進行數據交互在 NioEventLoop 333 線程中 ;
3 . NioEventLoopGroup 線程池封裝內容 :
① NioEventLoopGroup 中的若干個 NioEventLoop 線程都封裝在 children 中 , 線程個數是 CPU 核數 2 倍 ;
② 每個 NioEventLoop 線程中封裝了如下內容 :
- 選擇器 ( Selector ) , 用于監聽客戶端的讀寫 IO 事件 ;
- 任務隊列 ( taskQueue ) , 用于存儲事件對應的業務邏輯任務 ;
- 線程執行器 ( executor ) , 用于執行線程 ;
4 . ChannelHandlerContext 通道處理者上下文對象封裝內容 :
① 用戶自定義的 處理者 ( Handler ) , 這里指的是 服務器端的 ServerHandr ( 自定義 ) , 客戶端的 ClientHandler ( 自定義 ) ;
② 管道 ( ChannelPipeline ) : 其本質是雙向鏈表 , 該 ChannelHandlerContext 可以獲取該鏈表的前一個 ( prev ) , 后一個管道對象 ( next ) ;
③ 管道 與 通道 :
- 二者都可以通過 通道處理者上下文 ( ChannelHandlerContext ) 獲取 ;
- 管道 與 通道 都可以互相從對方獲取 ;
④ 管道 ( Pipeline ) 與 通道 ( Channel ) 關聯 : 通過管道可以獲取通道 , 通過通道也可以獲取其對應的管道 ;
5 . 處理者 ( Handler ) :
① 設置 Handler : 給 WorkerGroup 線程池中的 EventLoop 線程對應的管道設置 處理器 ( Handler ) ;
② 自定義 Handler : 一般這個 Handler 都是用戶自定義的類 , 繼承 ChannelInboundHandlerAdapter 類 ;
③ 運行機制 : 在 BossGroup 中連接客戶端成功后 , 將 NioSocketChannel 注冊給 WorkerGroup 中的 EventLoop 中的 選擇器 ( Selector ) , 如果監聽到客戶端數據 IO 事件 , 就會調用 管道 ( Pipeline ) 處理該事件 , 管道 ( Pipeline ) 中調用 處理器 ( Handler ) 處理相應的事件 , 該 處理器 ( Handler ) 可以是 Netty 提供的 , 也可以是開發者自定義的 ;
特別注意 : 自定義 Handler 中 , 重寫的 ChannelInboundHandlerAdapter 方法 , 將 super() 語句都刪除 ;
二、 Netty 案例服務器端代碼
1 . 服務器主程序
package kim.hsl.netty;import io.netty.bootstrap.ServerBootstrap; import io.netty.channel.ChannelFuture; import io.netty.channel.ChannelInitializer; import io.netty.channel.ChannelOption; import io.netty.channel.EventLoopGroup; import io.netty.channel.nio.NioEventLoopGroup; import io.netty.channel.socket.SocketChannel; import io.netty.channel.socket.nio.NioServerSocketChannel;/*** Netty 案例服務器端*/ public class Server {public static void main(String[] args) {// 1. 創建 BossGroup 線程池 和 WorkerGroup 線程池, 其中維護 NioEventLoop 線程// NioEventLoop 線程中執行無限循環操作// BossGroup 線程池 : 負責客戶端的連接// 指定線程個數 : 客戶端個數很少, 不用很多線程維護, 這里指定線程池中線程個數為 1EventLoopGroup bossGroup = new NioEventLoopGroup(1);// WorkerGroup 線程池 : 負責客戶端連接的數據讀寫EventLoopGroup workerGroup = new NioEventLoopGroup();// 2. 服務器啟動對象, 需要為該對象配置各種參數ServerBootstrap bootstrap = new ServerBootstrap();bootstrap.group(bossGroup, workerGroup) // 設置 主從 線程組 , 分別對應 主 Reactor 和 從 Reactor.channel(NioServerSocketChannel.class) // 設置 NIO 網絡套接字通道類型.option(ChannelOption.SO_BACKLOG, 128) // 設置線程隊列維護的連接個數.childOption(ChannelOption.SO_KEEPALIVE, true) // 設置連接狀態行為, 保持連接狀態.childHandler( // 為 WorkerGroup 線程池對應的 NioEventLoop 設置對應的事件 處理器 Handlernew ChannelInitializer<SocketChannel>() {// 創建通道初始化對象@Overrideprotected void initChannel(SocketChannel ch) throws Exception {// 為 管道 Pipeline 設置處理器 Hanedlerch.pipeline().addLast(new ServerHandr());}});System.out.println("服務器準備完畢 ...");ChannelFuture cf = null;try {// 綁定本地端口, 進行同步操作 , 并返回 ChannelFuturecf = bootstrap.bind(8888).sync();System.out.println("服務器開始監聽 8888 端口 ...");// 關閉通道 , 開始監聽操作cf.channel().closeFuture().sync();} catch (InterruptedException e) {e.printStackTrace();} finally {// 出現異常后, 優雅的關閉bossGroup.shutdownGracefully();workerGroup.shutdownGracefully();}} }2 . 服務器自定義 Handler 處理者
package kim.hsl.netty;import io.netty.buffer.ByteBuf; import io.netty.buffer.Unpooled; import io.netty.channel.Channel; import io.netty.channel.ChannelHandlerContext; import io.netty.channel.ChannelInboundHandlerAdapter; import io.netty.channel.ChannelPipeline; import io.netty.util.CharsetUtil;/*** Handler 處理者, 是 NioEventLoop 線程中處理業務邏輯的類** 繼承 : 該業務邏輯處理者 ( Handler ) 必須繼承 Netty 中的 ChannelInboundHandlerAdapter 類* 才可以設置給 NioEventLoop 線程** 規范 : 該 Handler 類中需要按照業務邏輯處理規范進行開發*/ public class ServerHandr extends ChannelInboundHandlerAdapter {/*** 讀取數據 : 在服務器端讀取客戶端發送的數據* @param ctx* 通道處理者上下文對象 : 封裝了 管道 ( Pipeline ) , 通道 ( Channel ), 客戶端地址信息* 管道 ( Pipeline ) : 注重業務邏輯處理 , 可以關聯很多 Handler* 通道 ( Channel ) : 注重數據讀寫* @param msg* 客戶端上傳的數據* @throws Exception*/@Overridepublic void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {// 查看 ChannelHandlerContext 中封裝的內容System.out.println("channelRead : ChannelHandlerContext ctx = " + ctx);// 將客戶端上傳的數據轉為 ByteBuffer// 這里注意該類是 Netty 中的 io.netty.buffer.ByteBuf 類// 不是 NIO 中的 ByteBuffer// io.netty.buffer.ByteBuf 性能高于 java.nio.ByteBufferByteBuf byteBuf = (ByteBuf) msg;// 將 ByteBuf 緩沖區數據轉為字符串, 打印出來System.out.println(ctx.channel().remoteAddress() + " 接收到客戶端發送的數據 : " + byteBuf.toString(CharsetUtil.UTF_8));}/*** 服務器端讀取數據完畢后回調的方法* @param ctx* 通道處理者上下文對象 : 封裝了 管道 ( Pipeline ) , 通道 ( Channel ), 客戶端地址信息* * 管道 ( Pipeline ) : 注重業務邏輯處理 , 可以關聯很多 Handler* * 通道 ( Channel ) : 注重數據讀寫* @throws Exception*/@Overridepublic void channelReadComplete(ChannelHandlerContext ctx) throws Exception {// 數據編碼 : 將字符串編碼, 存儲到 io.netty.buffer.ByteBuf 緩沖區中ByteBuf byteBuf = Unpooled.copiedBuffer("Hello Client", CharsetUtil.UTF_8);// 寫出并刷新操作 : 寫出數據到通道的緩沖區 ( write ), 并執行刷新操作 ( flush )ctx.writeAndFlush(byteBuf);}/*** 異常處理 , 上面的方法中都拋出了 Exception 異常, 在該方法中進行異常處理* @param ctx* @param cause* @throws Exception*/@Overridepublic void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {System.out.println("通道異常, 關閉通道");//如果出現異常, 就關閉該通道ctx.close();} }三、 Netty 案例客戶端代碼
1 . 客戶端主程序
package kim.hsl.netty;import io.netty.bootstrap.Bootstrap; import io.netty.channel.ChannelFuture; import io.netty.channel.ChannelInitializer; import io.netty.channel.EventLoopGroup; import io.netty.channel.nio.NioEventLoopGroup; import io.netty.channel.socket.SocketChannel; import io.netty.channel.socket.nio.NioSocketChannel;public class Client {public static void main(String[] args) {// 客戶端只需要一個 時間循環組 , 即 NioEventLoopGroup 線程池EventLoopGroup eventLoopGroup = new NioEventLoopGroup();// 客戶端啟動對象Bootstrap bootstrap = new Bootstrap();// 設置相關參數bootstrap.group(eventLoopGroup) // 設置客戶端的線程池.channel(NioSocketChannel.class) // 設置客戶端網絡套接字通道類型.handler( // 設置客戶端的線程池對應的 NioEventLoop 設置對應的事件處理器 Handlernew ChannelInitializer<SocketChannel>() {@Overrideprotected void initChannel(SocketChannel ch) throws Exception {ch.pipeline().addLast(new ClientHandr());}});try {// 開始連接服務器, 并進行同步操作// ChannelFuture 類分析 , Netty 異步模型// sync 作用是該方法不會再次阻塞ChannelFuture channelFuture = bootstrap.connect("127.0.0.1", 8888).sync();System.out.println("客戶端連接服務器成功 ...");// 關閉通道, 開始監聽channelFuture.channel().closeFuture().sync();} catch (InterruptedException e) {e.printStackTrace();}finally {// 優雅的關閉eventLoopGroup.shutdownGracefully();}} }2 . 客戶端自定義 Handler 處理者
package kim.hsl.netty;import io.netty.buffer.ByteBuf; import io.netty.buffer.Unpooled; import io.netty.channel.ChannelHandlerContext; import io.netty.channel.ChannelInboundHandlerAdapter; import io.netty.util.CharsetUtil;/*** Handler 處理者, 是 NioEventLoop 線程中處理業務邏輯的類** 繼承 : 該業務邏輯處理者 ( Handler ) 必須繼承 Netty 中的 ChannelInboundHandlerAdapter 類* 才可以設置給 NioEventLoop 線程** 規范 : 該 Handler 類中需要按照業務邏輯處理規范進行開發*/ public class ClientHandr extends ChannelInboundHandlerAdapter {/*** 通道就緒后觸發該方法* @param ctx* @throws Exception*/@Overridepublic void channelActive(ChannelHandlerContext ctx) throws Exception {// 查看 ChannelHandlerContext 中封裝的內容System.out.println("channelActive : ChannelHandlerContext ctx = " + ctx);// 數據編碼 : 將字符串編碼, 存儲到 io.netty.buffer.ByteBuf 緩沖區中ByteBuf byteBuf = Unpooled.copiedBuffer("Hello Server", CharsetUtil.UTF_8);// 寫出并刷新操作 : 寫出數據到通道的緩沖區 ( write ), 并執行刷新操作 ( flush )ctx.writeAndFlush(byteBuf);System.out.println("客戶端向服務器端發送 Hello Server 成功");}/*** 讀取數據 : 在服務器端讀取客戶端發送的數據* @param ctx* 通道處理者上下文對象 : 封裝了 管道 ( Pipeline ) , 通道 ( Channel ), 客戶端地址信息* 管道 ( Pipeline ) : 注重業務邏輯處理 , 可以關聯很多 Handler* 通道 ( Channel ) : 注重數據讀寫* @param msg* 服務器返回的數據* @throws Exception*/@Overridepublic void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {// 查看 ChannelHandlerContext 中封裝的內容System.out.println("channelRead : ChannelHandlerContext ctx = " + ctx);// 將服務器下發的數據轉為 ByteBuffer// 這里注意該類是 Netty 中的 io.netty.buffer.ByteBuf 類// 不是 NIO 中的 ByteBuffer// io.netty.buffer.ByteBuf 性能高于 java.nio.ByteBufferByteBuf byteBuf = (ByteBuf) msg;// 將 ByteBuf 緩沖區數據轉為字符串, 打印出來System.out.println(ctx.channel().remoteAddress() + " 服務器返回的數據 : " + byteBuf.toString(CharsetUtil.UTF_8));}/*** 異常處理 , 上面的方法中都拋出了 Exception 異常, 在該方法中進行異常處理* @param ctx* @param cause* @throws Exception*/@Overridepublic void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {System.out.println("通道異常, 關閉通道");//如果出現異常, 就關閉該通道ctx.close();} }四、 Netty 案例運行
1 . 運行服務器端 : 服務器啟動 , 監聽 8888 端口 ;
2 . 運行客戶端 : 客戶端連接服務器的 8888 端口 , 并向服務器端寫出 Hello Server 字符串 , 之后便接到服務器端回送的 Hello Client 字符串信息 ;
3 . 查看客戶端 : 服務器端接收到客戶端信息 , 向客戶端寫出 Hello Client 字符串 ;
總結
以上是生活随笔為你收集整理的【Netty】Netty 入门案例分析 ( Netty 模型解析 | Netty 服务器端代码 | Netty 客户端代码 )的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 【Netty】Netty 入门案例分析
- 下一篇: 【Netty】 异步任务调度 ( Tas