【Netty】服務端和客戶端

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

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

服務端

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();
        }
    }

從上圖的代碼能夠總結爲如下幾個步驟:緩存

一、建立ServerBootStrap實例
二、設置並綁定Reactor線程池:EventLoopGroup,EventLoop就是處理全部註冊到本線程的Selector上面的Channel
三、設置並綁定服務端的channel
四、五、建立處理網絡事件的ChannelPipeline和handler,網絡時間以流的形式在其中流轉,handler完成多數的功能定製:好比編解碼 SSl安全認證
六、綁定並啓動監聽端口
七、當輪訓到準備就緒的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中添加一個ServerBootstrapAcceptorsocket

主要的核心源碼以下:ide

@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註冊進childGroupoop

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有相關資料哦。

相關文章
相關標籤/搜索