【Netty】服務端和客戶端

歡迎關注公衆號:【愛編程
如果有需要後臺回覆2019贈送1T的學習資料哦!!

本文是基於Netty4.1.36進行分析

服務端

Netty服務端的啓動代碼基本都是如下:

private void start() throws Exception {

        final EchoServerHandler serverHandler = new EchoServerHandler();
        /**
         * NioEventLoop並不是一個純粹的I/O線程,它除了負責I/O的讀寫之外
         * 創建了兩個NioEventLoopGroup,
         * 它們實際是兩個獨立的Reactor線程池。
         * 一個用於接收客戶端的TCP連接,
         * 另一個用於處理I/O相關的讀寫操作,或者執行系統Task、定時任務Task等。
         */
        EventLoopGroup bossGroup = new NioEventLoopGroup();
        EventLoopGroup childGroup = new NioEventLoopGroup();

        try {
            //ServerBootstrap負責初始化netty服務器,並且開始監聽端口的socket請求
            ServerBootstrap b = new ServerBootstrap();
            b.group(bossGroup, childGroup)
                    .channel(NioServerSocketChannel.class)
                    .localAddress(new InetSocketAddress(port))
                    .childHandler(new ChannelInitializer<SocketChannel>() {
                        @Override
                        protected void initChannel(SocketChannel socketChannel) throws Exception {
//                            爲監聽客戶端read/write事件的Channel添加用戶自定義的ChannelHandler
                            socketChannel.pipeline().addLast(serverHandler);
                        }
                    });

            ChannelFuture f = b.bind().sync();

            f.channel().closeFuture().sync();
        } catch (InterruptedException e) {
            e.printStackTrace();
        } finally {
            bossGroup.shutdownGracefully().sync();
            childGroup.shutdownGracefully().sync();
        }
    }

從上圖的代碼可以總結爲以下幾個步驟:

1、創建ServerBootStrap實例
2、設置並綁定Reactor線程池:EventLoopGroup,EventLoop就是處理所有註冊到本線程的Selector上面的Channel
3、設置並綁定服務端的channel
4、5、創建處理網絡事件的ChannelPipeline和handler,網絡時間以流的形式在其中流轉,handler完成多數的功能定製:比如編解碼 SSl安全認證
6、綁定並啓動監聽端口
7、當輪訓到準備就緒的channel後,由Reactor線程:NioEventLoop執行pipline中的方法,最終調度並執行channelHandler

服務端創建時序圖

ServerBootStrap引導啓動服務端

它就是主要引導啓動服務端,工作包括以下:

  • 1.創建服務端Channel
  • 2.初始化服務端Channel
  • 3.將Channel註冊到selector
  • 4.端口綁定

1.創建服務端Channel

流程:
首先從用戶代碼的bind()其實就是AbstractBootstrap.bind(),然後通過反射工廠將用戶通過b.channel(NioServerSocketChannel.class)傳入的NioServerSocketChannel通過調用底層的jdk的SelectorProvider創建channel,同時也接着創建好對應的ChannelPipeline
詳情可以參考下圖,自己去查看一下源碼:

2.初始化服務端Channel

主要工作如下:

1)設置的option緩存到NioServerSocketChannelConfig裏
2)設置的attr設置到channel裏
3)保存配置的childOptions,配置的childAttrs 到ServerBootstrapAcceptor裏
4)往NioSocketChannel的pipeline中添加一個ServerBootstrapAcceptor

主要的核心源碼如下:

 @Override
    void init(Channel channel) throws Exception {
        final Map<ChannelOption<?>, Object> options = options0();
        synchronized (options) {
            setChannelOptions(channel, options, logger);
        }

        final Map<AttributeKey<?>, Object> attrs = attrs0();
        synchronized (attrs) {
            for (Entry<AttributeKey<?>, Object> e: attrs.entrySet()) {
                @SuppressWarnings("unchecked")
                AttributeKey<Object> key = (AttributeKey<Object>) e.getKey();
                channel.attr(key).set(e.getValue());
            }
        }

        ChannelPipeline p = channel.pipeline();

        final EventLoopGroup currentChildGroup = childGroup;
        final ChannelHandler currentChildHandler = childHandler;
        final Entry<ChannelOption<?>, Object>[] currentChildOptions;
        final Entry<AttributeKey<?>, Object>[] currentChildAttrs;
        synchronized (childOptions) {
            currentChildOptions = childOptions.entrySet().toArray(newOptionArray(0));
        }
        synchronized (childAttrs) {
            currentChildAttrs = childAttrs.entrySet().toArray(newAttrArray(0));
        }

        p.addLast(new ChannelInitializer<Channel>() {
            @Override
            public void initChannel(final Channel ch) throws Exception {
                final ChannelPipeline pipeline = ch.pipeline();
                ChannelHandler handler = config.handler();
                if (handler != null) {
                    pipeline.addLast(handler);
                }

                ch.eventLoop().execute(new Runnable() {
                    @Override
                    public void run() {
                        pipeline.addLast(new ServerBootstrapAcceptor(
                                ch, currentChildGroup, currentChildHandler, currentChildOptions, currentChildAttrs));
                    }
                });
            }
        });
    }

