1. 程式人生 > 實用技巧 >netty之微信-客戶端啟動流程(六)

netty之微信-客戶端啟動流程(六)

客戶端啟動流程
上一小節,我們已經學習了 Netty 服務端啟動的流程,這一小節,我們來學習一下 Netty 客戶端的啟動流程。原始碼

客戶端啟動 Demo
對於客戶端的啟動來說,和服務端的啟動類似,依然需要執行緒模型、IO 模型,以及 IO 業務處理邏輯三大引數,下面,我們來看一下客戶端啟動的標準流程NettyClient.java

public class NettyClient {
    public static void main(String[] args) {
        NioEventLoopGroup workerGroup = new NioEventLoopGroup();
        
        Bootstrap bootstrap 
= new Bootstrap(); bootstrap // 1.指定執行緒模型 .group(workerGroup) // 2.指定 IO 型別為 NIO .channel(NioSocketChannel.class) // 3.IO 處理邏輯 .handler(new ChannelInitializer<SocketChannel>() { @Override
public void initChannel(SocketChannel ch) { } }); // 4.建立連線 bootstrap.connect("juejin.im", 80).addListener(future -> { if (future.isSuccess()) { System.out.println("連線成功!"); } else { System.err.println(
"連線失敗!"); } }); } }

從上面程式碼可以看到,客戶端啟動的引導類是Bootstrap,負責啟動客戶端以及連線服務端,而上一小節我們在描述服務端的啟動的時候,這個輔導類是ServerBootstrap,引導類建立完成之後,下面我們描述一下客戶端啟動的流程

首先,與服務端的啟動一樣,我們需要給它指定執行緒模型,驅動著連線的資料讀寫,這個執行緒的概念可以和第一小節Netty是什麼中的IOClient.java建立的執行緒聯絡起來
然後,我們指定 IO 模型為NioSocketChannel,表示 IO 模型為 NIO,當然,你可以可以設定 IO 模型為OioSocketChannel,但是通常不會這麼做,因為 Netty 的優勢在於 NIO
接著,給引導類指定一個handler,這裡主要就是定義連線的業務處理邏輯,不理解沒關係,在後面我們會詳細分析
配置完執行緒模型、IO 模型、業務處理邏輯之後,呼叫connect方法進行連線,可以看到connect方法有兩個引數,第一個引數可以填寫 IP 或者域名,第二個引數填寫的是埠號,由於connect方法返回的是一個Future,也就是說這個方是非同步的,我們通過addListener方法可以監聽到連線是否成功,進而打印出連線資訊
到了這裡,一個客戶端的啟動的 Demo 就完成了,其實只要和 客戶端 Socket 程式設計模型對應起來,這裡的三個概念就會顯得非常簡單,遺忘掉的同學可以回顧一下Netty是什麼中的IOClient.java再回來看這裡的啟動流程哦

失敗重連
在網路情況差的情況下,客戶端第一次連線可能會連線失敗,這個時候我們可能會嘗試重新連線,重新連線的邏輯寫在連線失敗的邏輯塊裡

bootstrap.connect("juejin.im", 80).addListener(future -> {
    if (future.isSuccess()) {
        System.out.println("連線成功!");
    } else {
        System.err.println("連線失敗!");
        // 重新連線
    }
});

重新連線的時候,依然是呼叫一樣的邏輯,因此,我們把建立連線的邏輯先抽取出來,然後在重連失敗的時候,遞迴呼叫自身

private static void connect(Bootstrap bootstrap, String host, int port) {
    bootstrap.connect(host, port).addListener(future -> {
        if (future.isSuccess()) {
            System.out.println("連線成功!");
        } else {
            System.err.println("連線失敗,開始重連");
            connect(bootstrap, host, port);
        }
    });
}

上面這一段便是帶有自動重連功能的邏輯,可以看到在連線建立失敗的時候,會呼叫自身進行重連。

但是,通常情況下,連線建立失敗不會立即重新連線,而是會通過一個指數退避的方式,比如每隔 1 秒、2 秒、4 秒、8 秒,以 2 的冪次來建立連線,然後到達一定次數之後就放棄連線,接下來我們就來實現一下這段邏輯,我們預設重試 5 次

