天天看點

揭秘Netty之——快速啟動nettyTCP握手過程服務端啟動用戶端啟動下回分解

netty作為當今最為流行的網絡通信架構之一,包括RPC、消息等中間件都在用,很有必要深入研究一下。下面從netty的快速啟動開始分析。

netty的啟動包括服務端的啟動與用戶端的啟動,入口主要是ServerBootstrap、Bootstrap。ServerBootstrap啟動服務端,Bootstrap啟動用戶端。服務端的啟動比較複雜,用戶端啟動相對簡單。

TCP握手過程

啟動的過程其實就是建立網絡連接配接的過程,是以首先得回顧一下TCP的三次握手和4次揮手。

建立連接配接的過程

揭秘Netty之——快速啟動nettyTCP握手過程服務端啟動用戶端啟動下回分解

斷開連接配接的過程

揭秘Netty之——快速啟動nettyTCP握手過程服務端啟動用戶端啟動下回分解

1)如果建立連接配接後,用戶端發生了故障,怎麼辦?

如果服務端發生故障,很簡單所有的連接配接都會被動斷開,用戶端可以定時重試連接配接。

如果用戶端發生故障,因為連接配接越多,服務端的壓力也會越大,是以服務端不會允許無用的連結存在占用資源。這就涉及到 一個探活器(心跳設計)。服務端會定時發送心跳包到用戶端,如果連續幾次都沒有回應,則認為該連結沒意義,服務端會主動斷開該連接配接。

服務端啟動

服務端的啟動從ServerBootstrap說起,這個類是用的流式設計,友善配置各種資訊,先看一段簡易的代碼如下。

public static void main(String[] args) throws InterruptedException {
        ServerBootstrap serverBootstrap = new ServerBootstrap();

        NioEventLoopGroup boss = new NioEventLoopGroup(2);
        NioEventLoopGroup worker = new NioEventLoopGroup(5);

        serverBootstrap.channel(NioServerSocketChannel.class)
                .group(boss, worker)
                .option(ChannelOption.SO_BACKLOG, 5)
                .childOption(ChannelOption.SO_KEEPALIVE, true)
                .childHandler(new ChannelInitializer<NioSocketChannel>() {
                    @Override
                    protected void initChannel(NioSocketChannel ch) throws Exception {
                        ch.pipeline().addLast(new DelimiterBasedFrameDecoder(8192, Delimiters.lineDelimiter()));
                        ch.pipeline().addLast(new StringDecoder());
                        ch.pipeline().addLast(new StringEncoder());

                    }
                });

        ChannelFuture future = serverBootstrap.bind(18080).sync();
        future.channel().closeFuture().addListener(new GenericFutureListener() {

            @Override
            public void operationComplete(Future future) throws Exception {
                boss.shutdownGracefully();
                worker.shutdownGracefully();
            }
        }).sync();


    }
           

服務端啟動的過程中,核心主要幹了這麼幾件事:

1)指定channel類型,這決定了服務端建立什麼類型的channel。

2)指定服務端運作的線程組,包括boss線程組、worker線程組,隻有指定了線程組,後續服務端才能正常工作

3)為parentChannel與childChannel指定網絡協定的配置

4)為channel指定相關的channelHandler,這是網絡IO讀寫過程中會用到的邏輯處理類,主要是設定childChannel的Handler。

5)綁定端口。這算是服務端啟動的最後一步了,服務端綁定了端口之後,就是等待用戶端來建立連接配接了。

那綁定端口主要幹啥了呢,我們通過源碼可以發現,綁定端口的時候主要做以下幾件事:

1)建立parentChannel——>初始化parentChannel的配置,包括網絡協定的配置,pipeline的配置等等——>注冊parentChannel,将parentChannel注冊到boss線程組中的某個線程上,同時與selector關聯,等待用戶端的連接配接進來。從此該線程與selector便會跟随該parentChannel一輩子。這樣做的好處就是不需要切換線程上下文了,提高伺服器的性能。

2)當有用戶端請求進來的時候,parentChannel關聯的pipeline中會有對應的channelHandler處理,讀取用戶端的連接配接請求,生成childChannel,同時對于childChannel,又走一遍1的流程(建立channel、初始化channel、注冊channel),唯一不同的就是,childChannel關聯的是work線程組中的線程,這樣的話boss線程組與worker線程組的職責是分明的。

總結一下,整個啟動過程其實都是圍繞channel來做的,主要就是:建立channel——初始化channel(其中包括channelPipeline、channelHandler的設定,往channelPipeline中添加channelHandler的時候,會自動觸發handleAdded事件)——注冊channel——doBind()——channelActive。

用戶端啟動

用戶端的啟動相對簡單,同樣的先看一段簡易用戶端啟動的簡易代碼。

public static void main(String[] args) throws InterruptedException {
        Bootstrap bootstrap = new Bootstrap();

        NioEventLoopGroup worker = new NioEventLoopGroup(5);

        bootstrap.channel(NioSocketChannel.class)
                .group(worker)
                .handler(new ChannelInitializer<NioSocketChannel>() {
                    @Override
                    protected void initChannel(NioSocketChannel ch) throws Exception {
                        ch.pipeline().addLast(new DelimiterBasedFrameDecoder(8192, Delimiters.lineDelimiter()));
                        ch.pipeline().addLast(new StringDecoder());
                        ch.pipeline().addLast(new StringEncoder());

                    }
                })
                .remoteAddress("127.0.0.1", 18080);

        ChannelFuture future = bootstrap.connect().sync();
        future.channel().closeFuture().addListener(new GenericFutureListener() {

            @Override
            public void operationComplete(Future future) throws Exception {
                worker.shutdownGracefully();
            }
        }).sync();


    }
           

可見用戶端的啟動過程主要包括:

1)指定channel類型,這決定了用戶端建立連接配接時建立什麼類型的channel

2)指定線程組模型,用于用戶端通信

3)指定channelHandler,用于通信時處理IO

4)指定ChannelOption網絡協定配置

5)一切就緒後,就可以開始連接配接服務端了。連接配接成功後就可以開始通信了。

這裡有點要注意的就是,無論時用戶端還是服務端,對于channel的處理過程都是類似的。建立channel、初始化channel、注冊channel。

下回分解

netty中線程組的概念是什麼意思?

為什麼有boss線性組與worker線程組?它們分别是怎麼分工的?

parentChannel與childChannel是什麼關系?在netty中分别代表什麼?

ChannelHandler的工作機制是怎樣的?netty具體是怎麼實作的?

網絡程式設計中的channel到底是啥玩意兒?channel、用戶端、服務端之間是啥關系?

号稱是極品中的精品的網絡線程模型到底是長啥樣的?netty中又是怎麼實作的?

netty中到處都用的著名的pipeline模式是如何實作與應用的?

繼續閱讀