《netty入门与实战》笔记-02:服务端启动流程
為什么80%的碼農(nóng)都做不了架構(gòu)師?>>> ??
1.服務(wù)端啟動(dòng)流程
這一小節(jié),我們來(lái)學(xué)習(xí)一下如何使用 Netty 來(lái)啟動(dòng)一個(gè)服務(wù)端應(yīng)用程序,以下是服務(wù)端啟動(dòng)的一個(gè)非常精簡(jiǎn)的 Demo:
NettyServer.java
public class NettyServer {public static void main(String[] args) {NioEventLoopGroup bossGroup = new NioEventLoopGroup();NioEventLoopGroup workerGroup = new NioEventLoopGroup();ServerBootstrap serverBootstrap = new ServerBootstrap();serverBootstrap.group(bossGroup, workerGroup).channel(NioServerSocketChannel.class).childHandler(new ChannelInitializer<NioSocketChannel>() {protected void initChannel(NioSocketChannel ch) {}});serverBootstrap.bind(8000);} }- 首先看到,我們創(chuàng)建了兩個(gè)NioEventLoopGroup,這兩個(gè)對(duì)象可以看做是傳統(tǒng)IO編程模型的兩大線程組,bossGroup表示監(jiān)聽端口,accept 新連接的線程組,workerGroup表示處理每一條連接的數(shù)據(jù)讀寫的線程組。用生活中的例子來(lái)講就是,一個(gè)工廠要運(yùn)作,必然要有一個(gè)老板負(fù)責(zé)從外面接活,然后有很多員工,負(fù)責(zé)具體干活,老板就是bossGroup,員工們就是workerGroup,bossGroup接收完連接,扔給workerGroup去處理。
- 接下來(lái) 我們創(chuàng)建了一個(gè)引導(dǎo)類 ServerBootstrap,這個(gè)類將引導(dǎo)我們進(jìn)行服務(wù)端的啟動(dòng)工作,直接new出來(lái)開搞。
- 我們通過(guò).group(bossGroup, workerGroup)給引導(dǎo)類配置兩大線程組,這個(gè)引導(dǎo)類的線程模型也就定型了。
- 然后,我們指定我們服務(wù)端的 IO 模型為NIO,我們通過(guò).channel(NioServerSocketChannel.class)來(lái)指定 IO 模型,當(dāng)然,這里也有其他的選擇,如果你想指定 IO 模型為 BIO,那么這里配置上OioServerSocketChannel.class類型即可,當(dāng)然通常我們也不會(huì)這么做,因?yàn)镹etty的優(yōu)勢(shì)就在于NIO。
- 接著,我們調(diào)用childHandler()方法,給這個(gè)引導(dǎo)類創(chuàng)建一個(gè)ChannelInitializer,這里主要就是定義后續(xù)每條連接的數(shù)據(jù)讀寫,業(yè)務(wù)處理邏輯,不理解沒關(guān)系,在后面我們會(huì)詳細(xì)分析。ChannelInitializer這個(gè)類中,我們注意到有一個(gè)泛型參數(shù)NioSocketChannel,這個(gè)類呢,就是 Netty 對(duì) NIO 類型的連接的抽象,而我們前面NioServerSocketChannel也是對(duì) NIO 類型的連接的抽象,NioServerSocketChannel和NioSocketChannel的概念可以和 BIO 編程模型中的ServerSocket以及Socket兩個(gè)概念對(duì)應(yīng)上
我們的最小化參數(shù)配置到這里就完成了,我們總結(jié)一下就是,要啟動(dòng)一個(gè)Netty服務(wù)端,必須要指定三類屬性,分別是線程模型、IO 模型、連接讀寫處理邏輯,有了這三者,之后在調(diào)用bind(8000),我們就可以在本地綁定一個(gè) 8000 端口啟動(dòng)起來(lái),以上這段代碼讀者可以直接拷貝到你的 IDE 中運(yùn)行。
2.自動(dòng)綁定遞增端口
在上面代碼中我們綁定了 8000 端口,接下來(lái)我們實(shí)現(xiàn)一個(gè)稍微復(fù)雜一點(diǎn)的邏輯,我們指定一個(gè)起始端口號(hào),比如 1000,然后呢,我們從1000號(hào)端口往上找一個(gè)端口,直到這個(gè)端口能夠綁定成功,比如 1000 端口不可用,我們就嘗試綁定 1001,然后 1002,依次類推。
serverBootstrap.bind(8000);這個(gè)方法呢,它是一個(gè)異步的方法,調(diào)用之后是立即返回的,他的返回值是一個(gè)ChannelFuture,我們可以給這個(gè)ChannelFuture添加一個(gè)監(jiān)聽器GenericFutureListener,然后我們?cè)贕enericFutureListener的operationComplete方法里面,我們可以監(jiān)聽端口是否綁定成功,接下來(lái)是監(jiān)測(cè)端口是否綁定成功的代碼片段
serverBootstrap.bind(8000).addListener(new GenericFutureListener<Future<? super Void>>() {public void operationComplete(Future<? super Void> future) {if (future.isSuccess()) {System.out.println("端口綁定成功!");} else {System.err.println("端口綁定失敗!");}} });我們接下來(lái)從 1000 端口號(hào),開始往上找端口號(hào),直到端口綁定成功,我們要做的就是在 if (future.isSuccess())的else邏輯里面重新綁定一個(gè)遞增的端口號(hào),接下來(lái),我們把這段綁定邏輯抽取出一個(gè)bind方法
private static void bind(final ServerBootstrap serverBootstrap, final int port) {serverBootstrap.bind(port).addListener(new GenericFutureListener<Future<? super Void>>() {public void operationComplete(Future<? super Void> future) {if (future.isSuccess()) {System.out.println("端口[" + port + "]綁定成功!");} else {System.err.println("端口[" + port + "]綁定失敗!");bind(serverBootstrap, port + 1);}}}); }然后呢,以上代碼中最關(guān)鍵的就是在端口綁定失敗之后,重新調(diào)用自身方法,并且把端口號(hào)加一,然后,在我們的主流程里面,我們就可以直接調(diào)用
bind(serverBootstrap, 1000)端口成功綁定了在1024,從 1000 開始到 1023,端口均綁定失敗了,這是因?yàn)樵谖业?MAC 系統(tǒng)下,1023 以下的端口號(hào)都是被系統(tǒng)保留了,需要 ROOT 權(quán)限才能綁定。
以上就是自動(dòng)綁定遞增端口的邏輯,接下來(lái),我們來(lái)一起學(xué)習(xí)一下,服務(wù)端啟動(dòng),我們的引導(dǎo)類ServerBootstrap除了指定線程模型,IO 模型,連接讀寫處理邏輯之外,他還可以干哪些事情?
3.服務(wù)端啟動(dòng)其他方法
handler() 方法
serverBootstrap.handler(new ChannelInitializer<NioServerSocketChannel>() {protected void initChannel(NioServerSocketChannel ch) {System.out.println("服務(wù)端啟動(dòng)中");} })handler()方法呢,可以和我們前面分析的childHandler()方法對(duì)應(yīng)起來(lái),childHandler()用于指定處理新連接數(shù)據(jù)的讀寫處理邏輯,handler()用于指定在服務(wù)端啟動(dòng)過(guò)程中的一些邏輯,通常情況下呢,我們用不著這個(gè)方法。
attr() 方法
serverBootstrap.attr(AttributeKey.newInstance("serverName"), "nettyServer")attr()方法可以給服務(wù)端的 channel,也就是NioServerSocketChannel指定一些自定義屬性,然后我們可以通過(guò)channel.attr()取出這個(gè)屬性,比如,上面的代碼我們指定我們服務(wù)端channel的一個(gè)serverName屬性,屬性值為nettyServer,其實(shí)說(shuō)白了就是給NioServerSocketChannel維護(hù)一個(gè)map而已,通常情況下,我們也用不上這個(gè)方法。
那么,當(dāng)然,除了可以給服務(wù)端 channel NioServerSocketChannel指定一些自定義屬性之外,我們還可以給每一條連接指定自定義屬性
childAttr() 方法
serverBootstrap.childAttr(AttributeKey.newInstance("clientKey"), "clientValue")上面的childAttr可以給每一條連接指定自定義屬性,然后后續(xù)我們可以通過(guò)channel.attr()取出該屬性。
childOption() 方法
serverBootstrap.childOption(ChannelOption.SO_KEEPALIVE, true).childOption(ChannelOption.TCP_NODELAY, true)childOption()可以給每條連接設(shè)置一些TCP底層相關(guān)的屬性,比如上面,我們?cè)O(shè)置了兩種TCP屬性,其中
- ChannelOption.SO_KEEPALIVE表示是否開啟TCP底層心跳機(jī)制,true為開啟
- ChannelOption.TCP_NODELAY表示是否開始Nagle算法,true表示關(guān)閉,false表示開啟,通俗地說(shuō),如果要求高實(shí)時(shí)性,有數(shù)據(jù)發(fā)送時(shí)就馬上發(fā)送,就關(guān)閉,如果需要減少發(fā)送次數(shù)減少網(wǎng)絡(luò)交互,就開啟。
其他的參數(shù)這里就不一一講解,有興趣的同學(xué)可以去這個(gè)類里面自行研究。
option() 方法
除了給每個(gè)連接設(shè)置這一系列屬性之外,我們還可以給服務(wù)端channel設(shè)置一些屬性,最常見的就是so_backlog,如下設(shè)置
serverBootstrap.option(ChannelOption.SO_BACKLOG, 1024)表示系統(tǒng)用于臨時(shí)存放已完成三次握手的請(qǐng)求的隊(duì)列的最大長(zhǎng)度,如果連接建立頻繁,服務(wù)器處理創(chuàng)建新連接較慢,可以適當(dāng)調(diào)大這個(gè)參數(shù)
4.總結(jié)
- 本文中,我們首先學(xué)習(xí)了 Netty 服務(wù)端啟動(dòng)的流程,一句話來(lái)說(shuō)就是:創(chuàng)建一個(gè)引導(dǎo)類,然后給他指定線程模型,IO模型,連接讀寫處理邏輯,綁定端口之后,服務(wù)端就啟動(dòng)起來(lái)了。
- 然后,我們學(xué)習(xí)到 bind 方法是異步的,我們可以通過(guò)這個(gè)異步機(jī)制來(lái)實(shí)現(xiàn)端口遞增綁定。
- 最后呢,我們討論了 Netty 服務(wù)端啟動(dòng)額外的參數(shù),主要包括給服務(wù)端 Channel 或者客戶端 Channel 設(shè)置屬性值,設(shè)置底層 TCP 參數(shù)。
以上內(nèi)容來(lái)源于掘金小冊(cè)《Netty 入門與實(shí)戰(zhàn):仿寫微信 IM 即時(shí)通訊系統(tǒng)》,若想獲得更多,更詳細(xì)的內(nèi)容,請(qǐng)用微信掃碼訂閱:
轉(zhuǎn)載于:https://my.oschina.net/funcy/blog/2242215
總結(jié)
以上是生活随笔為你收集整理的《netty入门与实战》笔记-02:服务端启动流程的全部?jī)?nèi)容,希望文章能夠幫你解決所遇到的問(wèn)題。
- 上一篇: 梦到隔壁着火是什么意思
- 下一篇: 一文详解java中对JVM的深度解析、调