小結:
總體如上面工作流程所述。
特別地建議:查看ServerBootstrapAcceptor源碼,你可以發現ServerBootstrapAcceptor在channelRead事件觸發的時候(也就有客戶端連接的時候),把childHandler加到childChannel Pipeline的末尾,設置childHandler的options和attrs,最後把childHandler註冊進childGroup

3.將Channel註冊到selector

註冊過程如下圖

小結:
Channel 註冊過程所做的工作就是將 Channel 與對應的 EventLoop 關聯。

1).每個 Channel 都會關聯一個特定的 EventLoop, 並且這個 Channel 中的所有 IO 操作都是在這個 EventLoop 中執行的;

2).當關聯好 Channel 和 EventLoop 後, 會繼續調用底層的 Java NIO SocketChannel 的 register 方法, 將底層的 Java NIO SocketChannel 註冊到指定的 selector 中.

通過這兩步, 就完成了 Netty Channel 的註冊過程.

4.端口綁定

端口綁定的源碼流程基本如下圖,詳情可以還是你自己讀一下源碼比較好點。

小結:
其實netty端口綁定是調用 jdk的javaChannel().bind(localAddress, config.getBacklog());進行綁定,然後TCP鏈路建立成功,Channel激活事件,通過channelPipeline進行傳播。

客戶端

客戶端啓動的常規代碼如下:

  private void start() throws Exception {

        /**
         * Netty用於接收客戶端請求的線程池職責如下。
         * (1)接收客戶端TCP連接,初始化Channel參數;
         * (2)將鏈路狀態變更事件通知給ChannelPipeline
         */
        EventLoopGroup group = new NioEventLoopGroup();
        try {
            Bootstrap b = new Bootstrap();
            b.group(group)
                    .channel(NioSocketChannel.class)
                    .remoteAddress(new InetSocketAddress(host,port))
                    .handler(new ChannelInitializer<SocketChannel>() {
                        @Override
                        protected void initChannel(SocketChannel socketChannel) throws Exception {
                            socketChannel.pipeline().addLast(new EchoClientHandler());
                        }
                    });
            //綁定端口
            ChannelFuture f = b.connect().sync();

            f.channel().closeFuture().sync();
        } catch (Exception e) {
            group.shutdownGracefully().sync();
        }


    }

Netty客戶端創建時序圖

流程:

1.用戶線程創建Bootstrap實例,通過API設置創建客戶端相關的參數,異步發起客戶端連接。
2.創建處理客戶端連接、I/O讀寫的Reactor線程組NioEventLoopGroup,默認爲CPU內核數的2倍。
3.通過Bootstrap的ChannelFactory和用戶指定的Channel類型創建用於客戶端NioSocketChannel,它的功能類似於JDK NIO類庫提供的SocketChannel
4.創建默認的Channel Handler Pipeline,用於調度和執行網路事件。
5.異步發起TCP連接,判斷連接是否成功。如果成功,則直接將NioSocketChannel註冊到多路複用器上,監聽讀操作位,用於數據包讀取和消息發送,如果沒有立即連接成功,則註冊連接監聽爲到多路複用器,等待連接結果。
6.註冊對應的網絡監聽狀態爲到多路複用器。
7.由多路複用器在I/O現場中輪詢個Channel,處理連接結果。
8.如果連接成功,設置Future結果,發送連接成功事件,觸發ChannelPipeline執行。
9.由ChannelPipeline調度執行系統和用戶的ChannelHandler,執行邏輯。

源碼調用流程如下圖:

小結:
客戶端是如何發起 TCP 連接的?

如下圖:

特別提醒:
在AbstractChannelHandlerContext.connect()#findContextOutbound這步操作是返回的結果next其實是頭節點,也就是說在下一步next.invokeConnect()這裏的next就是頭節點,所以最終是調用HeadContext .connect()

總結

本文主要講述netty服務端和客戶端的簡單工作流程。
具體服務端與客戶端如何通信,以及內存管理等方面的知識下一次再寫。

最後

如果對 Java、大數據感興趣請長按二維碼關注一波,我會努力帶給你們價值。覺得對你哪怕有一丁點幫助的請幫忙點個贊或者轉發哦。
關注公衆號【愛編碼】,回覆2019有相關資料哦。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章