Netty源碼------Channel的創建到底幹了什麼?

Channel的創建到底幹了什麼?

目錄

Channel的創建到底幹了什麼?

1、先來了解一些什麼是Netty的Channel

2、Netty的Channel創建的時序圖

3、源碼分析

Channel創建

Channel初始化

Channel註冊

Channel綁定

總結


1、先來了解一些什麼是Netty的Channel

什麼是Channel?

channel是一個管道,用於連接字節緩衝區Buf和另一端的實體,這個實例可以是Socket,也可以是File, 在Nio網絡編程模型中, 服務端和客戶端進行IO數據交互(得到彼此推送的信息)的媒介就是Channel

Netty對Jdk原生的ServerSocketChannel進行了封裝和增強封裝成了NioXXXChannel, 相對於原生的JdkChannel, Netty的Channel增加了如下的組件:

  • id 標識唯一身份信息
  • 可能存在的parent Channel
  • 管道 pepiline
  • 用於數據讀寫的unsafe內部類
  • 關聯上相伴終生的NioEventLoop

整個族羣中,AbstractChannel 是最爲關鍵的一個抽象類,從它繼承出了AbstractNioChannel、AbstractOioChannel、AbstractEpollChannel、LocalChannel、EmbeddedChannel等類,每個類代表了不同的協議以及相應的IO模型。除了 TCP 協議以外,Netty 還支持很多其他的連接協議,並且每種協議還有 NIO(異步 IO) 和 OIO(Old-IO,即傳統的阻塞 IO) 版本的區別. 不同協議不同的阻塞類型的連接都有不同的 Channel 類型與之對應。下面是一些常用的 Channel 類型:

  • NioSocketChannel:代表異步的客戶端 TCP Socket 連接
  • NioServerSocketChannel:異步的服務器端 TCP Socket 連接
  • NioDatagramChannel:異步的 UDP 連接
  • NioSctpChannel:異步的客戶端 Sctp 連接
  • NioSctpServerChannel:異步的 Sctp 服務器端連接
  • OioSocketChannel:同步的客戶端 TCP Socket 連接
  • OioServerSocketChannel:同步的服務器端 TCP Socket 連接
  • OioDatagramChannel:同步的 UDP 連接
  • OioSctpChannel:同步的 Sctp 服務器端連接
  • OioSctpServerChannel:同步的客戶端 TCP Socket 連接

具體的Channel框架結構圖詳解可以參考:

https://www.cnblogs.com/ZhuChangwu/p/11204057.html


2、Netty的Channel創建的時序圖

這是下面對應源碼的時序圖簡單版,爲了排版,省略了一些。

3、源碼分析

這是一個大家平時經常使用的套路代碼。那麼group(),bind()等方法究竟幹了什麼呢?下面從源碼開始找答案:

public void start() {
		init();

		//Netty封裝了NIO,Reactor模型,Boss,worker
		// Boss線程
		EventLoopGroup bosGroup = new NioEventLoopGroup();
		//worker工作線程
		EventLoopGroup workerGroup = new NioEventLoopGroup();
		try {
			//相當於Nio中的ServerSocketChannel
			ServerBootstrap server = new ServerBootstrap();
			//開始設置各種參數
			server.group(bosGroup, workerGroup)
					//主線程處理類
					.channel(NioServerSocketChannel.class)
					//子線程處理類Handler
					.childHandler(new ChannelInitializer<SocketChannel>() {
						protected void initChannel(SocketChannel client) throws Exception {
							//這裏採用了責任鏈模式進行
							client.pipeline().addLast(new HttpResponseEncoder()) //編碼器
									.addLast(new HttpRequestDecoder())    //解碼器
									.addLast(new GPTomcatHandler());     //業務邏輯處理
						}
					})
					.option(ChannelOption.SO_BACKLOG, 64)  //針對主線程的配置,分配線程最大數量
					.childOption(ChannelOption.SO_KEEPALIVE, true); //針對子線程的配置,保喫長連接
			//啓動服務,綁定端口,異步
			ChannelFuture future = server.bind(port).sync();
			System.out.println("GP Tomcat 已啓動,監聽的端口是:" + port);
			future.channel().closeFuture().sync();
		} catch (Exception e) {
			e.printStackTrace();
		} finally {
			//關閉線程池
             bosGroup.shutdownGracefully();
             workerGroup.shutdownGracefully();
		}
	}

