首頁 > 軟體

Netty分散式server啟動流程Nio建立原始碼分析

2022-03-25 13:02:16

前文傳送門 Netty分散式Server啟動流程伺服器端初始化原始碼分析

NioServerSocketChannel建立

我們如果熟悉Nio, 則對channel的概念則不會陌生, channel在相當於一個通道, 用於資料的傳輸

Netty將jdk的channel進行了包裝, 併為其擴充套件了更多的功能

在netty中也分為伺服器端channel和使用者端channel, 在Nio模式下, 伺服器端channel對應的類為NioServerSocketChannel, 包裝的jdk的ServerSocketChannel

使用者端channel對應的類為NioSocketChannel, 所包裝的jdk的類為SocketChannel

繼承關係

最簡單的繼承關係如下(經簡化):

我們繼續看第一小節demo:

//建立boss和worker執行緒(1)
EventLoopGroup bossGroup = new NioEventLoopGroup(1);
EventLoopGroup workerGroup = new NioEventLoopGroup();
//建立ServerBootstrap(2)
ServerBootstrap b = new ServerBootstrap();
//初始化boss和work執行緒化兩個執行緒(3)
b.group(bossGroup, workerGroup)
        //宣告NioServerSocketChannel(4)
        .channel(NioServerSocketChannel.class)
        //初始化使用者端Handler(5)
        .childHandler(new ChannelInitializer<SocketChannel>() {
            @Override
            public void initChannel(SocketChannel ch) {
                ch.pipeline().addLast(new StringDecoder());
                ch.pipeline().addLast(new StringEncoder());
                ch.pipeline().addLast(new ServerHandler());
            }
        });
//繫結埠(6)
ChannelFuture f = b.bind(8888).sync();
f.channel().closeFuture().sync();

繫結埠

我們繼續看第六步, 繫結埠:

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

在此, 我們看到繫結了8888埠

我們跟到bind(8888)方法中:

public ChannelFuture bind(int inetPort) {
    return bind(new InetSocketAddress(inetPort));
}

埠封裝成socket地址物件

繼續跟bind方法:

public ChannelFuture bind(SocketAddress localAddress) {
    validate();
    return doBind(localAddress);
}

validate()做了一些屬性驗證

我們繼續跟到doBind(localAddress)方法:

private ChannelFuture doBind(final SocketAddress localAddress) {
    //初始化並註冊(1)
    final ChannelFuture regFuture = initAndRegister();
    //獲得channel(2)
    final Channel channel = regFuture.channel();
    if (regFuture.cause() != null) {
        return regFuture;
    }
    if (regFuture.isDone()) {
        ChannelPromise promise = channel.newPromise();
        //繫結(3) 
        doBind0(regFuture, channel, localAddress, promise);
        return promise;
    } else {
        //去除非關鍵程式碼
        return promise;
    }
}

去除了一些非關鍵的程式碼, 重點關注註釋標註的第一步, 初始化並註冊:

final ChannelFuture regFuture = initAndRegister();

跟進initAndRegister()方法 建立channel

final ChannelFuture initAndRegister() {
    Channel channel = null;
    try {
        //建立channel
        channel = channelFactory.newChannel();
        init(channel);
    } catch (Throwable t) {
        //忽略非關鍵程式碼
    }
    ChannelFuture regFuture = config().group().register(channel);
    //忽略非關鍵程式碼
    return regFuture;
}

關注新增註釋的步驟, 建立channel, 這一點也是我們這節需要講明白的內容

看這一步:

channel = channelFactory.newChannel();

這裡channelFactory呼叫了newChannel()的這個方法, 這個方法從名字就不難理解, 是新建一個channel, 回顧下上一小節, 這個channelFactory是在哪裡初始化呢?

根據上一小節程式碼, channelFactory是在Bootstrap的channelFactory ()方法初始化的:

public B channelFactory(ChannelFactory<? extends C> channelFactory) {
    this.channelFactory = channelFactory;
    return (B) this;
}

而這個方法又是channel()方法中呼叫的:

public B channel(Class&lt;? extends C&gt; channelClass) {
    return channelFactory(new ReflectiveChannelFactory&lt;C&gt;(channelClass));
}

這裡傳入ReflectiveChannelFactory物件就是初始化的channelFactory物件

所以newChannel()是呼叫ReflectiveChannelFactory物件的newChannel方法

跟到ReflectiveChannelFactory物件的newChannel方法中:

@Override
public T newChannel() {
    try { 
        return clazz.newInstance();
    } catch (Throwable t) {
        throw new ChannelException("Unable to create Channel from class " + clazz, t);
    }
}

我們看到這個clazz物件通過反射建立了channel, 這個clazz物件, 就是我們上一節提到過的, 初始化的NioServerSocketChannel的class物件

這裡通過反射呼叫, 會建立一個NioServerSokectChannel

學習過nio的小夥伴都知道jdk的ServerSocketChannel, 用於接受連結事件, 而netty的NioServerSocketChannel是和jdk的channel有什麼關係呢?

