Netty實戰 IM即時通訊系統(五)客戶端啟動流程
阿新 • • 發佈:2018-12-27
##
零、 目錄
- IM系統簡介
- Netty 簡介
- Netty 環境配置
- 服務端啟動流程
- 實戰: 客戶端和服務端雙向通訊
- 資料傳輸載體ByteBuf介紹
- 客戶端與服務端通訊協議編解碼
- 實現客戶端登入
- 實現客戶端與服務端收發訊息
- pipeline與channelHandler
- 構建客戶端與服務端pipeline
- 拆包粘包理論與解決方案
- channelHandler的生命週期
- 使用channelHandler的熱插拔實現客戶端身份校驗
- 客戶端互聊原理與實現
- 群聊的發起與通知
- 群聊的成員管理(加入與退出,獲取成員列表)
- 群聊訊息的收發及Netty效能優化
- 心跳與空閒檢測
- 總結
- 擴充套件
五、 客戶端啟動流程
-
客戶端啟動demo
/** * 客戶端啟動流程 * */ public class Test_05_客戶端啟動流程 { public static void main(String[] args) { NioEventLoopGroup workerGroup = new NioEventLoopGroup(); Bootstrap bootstrap = new Bootstrap(); bootstrap // 指定執行緒模型 .group(workerGroup) // 指定IO 模型 .channel(NioSocketChannel.class) // 指定業務處理邏輯 .handler(new ChannelInitializer<NioSocketChannel>() { @Override protected void initChannel(NioSocketChannel ch) throws Exception { } }); // 建立連線 bootstrap.connect("127.0.0.1" , 8000) .addListener(future ->{ if(future.isSuccess()) { System.out.println("連線成功"); }else { System.out.println("連線失敗"); } }); } }
- 從上面的程式碼可以看出 , 客戶端的啟動引導類是BootStrap , 負責啟動客戶端以及連線服務端 , 而上面一小節我們在描述服務端啟動的時候, 這個引導類是 ServerBootStrap , 引導類建立完成之後我們描述一下客戶端啟動流程:
- 首先和服務端的啟動流程一樣 , 我們需要給特指定執行緒模型, 驅動著連線的資料讀寫
- 然後我們指定IO 模型為 NioSocketChannel , 表示IO模型為 NIO
- 接著, 我們給引導類指定一個handler , 這裡主要就是定義連線的業務處理邏輯 , 不理解沒有關係 , 我們在後面會詳解
- 配置完執行緒模型 , IO 模型 , 業務處理邏輯之後 , 呼叫connect() 方法進行連線 , 可以看到connect() 方法有兩個引數 , 第一個引數可以填寫IP或域名 ,第二個引數填寫的是埠號 , 由於connect() 方法返回的是一個Future , 也就是說這個方法是非同步的 , 我們通過addListener方法可以監聽到連線是否成功 , 進而列印連線狀態
- 到這裡一個客戶端的demo 就完成了 , 其實只要和客戶端Socket 程式設計模型對應起來 , 這裡的三個概念就會顯得非常簡單
- 從上面的程式碼可以看出 , 客戶端的啟動引導類是BootStrap , 負責啟動客戶端以及連線服務端 , 而上面一小節我們在描述服務端啟動的時候, 這個引導類是 ServerBootStrap , 引導類建立完成之後我們描述一下客戶端啟動流程:
-
失敗重連
-
在網路差的情況下 , 客戶端第一次連線可能會失敗 , 這個時候我們可能會嘗試重新連線 , 重新連線的邏輯寫在連線失敗的邏輯塊裡
// 建立連線 bootstrap.connect("127.0.0.1" , 8000) .addListener(future ->{ if(future.isSuccess()) { System.out.println("連線成功"); }else { System.out.println("連線失敗"); //TODO: 重新連線邏輯 } });
-
重新連線時依然是呼叫相同的邏輯 , 所以我們把連線的程式碼抽取出來, 實現程式碼複用 , 在連線失敗的情況下使用遞迴的方法 實現重連
public static void connect(Bootstrap bootstrap, String IP, int port) { // 建立連線 bootstrap.connect(IP, port).addListener(future -> { if (future.isSuccess()) { System.out.println("連線成功"); } else { System.out.println("連線失敗,執行重連"); connect(bootstrap, IP, port); } }); }
-
以上程式碼就實現了重連機制 , 但是在通常情況下連線失敗不會立即重連 , 而是通過一個指數退避的方式 , 比如 每隔1秒、2秒、4秒、8秒 , 以2的次冪來實現建立連線 , 然後到達一定次數之後就放棄重連
connect(bootstrap , "127.0.0.1" , 8000 , 5); public static void connect(Bootstrap bootstrap, String IP, int port ,int maxRetry , int... retryIndex) { // 建立連線 bootstrap.connect(IP, port).addListener(future -> { // 由於閉包特性 不能修改外部的變數 所有需要在閉包內定義一個相同的變數 拷貝外部變數的值 int[] finalRetryIndex ; if (future.isSuccess()) { System.out.println("連線成功"); } else if(maxRetry == 0){ System.out.println("到達重試最大次數,放棄重連"); }else { // 初始化 重試計數 if(retryIndex.length == 0) { finalRetryIndex = new int[] {0}; }else { finalRetryIndex = retryIndex; } //計算時間間隔 int delay = 1 << finalRetryIndex[0]; // 執行重試 System.out.println(new Date()+"連線失敗,剩餘重連次數:"+maxRetry+","+delay+"秒後執行第"+(finalRetryIndex[0]+1)+"次重連..."); bootstrap.config().group().schedule(()->{ connect(bootstrap, IP, port , maxRetry-1 , finalRetryIndex[0]+1); }, delay, TimeUnit.SECONDS); } }); } 執行結果: Thu Dec 27 11:04:19 CST 2018連線失敗,剩餘重連次數:5,1秒後執行第1次重連... Thu Dec 27 11:04:21 CST 2018連線失敗,剩餘重連次數:4,2秒後執行第2次重連... Thu Dec 27 11:04:24 CST 2018連線失敗,剩餘重連次數:3,4秒後執行第3次重連... Thu Dec 27 11:04:29 CST 2018連線失敗,剩餘重連次數:2,8秒後執行第4次重連... Thu Dec 27 11:04:38 CST 2018連線失敗,剩餘重連次數:1,16秒後執行第5次重連... 到達重試最大次數,放棄重連
- 從上面的程式碼中我們可以看到 , 通過判斷是否連線成功以及剩餘重試次數 , 分別執行不同的邏輯
- 如果連線成功則列印連線成功的訊息
- 如果連線失敗 , 但是重試次數已經用完則放棄連線
- 如果連線失敗 , 但是連線沒有用完則計算下一次重試時間間隔 , 然後定時重連
- 從上面程式碼中我們可以看到 , 定時任務是呼叫bootstrap.config().group().schedule() , 其中bootStrap.config()這個方法返回的是BootStrapConfig , 他是對BootStrap 引數配置的抽象 , 然後ootstrap.config().group() 返回的就是我們一開始設定的執行緒模型workerGroup , 最後呼叫schedule() 方法就可以實現定時任務邏輯了。
- 從上面的程式碼中我們可以看到 , 通過判斷是否連線成功以及剩餘重試次數 , 分別執行不同的邏輯
-
-
-
客戶端啟動其他方法
-
attr(): attr()方法可以給客戶端channel也就是NioSocketChannel繫結自定義屬性 , 然後我們通過channel.attr()取出屬性。 說白了就是給NioSocketChannel維護一個Map 而已
//設定屬性 bootstrap.attr(AttributeKey.newInstance("clientName"), "NettyClient"); //業務邏輯 bootstrap.handler(new ChannelInitializer<NioSocketChannel>() { @Override protected void initChannel(NioSocketChannel ch) throws Exception { // 取出屬性 Attribute<Object> attr = ch.attr(AttributeKey.valueOf("clientName")); System.out.println("客戶端名稱:"+attr.get()); } });
-
option(): option()可以給連線設定一些TCP底層的相關屬性 : (ChannelOption相關引數詳解在 上一節《服務端啟動流程》中有連線地址)
// 設定TCP 相關的屬性 // 設定連線超時時間 bootstrap.option(ChannelOption.CONNECT_TIMEOUT_MILLIS, 5000); // 開啟TCP 心跳機制 bootstrap.option(ChannelOption.SO_KEEPALIVE, true);
-
-
總結:
- 在本節中我們學習了Netty客戶端啟動的流程 , 一句話來說就是: 建立一個引導類 , 然後給他指定執行緒 , IO模型 , 指定業務邏輯 ,連線特定的IP:port 客戶端就啟動起來了
- 然後我們學習到 connect()方法時非同步的 , 我們可以通過非同步回撥機制來實現指數退避重連機制。
- 最後我們討論了Netty客戶端啟動的額外引數 , 只要包括給客戶端Channel 繫結自定義屬性 , 設定TCP底層引數。
-
疑問:
- 客戶端Channel設定的attr是否會被服務端接收到,並且以此進行必要的引數傳遞?
- 答: 客戶端Channel 會被服務端接收到。
- 客戶端Channel設定的attr是否會被服務端接收到,並且以此進行必要的引數傳遞?