Netty(五)—服務端小記

Netty服務端

  • Netty客戶端程序員啓動需要使用 Bootstrap 對象
  • 和客戶端不同,服務端使用 ServerBootStrap 對象來啓動

Server代碼實現

  • 通過和客戶端代碼實現對比,可以發現
    • 服務端設置了兩個EventLoopGroup對象,分別用於處理客戶端連接請求和處理IO操作
    • 與客戶端實現不同 服務端使用 ServerBootstrap 來實現程序啓動
    • 使用 ServerBootstrap 的 bind 方法來啓動服務
public void start(int port) {
        EventLoopGroup bossGroup = new NioEventLoopGroup();
        EventLoopGroup workerGroup = new NioEventLoopGroup();
        try {
            ServerBootstrap b = new ServerBootstrap();
            b.group(bossGroup, workerGroup)
                    .channel(NioServerSocketChannel.class)
                    .option(ChannelOption.SO_BACKLOG, 1024)
                    .childHandler(new ChannelInitializer<SocketChannel>() {
                        @Override
                        public void initChannel(SocketChannel ch) throws Exception {
                            ChannelPipeline pipeline = ch.pipeline();
                            ...
                        }
                    });
            ChannelFuture f = b.bind(this.port).sync();
            log.info("服務已啓動,監聽端口" + this.port);
            f.channel().closeFuture().sync();
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            workerGroup.shutdownGracefully();
            bossGroup.shutdownGracefully();
        }
    }
  • 服務端使用ServerBootstrap而非Bootstarp
  • ServerBootstrap同樣提供channel方法設置Channel Type
    • 完成實例化操作的對象是 ReflectiveChannelFactory,通過反射調用到 NioServerSocketChannel 的構造方法

NioServerSocketChannel初始化和註冊

  • 客戶端使用Channel Type爲NioSocketChannel
  • 與客戶端不同的,服務端的入口是ServerBootstrap的bind方法,然後調用到父類的 doBind 方法完成 初始化和註冊

在這裏插入圖片描述

  • 與客戶端不同的一點是,服務端傳入參數是 SelectionKey.OP_ACCEPT ,表示服務啓動後要監聽客戶端的連接請求

在這裏插入圖片描述

  • 在服務端由於有兩個EventLoopGroup,所以Channel的註冊與客戶端略有不同
  • 在initAndRegister方法中,首先完成了bossGroup和NioServerSocketChannel的關聯
  • 接下來在init方法中完成了workerGroup和NioServerSocketChannel的關聯
  • 當一個客戶端連接發送到服務端時,Java底層NIO的ServerSocketChannel會有已經初始化完成的 SelectionKey.OP_ACCEPT就緒
  • 由NioServerSocketChannel的doReadMessages方法通過調用javaChannel().accept()方法獲取建立連接的SocketChannel對象

bossGroup和workerGroup

  • 客戶端初始化流程的時候實例化了一個EventLoopGroup對象
  • 而服務端實例化了兩個EventLoopGroup對象,其工作模型如下

在這裏插入圖片描述

  • 服務端bossGroup不斷監聽是否有客戶端的連接,當拿到一個連接之後,bossGroup初始化各項資源
  • 然後workerGroup中選出一個EventLoop與目標連接建立綁定
  • 接下來與客戶端的交互過程在分配的 EventLoop 中完成

Selector事件輪詢

  • 服務端啓動由ServerBootstrap的bind()方法開始,調用到AbstractBootstrap的doBind方法

在這裏插入圖片描述

  • doBind0 方法,這裏調用了 與 channel關聯的 EventLoop 的execute方法 AbstractBootstrap -> doBind0

在這裏插入圖片描述

  • 接下來調用到 SingleThreadEventExecutor 的 execute 方法
  • 這裏主要做的事情就是創建線程,將線程添加到EventLoop的隊列中去

在這裏插入圖片描述

  • startThread() 方法

在這裏插入圖片描述

  • SingleThreadEventExecutor.this.run方法,這裏的this實際上時EventLoop對象
    • 這裏實現了具體的輪詢邏,體現了java NIO中的 select() 方法
    protected void run() {
        for (;;) {
            try {
                switch (selectStrategy.calculateStrategy(selectNowSupplier, hasTasks())) {
                    case SelectStrategy.CONTINUE:
                        continue;
                    case SelectStrategy.SELECT:
                        select(wakenUp.getAndSet(false));
                        if (wakenUp.get()) {
                            selector.wakeup();
                        }
                    default:
                }
                cancelledKeys = 0;
                needsToSelectAgain = false;
                final int ioRatio = this.ioRatio;
                if (ioRatio == 100) {
                    try {
                        processSelectedKeys();
                    } finally {
                        // Ensure we always run tasks.
                        runAllTasks();
                    }
                } else {
                    final long ioStartTime = System.nanoTime();
                    try {
                        processSelectedKeys();
                    } finally {
                        final long ioTime = System.nanoTime() - ioStartTime;
                        runAllTasks(ioTime * (100 - ioRatio) / ioRatio);
                    }
                }
            } catch (Throwable t) {
                handleLoopException(t);
            }
            try {
                if (isShuttingDown()) {
                    closeAll();
                    if (confirmShutdown()) {
                        return;
                    }
                }
            } catch (Throwable t) {
                handleLoopException(t);
            }
        }
    }

本文總結

  • Netty作爲服務端程序啓動與客戶端的區別
  • 服務端使用NioServerSocketChannel進行初始化和註冊的基本過程
  • boosGroup與workerGroup的區別及其工作模型
  • 跟蹤源碼找到了相關代碼,體現了selector的輪詢機制
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章