服務端Channel的創建過程,主要分爲四個步驟:

1)Channel創建;

2)Channel初始化;

3)Channel註冊;

4)Channel綁定。

 

Channel創建

我們以下面的代碼爲例進行解析:

調用channel()接口設置 AbstractBootstrap 的成員變量 channelFactory,該變量顧名思義就是用於創建channel的工廠類。源碼如下:


...

public B channel(Class<? extends C> channelClass) {
    if (channelClass == null) {
        throw new NullPointerException("channelClass");
    }
    // 創建 channelFactory
    return channelFactory(new ReflectiveChannelFactory<C>(channelClass));
}

...

public B channelFactory(ChannelFactory<? extends C> channelFactory) {
    if (channelFactory == null) {
        throw new NullPointerException("channelFactory");
    }
    
    if (this.channelFactory != null) {
        throw new IllegalStateException("channelFactory set already");
    }
    this.channelFactory = channelFactory;
    return (B) this;
}

...

channelFactory 設置爲 ReflectiveChannelFactory ,在我們這個例子中 clazz 爲 NioServerSocketChannel ,我們可以看到其中有個 newChannel() 接口,通過反射的方式來調用,這個接口的調用處我們後面會介紹到。源碼如下:

// Channel工廠類
public class ReflectiveChannelFactory<T extends Channel> implements ChannelFactory<T> {

    private final Class<? extends T> clazz;

    public ReflectiveChannelFactory(Class<? extends T> clazz) {
        if (clazz == null) {
            throw new NullPointerException("clazz");
        }
        this.clazz = clazz;
    }
	
    @Override
    public T newChannel() {
        try {
            // 通過反射來進行常見Channel實例
            return clazz.newInstance();
        } catch (Throwable t) {
            throw new ChannelException("Unable to create Channel from class " + clazz, t);
        }
    }

    @Override
    public String toString() {
        return StringUtil.simpleClassName(clazz) + ".class";
    }
}

接下來我們來看下 NioServerSocketChannel 的構造函數,主要就是:

  • 生成ServerSocketChannel對象。NioServerSocketChannel創建時,首先使用SelectorProvider的openServerSocketChannel打開服務器套接字通道。SelectorProvider是Java的NIO提供的抽象類,是選擇器和可選擇通道的服務提供者。具體的實現類有SelectorProviderImpl,EPollSelectorProvide,PollSelectorProvider。選擇器的主要工作是根據操作系統類型和版本選擇合適的Provider:如果LInux內核版本>=2.6則,具體的SelectorProvider爲EPollSelectorProvider,否則爲默認的PollSelectorProvider。
  • 設置 ServerSocketChannelConfig 成員變量。
private static ServerSocketChannel newSocket(SelectorProvider provider) {
    try {
        // 調用JDK底層API生成 ServerSocketChannel 對象實例
        return provider.openServerSocketChannel();
    } catch (IOException e) {
        throw new ChannelException("Failed to open a server socket.", e);
    }
}

private final ServerSocketChannelConfig config;

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

public NioServerSocketChannel(SelectorProvider provider) {
    this(newSocket(provider));
}

public NioServerSocketChannel(ServerSocketChannel channel) {
    // 調用 AbstractNioChannel 構造器,創建 NioServerSocketChannel,設置SelectionKey爲ACCEPT
    super(null, channel, SelectionKey.OP_ACCEPT);
    // 創建ChannleConfig對象,主要是TCP參數配置類
    config = new NioServerSocketChannelConfig(this, javaChannel().socket());
}