實際上netty的channel和jdk的channel的含義一樣, 也是一個通道, 只是netty為其做了擴充套件, 而channel的事件處理, 也是通過jdk的channel去做的, 我們跟隨著NioServerSocketChannel的建立過程, 來了解他們之間的關聯關係

clazz.newInstance()通過反射建立一個NioServerSocketChannel物件, 首先會走到NioServerSocketChannel的構造方法, 我們跟到他的構造方法, 檢視NioServerSocketChannel的建立過程

首先會呼叫它的無參構造方法:

public NioServerSocketChannel() {
    this(newSocket(DEFAULT_SELECTOR_PROVIDER));
}

我們看到這個構造方法呼叫了另一個有參的構造方法, 傳入引數是 newSocket(DEFAULT_SELECTOR_PROVIDER) 

我們首先看DEFAULT_SELECTOR_PROVIDER這個這變數:

private static final SelectorProvider DEFAULT_SELECTOR_PROVIDER = SelectorProvider.provider();

看到這初始化了一個SelectorProvider物件, 而這個物件是通過靜態方法provider()建立的, SelectorProvider物件可以用於建立jdk底層的ServerSocketChannel

我們繼續跟到newSocket(DEFAULT_SELECTOR_PROVIDER)中:

private static ServerSocketChannel newSocket(SelectorProvider provider) {
    return provider.openServerSocketChannel();
}

去掉try-catch塊, 發現這個方法是通過SelectorProvider物件的openServerSocketChannel()方法建立一個jdk底層的ServerSocketChannel, 至此我們可以知道, 與NioServerSokectChannel繫結的jdk底層的ServerSocketChannel就是這麼建立的

父類別的構造方法

那麼建立之後如何與netty的channel繫結?繼續跟程式碼

跟到this(newSocket(DEFAULT_SELECTOR_PROVIDER))中:

public NioServerSocketChannel(ServerSocketChannel channel) {
    super(null, channel, SelectionKey.OP_ACCEPT);
    config = new NioServerSocketChannelConfig(this, javaChannel().socket());
}

我們看到這裡呼叫了父類別的構造方法,繼續往裡跟:

protected AbstractNioMessageChannel(Channel parent, SelectableChannel ch, int readInterestOp) {
    super(parent, ch, readInterestOp);
}

這裡呼叫了其父類別AbstractNioMessageChannel類的構造方法, AbstractNioMessageChannel這個類同學們請記住, 有關是NioServerSocketChannel的父類別, 代表著伺服器端channel的相關屬性和操作, 之後有關伺服器端channel的一些事件會在這個類中完成

我們看到這個類的構造方法中又呼叫了它的父類別的構造方法, 我們繼續跟:

protected AbstractNioChannel(Channel parent, SelectableChannel ch, int readInterestOp) {
    super(parent);
    //儲存channel
    this.ch = ch;
    //繫結事件
    this.readInterestOp = readInterestOp;
    try {
        //設定為非阻塞
        ch.configureBlocking(false);
    } catch (IOException e) {
        //去掉非關鍵程式碼
    }
}

這裡又呼叫了其父類別AbstractChannel的構造方法, 跟進去這個方法之前, 我們先往下看

首先看這一步:

this.ch = ch;

這步就是繫結jdk底層的ServerSocketChannel, 至此我們知道, jdk的channel和netty定義的channel是組合關係, netty的channel中有個jdk的channel的成員變數, 而這個成員變數就定義在AbstractNioChannel這個類當中, 希望同學們將這個結論牢牢記住, 對以後的學習很有幫助

將jdk的channel設定為非阻塞模式

我們看到後面的這一步:

ch.configureBlocking(false);

這一步, 就是將jdk的channel設定為非阻塞模式, 這裡熟悉Nio的同學應該不會陌生, 這裡不再贅述

 我們繼續跟到super(parent)中, 走到其父類別AbstractChannel的構造方法:

protected AbstractChannel(Channel parent) {
    this.parent = parent;
    id = newId();
    unsafe = newUnsafe();
    pipeline = newChannelPipeline();
}

首先看下這個parent, 這個parent是NioServerSocketChannel呼叫其父類別構造方法傳入的, 傳入的是null, 所以這一步AbstractChannel的屬性parent也是null, 這個parent, 我們之後再講使用者端channel的時候會講到

id = newId()是為每個channel建立一個唯一id

我們重點關注下後兩步:

unsafe = newUnsafe();
pipeline = newChannelPipeline();

這裡初始化了兩個屬性unsafe, 和pipeline, 目前我們只需要知道這兩個屬性是在這裡初始化的, 至於這兩個屬性的概念, 後面的章節會講到

以上就是建立NioServerSocketChannel的過程, 同學們可以課後跟進原始碼去熟悉鞏固

以上就是Netty分散式server啟動流程Nio建立原始碼分析的詳細內容,更多關於Netty分散式server啟動流程Nio建立的資料請關注it145.com其它相關文章!


IT145.com E-mail:sddin#qq.com