本節筆記承接上一節《netty 學習筆記一》,介紹 netty 服務端、客戶端啓動流程中關注的一些要素。java
1 public void minimalServer() { 2 // 兩大線程組,boss線程組負責監聽端口、accept新鏈接;worker線程組負責處理鏈接上數據的讀寫 3 NioEventLoopGroup boss = new NioEventLoopGroup(); 4 NioEventLoopGroup worker = new NioEventLoopGroup(); 5 6 // 服務端引導類,直接 new 出來用 7 ServerBootstrap serverBootstrap = new ServerBootstrap(); 8 serverBootstrap 9 // 1.給引導類配置線程模型 10 .group(boss, worker) 11 // 2.指定服務端IO模型爲 NIO——若是要配置爲 BIO,能夠選擇 OioServerSocketChannel 12 .channel(NioServerSocketChannel.class) 13 // 3. IO處理邏輯 14 // 方法的 child 怎麼理解呢?這是由於底層 accept 返回的鏈接和監聽的端口會使用不一樣的端口,所以 childHandler 就是爲新鏈接的讀寫所設置的 handler 15 // 新鏈接中對應的泛型抽象是 NioSocketChannel, 該對象標識一條客戶端鏈接 16 .childHandler(new ChannelInitializer<NioSocketChannel>() { 17 @Override 18 protected void initChannel(NioSocketChannel ch) throws Exception { 19 20 } 21 }); 22 // 以上就是 netty 最小化參數配置,須要三類屬性:1.線程模型 2.IO模型 3.鏈接讀寫處理邏輯 23 // 有了這三類屬性,調用 ServerBootstrap#bind 方法就能夠在本地運行起來 24 25 /* 26 服務端啓動的其餘方法 27 */ 28 // handler方法和 childHandler方法對應起來。那麼 handler方法就對應服務器自身啓動時的一些邏輯 29 serverBootstrap.handler(new ChannelInitializer<NioServerSocketChannel>() { 30 @Override 31 protected void initChannel(NioServerSocketChannel ch) throws Exception { 32 System.out.println("服務器啓動中"); 33 } 34 }); 35 // attr方法能夠給服務端的 channel 也就是 NioServerSocketChannel 對象指定一些自定義屬性,說簡單點就是給它維護一個 map 36 // 經過 channel.attr() 取出屬性 37 serverBootstrap.attr(AttributeKey.newInstance("serverName"), "nettyServer"); 38 // childAttr方法 和 attr方法對應,那麼前者就是爲每一條新鏈接指定自定義屬性,依然經過 channel.attr() 取出屬性 39 serverBootstrap.childAttr(AttributeKey.newInstance("clientKey"), "clientValue"); 40 // 設置TCP底層相關的屬性,一樣也有兩個對應的方法:option() 和 childOption() 41 // 服務端最多見的TCP底層屬性,表示系統用於臨時存放已完成三次握手的請求的鏈接隊列的最大長度。若是鏈接創建頻繁,服務器處理建立新鏈接較慢,能夠適當調大這個參數 42 serverBootstrap.option(ChannelOption.SO_BACKLOG, 1024); 43 // 爲每一條新鏈接設置的 TCP底層屬性: 44 // SO_KEEPALIVE:是否使用心跳機制; 45 // TCP_NODELAY:nagle算法開關, TCP 用來處理 small packet problem 的算法,字面意思爲是否立馬發送。若是要求實時性高該值設爲 true,如 redis 就是這麼作的; 46 // 一般默認操做系統該值爲 false, 這容許 TCP 在未發送數據中最多能夠有一個未被確認的小分組, 47 // 舉例來講客戶端每次發送一個字節,將 HELLO 分五次發給服務端,客戶端在應用層會調用 5次send方法。 48 // Nagle 算法會將 HELLO 分爲 H 和 LLEO 兩個分組,即 H 發送出去時 LLEO 在 nagle算法處理下被合併,在 H 的 ACK回來以後 LLEO 才被髮出去,總共發送兩次 49 // 因而開啓 Nagle算法後應用層調用 5次send 實際在 TCP 層面只發了兩個包。若是 TCP_NODELAY 爲 true,即禁用 Nagle算法,那麼 HELLO 就會分紅 5個 TCP packet,每一個包都佔 41字節,比較浪費帶寬 50 serverBootstrap.childOption(ChannelOption.TCP_NODELAY, true) 51 .childOption(ChannelOption.SO_KEEPALIVE, true); 52 53 // 修改成自動綁定遞增端口 54 // serverBootstrap.bind(8000); 55 bind(serverBootstrap, 8000); 56 } 57 58 private void bind(ServerBootstrap serverBootstrap, int port) { 59 serverBootstrap.bind(port).addListener(future -> { 60 if (future.isSuccess()) { 61 System.out.println("成功綁定端口[" + port + "]"); 62 } else { 63 System.out.println("綁定端口[" + port + "]失敗"); 64 bind(serverBootstrap, port + 1); 65 } 66 }); 67 }
客戶端啓動流程代碼:https://github.com/christmad/code-share/blob/master/share-netty/src/main/java/code.share.netty/NettyClientBootstrap.javagithub
1 private static final int MAX_RETRY = 5; 2 3 public void minimalClient() { 4 NioEventLoopGroup worker = new NioEventLoopGroup(); 5 6 Bootstrap bootstrap = new Bootstrap(); 7 bootstrap 8 // 參考 NettyServerBootstrap, client啓動一樣也有幾個相似的流程 9 // 1.指定線程模型 10 .group(worker) 11 // 2.指定IO類型爲 NIO 12 .channel(NioSocketChannel.class) 13 // 3.IO處理邏輯 14 // 這裏的泛型官網demo用的也是 SocketChannel 接口, 實現類由上面的 channel() 方法指定 15 .handler(new ChannelInitializer<SocketChannel>() { 16 @Override 17 protected void initChannel(SocketChannel ch) throws Exception { 18 19 } 20 }); 21 /* 22 客戶端啓動的其餘方法 23 */ 24 // attr方法,給 NioSocketChannel 綁定屬性,經過 channel.attr() 取出屬性 25 bootstrap.attr(AttributeKey.newInstance("clientName"), "christmad-netty"); 26 // option方法,給客戶端鏈接設置TCP底層屬性. 27 bootstrap.option(ChannelOption.CONNECT_TIMEOUT_MILLIS, 5000) 28 .option(ChannelOption.SO_KEEPALIVE, true) 29 .option(ChannelOption.TCP_NODELAY, true); 30 31 32 // 4.創建鏈接——指數退避時間間隔的失敗重連 33 int port = 8000; 34 int retry = 5; 35 connect(bootstrap, port, retry); 36 } 37 38 // 爲重連設計一個指數退避的重連間隔 39 private void connect(Bootstrap bootstrap, int port, int retry) { 40 bootstrap.connect("localhost", 8000).addListener(future -> { 41 if (future.isSuccess()) { 42 System.out.println("鏈接成功"); 43 } else if (retry == 0) { 44 System.err.println("鏈接失敗,重連次數已用完,放棄鏈接!"); 45 } else { 46 // 第幾回重連 47 int order = MAX_RETRY - retry + 1; 48 // 本次重連間隔 49 int delay = 1 << order; 50 System.err.println(new Date() + ": 鏈接失敗,第" + order + "次重連......"); 51 // Bootstrap 的定時任務 52 bootstrap.config().group().schedule(() -> connect(bootstrap, port, retry - 1), delay, TimeUnit.SECONDS); 53 54 } 55 }); 56 }