connect(bootstrap, "juejin.im", 80, MAX_RETRY);
 
private static void connect(Bootstrap bootstrap, String host, int port, int retry) {
    bootstrap.connect(host, port).addListener(future -> {
        if (future.isSuccess()) {
            System.out.println("連線成功!");
        } else if (retry == 0) {
            System.err.println("重試次數已用完,放棄連線!");
        } else {
            // 第幾次重連
            int order = (MAX_RETRY - retry) + 1;
            // 本次重連的間隔
            int delay = 1 << order;
            System.err.println(new Date() + ": 連線失敗,第" + order + "次重連……");
            bootstrap.config().group().schedule(() -> connect(bootstrap, host, port, retry - 1), delay, TimeUnit
                    .SECONDS);
        }
    });
}

從上面的程式碼可以看到,通過判斷連線是否成功以及剩餘重試次數,分別執行不同的邏輯

如果連線成功則列印連線成功的訊息
如果連線失敗但是重試次數已經用完,放棄連線
如果連線失敗但是重試次數仍然沒有用完,則計算下一次重連間隔delay,然後定期重連
在上面的程式碼中,我們看到,我們定時任務是呼叫bootstrap.config().group().schedule(), 其中bootstrap.config()這個方法返回的是BootstrapConfig,他是對Bootstrap配置引數的抽象,然後bootstrap.config().group()返回的就是我們在一開始的時候配置的執行緒模型workerGroup,調workerGroup的schedule方法即可實現定時任務邏輯。

在schedule方法塊裡面,前面四個引數我們原封不動地傳遞,最後一個重試次數引數減掉一,就是下一次建立連線時候的上下文資訊。讀者可以自行修改程式碼,更改到一個連線不上的服務端 Host 或者 Port,檢視控制檯日誌就可以看到5次重連日誌。

以上就是實現指數退避的客戶端重連邏輯,接下來,我們來一起學習一下,客戶端啟動,我們的引導類Bootstrap除了指定執行緒模型,IO 模型,連線讀寫處理邏輯之外,他還可以幹哪些事情?

客戶端啟動其他方法
attr() 方法

bootstrap.attr(AttributeKey.newInstance("clientName"), "nettyClient")

attr()方法可以給客戶端 Channel,也就是NioSocketChannel繫結自定義屬性,然後我們可以通過channel.attr()取出這個屬性,比如,上面的程式碼我們指定我們客戶端 Channel 的一個clientName屬性,屬性值為nettyClient,其實說白了就是給NioSocketChannel維護一個 map 而已,後續在這個NioSocketChannel通過引數傳來傳去的時候,就可以通過他來取出設定的屬性,非常方便。

option() 方法

Bootstrap
        .option(ChannelOption.CONNECT_TIMEOUT_MILLIS, 5000)
        .option(ChannelOption.SO_KEEPALIVE, true)
        .option(ChannelOption.TCP_NODELAY, true)

option()方法可以給連線設定一些 TCP 底層相關的屬性,比如上面,我們設定了三種 TCP 屬性,其中

ChannelOption.CONNECT_TIMEOUT_MILLIS表示連線的超時時間,超過這個時間還是建立不上的話則代表連線失敗
ChannelOption.SO_KEEPALIVE表示是否開啟 TCP 底層心跳機制,true 為開啟
ChannelOption.TCP_NODELAY表示是否開始 Nagle 演算法,true 表示關閉,false 表示開啟,通俗地說,如果要求高實時性,有資料傳送時就馬上傳送,就設定為 true 關閉,如果需要減少傳送次數減少網路互動,就設定為 false 開啟
其他的引數這裡就不一一講解,有興趣的同學可以去這個類裡面自行研究。

總結
本文中,我們首先學習了 Netty 客戶端啟動的流程,一句話來說就是:建立一個引導類,然後給他指定執行緒模型,IO 模型,連線讀寫處理邏輯,連線上特定主機和埠,客戶端就啟動起來了。
然後,我們學習到connect方法是非同步的,我們可以通過這個非同步回撥機制來實現指數退避重連邏輯。
最後呢,我們討論了 Netty 客戶端啟動額外的引數,主要包括給客戶端 Channel 繫結自定義屬性值,設定底層 TCP 引數。