AbstractNioChannel 的構造器如下:

protected AbstractNioChannel(Channel parent, SelectableChannel ch, int readInterestOp) {
    // 調用 AbstractChannel 構造器
    super(parent);
    this.ch = ch;
    // 從上一步過來,這裏設置爲 SelectionKey.OP_ACCEPT
    this.readInterestOp = readInterestOp;
    try {
        // 設置爲非阻塞狀態
        ch.configureBlocking(false);
    } catch (IOException e) {
        try {
            ch.close();
        } catch (IOException e2) {
            if (logger.isWarnEnabled()) {
                logger.warn("Failed to close a partially initialized socket.", e2);
            }
        }
        throw new ChannelException("Failed to enter non-blocking mode.", e);
    }
}

在 AbstractChannel 構造器中,會設Channel關聯的三個核心對象:ChannelId、ChannelPipeline、Unsafe。

  • 初始化ChannelId,ChannelId是一個全局唯一的值;
  • 創建 NioMessageUnsafe 實例,該類爲Channel提供了用於完成網絡通訊相關的底層操作,如connect(),read(),register(),bind(),close()等;
  • 爲Channel創建DefaultChannelPipeline,初始事件傳播管道。
protected AbstractChannel(Channel parent) {
    this.parent = parent;
    // 設置ChannelId
    id = newId();
    // 設置Unsafe
    unsafe = newUnsafe();
    // 設置Pipeline
    pipeline = newChannelPipeline();
}

從 NioServerSocketChannelConfig 的構造函數追溯下去,在 DefaultChannelConfig 會設置channel成員變量。

public DefaultChannelConfig(Channel channel) {
    this(channel, new AdaptiveRecvByteBufAllocator());
}

protected DefaultChannelConfig(Channel channel, RecvByteBufAllocator allocator) {
    setRecvByteBufAllocator(allocator, channel.metadata());
    // 綁定channel
    this.channel = channel;
}

以上就是channel創建的過程,總結一下:

  • 通過 ReflectiveChannelFactory 工廠類,以反射的方式對channel進行創建;
  • channel創建的過程中,會創建四個重要的對象:ChannelId、ChannelConfig、ChannelPipeline、Unsafe。
     

Channel初始化

  • 將啓動器(Bootstrap)設置的選項和屬性設置到NettyChannel上面
  • 向Pipeline添加初始化Handler,供註冊後使用

我們從 AbstractBootstrap 的 bind() 接口進去,調用鏈:bind() —> doBind(localAddress) —> initAndRegister() —> init(Channel channel),我們看下 ServerBootstrap 中 init() 接口的實現:

final ChannelFuture initAndRegister() {
    Channel channel = null;
    try {
        // 調用Channel工程類的newChannel()接口,創建channel,就是前面我們講的部分內容
        channel = channelFactory.newChannel();
        // 初始化channel
        init(channel);
    } catch (Throwable t) {
        ....
}

初始化Channel,我們來重點看下 init(channel) 接口:

void init(Channel channel) throws Exception {
    // 獲取啓動器 啓動時配置的option參數,主要是TCP的一些屬性
    final Map<ChannelOption<?>, Object> options = options0();
    // 將獲得到 options 配置到 ChannelConfig 中去
    synchronized (options) {
        setChannelOptions(channel, options, logger);
    }

    // 獲取 ServerBootstrap 啓動時配置的 attr 參數
    final Map<AttributeKey<?>, Object> attrs = attrs0();
    // 配置 Channel attr,主要是設置用戶自定義的一些參數
    synchronized (attrs) {
        for (Entry<AttributeKey<?>, Object> e: attrs.entrySet()) {
            @SuppressWarnings("unchecked")
            AttributeKey<Object> key = (AttributeKey<Object>) e.getKey();
            channel.attr(key).set(e.getValue());
        }
    }
	
    // 獲取channel中的 pipeline,這個pipeline使我們前面在channel創建過程中設置的 pipeline
    ChannelPipeline p = channel.pipeline();

    // 將啓動器中配置的 childGroup 保存到局部變量 currentChildGroup
    final EventLoopGroup currentChildGroup = childGroup;
    // 將啓動器中配置的 childHandler 保存到局部變量 currentChildHandler
    final ChannelHandler currentChildHandler = childHandler;
    final Entry<ChannelOption<?>, Object>[] currentChildOptions;
    final Entry<AttributeKey<?>, Object>[] currentChildAttrs;
    // 保存用戶設置的 childOptions 到局部變量 currentChildOptions
    synchronized (childOptions) {
        currentChildOptions = childOptions.entrySet().toArray(newOptionArray(childOptions.size()));
    }
    // 保存用戶設置的 childAttrs 到局部變量 currentChildAttrs
    synchronized (childAttrs) {
        currentChildAttrs = childAttrs.entrySet().toArray(newAttrArray(childAttrs.size()));
    }

    p.addLast(new ChannelInitializer<Channel>() {
        @Override
        public void initChannel(final Channel ch) throws Exception {
            final ChannelPipeline pipeline = ch.pipeline();
            // 獲取啓動器上配置的handler
            ChannelHandler handler = config.handler();
            if (handler != null) {
                // 添加 handler 到 pipeline 中
                pipeline.addLast(handler);
            }
            ch.eventLoop().execute(new Runnable() {
                @Override
                public void run() {
                    // 用child相關的參數創建出一個新連接接入器ServerBootstrapAcceptor
                    // 通過 ServerBootstrapAcceptor 可以將一個新連接綁定到一個線程上去
                    // 每次有新的連接進來 ServerBootstrapAcceptor 都會用child相關的屬性對它們進行配置,並註冊到ChaildGroup上去
                    pipeline.addLast(new ServerBootstrapAcceptor(
                            ch, currentChildGroup, currentChildHandler, currentChildOptions, currentChildAttrs));
                }
            });
        }
    });
}

Channel註冊

在channel完成創建和初始化之後,接下來就需要將其註冊到事件輪循器Selector上去。我們回到 initAndRegister 接口上去:

final ChannelFuture initAndRegister() {

    ...

    // 獲取 EventLoopGroup ,並調用它的 register 方法來註冊 channel
    ChannelFuture regFuture = config().group().register(channel);
    if (regFuture.cause() != null) {
        if (channel.isRegistered()) {
            channel.close();
        } else {
            channel.unsafe().closeForcibly();
        }
    }
    return regFuture;
}

最終會向下調用到 SingleThreadEventLoop 中的 register 接口:

@Override
public ChannelFuture register(final ChannelPromise promise) {
    ObjectUtil.checkNotNull(promise, "promise");
    // 調用unsafe的register接口
    promise.channel().unsafe().register(this, promise);
    return promise;
}

代碼跟蹤下去,直到 AbstractChannel 中的 AbstractUnsafe 這個類中的 register 接口。

@Override
public final void register(EventLoop eventLoop, final ChannelPromise promise) {
    if (eventLoop == null) {
        throw new NullPointerException("eventLoop");
    }
    if (isRegistered()) {
        promise.setFailure(new IllegalStateException("registered to an event loop already"));
        return;
    }
    if (!isCompatible(eventLoop)) {
        promise.setFailure(
                new IllegalStateException("incompatible event loop type: " + eventLoop.getClass().getName()));
        return;
    }

    // 將該Channel與eventLoop 進行綁定,後續與該channel相關的IO操作都由eventLoop來處理
    AbstractChannel.this.eventLoop = eventLoop;
	// 初次註冊時 eventLoop.inEventLoop() 返回false
    if (eventLoop.inEventLoop()) {
        // 調用實際的註冊接口register0
        register0(promise);
    } else {
        try {
            eventLoop.execute(new Runnable() {
                @Override
                public void run() {
                    // 調用實際的註冊接口register0
                    register0(promise);
                }
            });
        } catch (Throwable t) {
            logger.warn(
                    "Force-closing a channel whose registration task was not accepted by an event loop: {}",
                    AbstractChannel.this, t);
            closeForcibly();
            closeFuture.setClosed();
            safeSetFailure(promise, t);
        }
    }
}

register0接口主要分爲以下三段邏輯:

  • doRegister();

  • pipeline.invokeHandlerAddedIfNeeded();

  • pipeline.fireChannelRegistered();

private void register0(ChannelPromise promise) {
    try {
        if (!promise.setUncancellable() || !ensureOpen(promise)) {
            return;
        }
        boolean firstRegistration = neverRegistered;
        // 調用 doRegister() 接口
        doRegister();
        neverRegistered = false;
        registered = true;
        
       	// 通過pipeline的傳播機制,觸發handlerAdded事件
        pipeline.invokeHandlerAddedIfNeeded();
        safeSetSuccess(promise);
        // 通過pipeline的傳播機制,觸發channelRegistered事件
        pipeline.fireChannelRegistered();
        // 還沒有綁定,所以這裏的 isActive() 返回false.
        if (isActive()) {
            if (firstRegistration) {
                pipeline.fireChannelActive();
            } else if (config().isAutoRead()) {
                beginRead();
            }
        }
    } catch (Throwable t) {
        closeForcibly();
        closeFuture.setClosed();
        safeSetFailure(promise, t);
    }
}

我們來看 AbstractNioChannel 中的 doRegister()接口,最終調用的就是Java JDK底層的NIO API來註冊。

@Override
protected void doRegister() throws Exception {
    boolean selected = false;
    for (;;) {
        try {
            // eventLoop().unwrappedSelector():獲取selector,將在後面介紹 EventLoop 創建時會講到
            // 將selector註冊到Java NIO Channel上
            // ops 設置爲 0,表示不關心任何事件
            // att 設置爲 channel自身,表示後面還會將channel取出來用作它用(後面文章會講到)
            selectionKey = javaChannel().register(eventLoop().unwrappedSelector(), 0, this);
            return;
        } catch (CancelledKeyException e) {
            if (!selected) {
                eventLoop().selectNow();
                selected = true;
            } else {
                throw e;
            }
        }
    }
}

Channel綁定

在完成創建、初始化以及註冊之後,接下來就是Channel綁定操作。

從啓動器的bind()接口開始,往下調用 doBind() 方法:

private ChannelFuture doBind(final SocketAddress localAddress) {
    // 初始化及註冊
    final ChannelFuture regFuture = initAndRegister();
    final Channel channel = regFuture.channel();
    if (regFuture.cause() != null) {
        return regFuture;
    }

    if (regFuture.isDone()) {
        // At this point we know that the registration was complete and successful.
        ChannelPromise promise = channel.newPromise();
        // 調用 doBind0
        doBind0(regFuture, channel, localAddress, promise);
        return promise;
    } else {
        ....
    }
}

doBind 方法又會調用 doBind0() 方法,在doBind0()方法中會通過EventLoop去執行channel的bind()任務

private static void doBind0(
        final ChannelFuture regFuture, final Channel channel,
        final SocketAddress localAddress, final ChannelPromise promise) {

    channel.eventLoop().execute(new Runnable() {
        @Override
        public void run() {
            if (regFuture.isSuccess()) {
                // 調用channel.bind接口
                channel.bind(localAddress, promise).addListener(ChannelFutureListener.CLOSE_ON_FAILURE);
            } else {
                promise.setFailure(regFuture.cause());
            }
        }
    });
}

doBind0() 方法往下會條用到 pipeline.bind(localAddress, promise); 方法,通過pipeline的傳播機制,最終會調用到 AbstractChannel.AbstractUnsafe.bind() 方法,這個方法主要做兩件事情:

  • 調用doBind():調用底層JDK API進行Channel的端口綁定。
  • 調用pipeline.fireChannelActive():
     
@Override
public final void bind(final SocketAddress localAddress, final ChannelPromise promise) {
    
    ....
    
    // wasActive 在綁定成功前爲 false
    boolean wasActive = isActive();
    try {
        // 調用doBind()調用JDK底層API進行端口綁定
        doBind(localAddress);
    } catch (Throwable t) {
        safeSetFailure(promise, t);
        closeIfClosed();
        return;
    }
	// 完成綁定之後,isActive() 返回true
    if (!wasActive && isActive()) {
        invokeLater(new Runnable() {
            @Override
            public void run() {
                // 觸發channelActive事件
                pipeline.fireChannelActive();
            }
        });
    }
    safeSetSuccess(promise);
}

我們這裏看服務端 NioServerSocketChannel 實現的 doBind方法,最終會調用JDK 底層 NIO Channel的bind方法

@Override
protected void doBind(SocketAddress localAddress) throws Exception {
    if (PlatformDependent.javaVersion() >= 7) {
        javaChannel().bind(localAddress, config.getBacklog());
    } else {
        javaChannel().socket().bind(localAddress, config.getBacklog());
    }
}

調用 pipeline.fireChannelActive(),開始傳播active事件,pipeline首先就會調用HeadContext節點進行事件傳播,會調用到 DefaultChannelPipeline.HeadContext.channelActive() 方法:

@Override
public void channelActive(ChannelHandlerContext ctx) throws Exception {
    // 觸發heanlder 的 ChannelActive 方法
    ctx.fireChannelActive();
    // 調用接口readIfIsAutoRead
    readIfIsAutoRead();
}

private void readIfIsAutoRead() {
    if (channel.config().isAutoRead()) {
        // 調用channel.read()
        channel.read();
    }
}

channel.read() 方法往下會調用到 AbstractChannelHandlerContext.read() 方法:

@Override
public ChannelHandlerContext read() {
    // 獲取下一個ChannelHandlerContext節點
    final AbstractChannelHandlerContext next = findContextOutbound();
    // 獲取EventExecutor
    EventExecutor executor = next.executor();
    if (executor.inEventLoop()) {
        // 調用下一個節點的invokeRead接口
        next.invokeRead();
    } else {
        Runnable task = next.invokeReadTask;
        if (task == null) {
            next.invokeReadTask = task = new Runnable() {
                @Override
                public void run() {
                    next.invokeRead();
                }
            };
        }
        executor.execute(task);
    }
    return this;
}

通過pipeline的事件傳播機制,最終會調用到 AbstractChannel.AbstractUnsafe.beginRead() 方法:

@Override
public final void beginRead() {
    assertEventLoop();
    if (!isActive()) {
        return;
    }
    try {
        // 調用 doBeginRead();
        doBeginRead();
    } catch (final Exception e) {
        invokeLater(new Runnable() {
            @Override
            public void run() {
                pipeline.fireExceptionCaught(e);
            }
        });
        close(voidPromise());
    }
}

我們看下 AbstractNioChannel 對doBeginRead接口的實現邏輯

// 註冊一個OP_ACCEPT
@Override
protected void doBeginRead() throws Exception {
    // Channel.read() or ChannelHandlerContext.read() was called
    // 獲取channel註冊是的設置的 selectionKey
    final SelectionKey selectionKey = this.selectionKey;
    // selectionKey無效則返回
    if (!selectionKey.isValid()) {
        return;
    }
	
    readPending = true;
	// 前面講到channel在註冊的時候,這是 interestOps 設置的是 0
    final int interestOps = selectionKey.interestOps();
    // readInterestOp 在前面講到channel創建的時候,設置值爲 SelectionKey.OP_ACCEPT
    if ((interestOps & readInterestOp) == 0) {
        // 最終 selectionKey 的興趣集就會設置爲 SelectionKey.OP_ACCEPT
        // 表示隨時可以接收新連接的接入
        selectionKey.interestOps(interestOps | readInterestOp);
    }
}

總結

至此,我們就分析完了Channel的創建、初始化、註冊、綁定的流程。

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