1. 程式人生 > >Java Netty 學習(八) - Netty的Channel

Java Netty 學習(八) - Netty的Channel

在普通IO中,通過把機器傳輸抽象成java.net.Socket流,也就是SocketServerSocket
NIO時,則變為了java.nio.channels.Channel,也是作為傳輸的管道,具體可看Java Netty 學習(四) - NIO基礎知識Channel和Pipe
那今天就一起學習Netty中的Channel,看它又是一種怎麼樣的設計形式。

介紹

在netty的Channel中,提供了很多共用的API,相比與大大降低了直接使用Socket的複雜性。另外,它是Netty網路通訊的主題,由他負責直接對端進行網路通訊,註冊,以及相關的資料操作。
Channel提供了許多預定義的專門實現的類:

  • EmbeddedChannel:用於測試ChannelHandler的類
  • LocalServerChannel:同一個JVM內部實現client和server之間通訊的Channel
  • NioDatagramChannel:使用UDP進行網路傳輸,和java.net.DatagramSocket對應
  • NioSctpChannel:基於Sctp協議的操作
  • NioSocketChannel:使用TCP進行網路傳輸

結構

Channel接口裡面主要有以下方法:

  • ChannelId id():返回唯一的channel id
  • EventLoop eventLoop():返回專屬被註冊進入的EventLoop
  • Channel parent():返回父channel
  • ChannelConfig config():返回channel的config
  • SocketAddress localAddress():返回自己的socketAddress
  • SocketAddress remoteAddress():返回遠端的Address
  • Unsafe unsafe():返回unsafe,內部實現的unsafe,一些方法只能用於I/O執行緒呼叫,其他的如localAddress則可以被使用者執行緒呼叫。

狀態

按照Channel定義的模型,Channel有四個狀態

  • ChannelUnregistered:Channel已經被建立,但還未註冊到EventLoop
  • ChannelRegistered:Channel已經被註冊到EventLoop
  • ChannelActive:Channel處於活動狀態,他現在可以接受和傳送資料了
  • ChannelInactive:Channel沒有連線到遠端節點

NioSocketChannel

下面看看NioSocketChannel相關特性:
在這裡插入圖片描述

賦值

記得上一篇文章給出的例項,Bootstrap呼叫的建造者模式的過程中用到了NioSocketChannel

 b.group(group)
                .channel(NioSocketChannel.class)
                .handler(new ChannelInitializer<SocketChannel>() {
            @Override
            protected void initChannel(SocketChannel ch) throws Exception {
                ch.pipeline().addLast(new HelloClientHandler());
            }
        });

下面跟著原始碼看看裡面做了些什麼事:

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

再看channelFactory:

    public B channelFactory(io.netty.channel.ChannelFactory<? extends C> channelFactory) {
        return channelFactory((ChannelFactory<C>) channelFactory);
    }

最後設定以下channelFactory:

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

在channel方法裡面,首先還是返回一個AbstractBootstrap的子類以供繼續建造物件,接著把一個
ReflectiveChannelFactory物件傳遞給了channelFactory,並且以傳入的channelClass為引數,名字上可知是返回一個channel類,包裝了很多層工廠方法為了實現程式碼的統一性。
進入原始碼可知,ReflectiveChannelFactory是一個繼承了ChannelFactory的物件裡面有一個newChannel方法如下:

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

執行了傳入引數的Class的newInstance方法。

現在已經清楚知道Bootstrap中的channel方法做了什麼事了,就是把一個相應型別的ChannelFactory傳進去,這樣就可以產生Channel。
那麼,NioSocketChannel什麼時候被初始化的呢?

初始化

首先在NioSocketChannel中打斷點,跟蹤到呼叫服務:
在這裡插入圖片描述

可以很清楚的看到
由connect->doResolveAndConnect->initAndRegister->newChannel一級一級呼叫:
返回一個非同步的ChannelFuture

ChannelFuture future = b.connect(this.address, this.port);

再到:

 public ChannelFuture connect(String inetHost, int inetPort) {
        return connect(InetSocketAddress.createUnresolved(inetHost, inetPort));
    }

接著在doResolveAndConnect執行初始化一個Channel,但此時並沒有連線上,就相當先拿到一個預設配置的Channel,但是現在還沒有用。
接著執行initAndRegister,並在這裡面呼叫:

    final ChannelFuture initAndRegister() {
        Channel channel = null;
        try {
            channel = channelFactory.newChannel();
            init(channel);
        } catch (Throwable t) {
            if (channel != null) {
                channel.unsafe().closeForcibly();
            }
            return new DefaultChannelPromise(channel, GlobalEventExecutor.INSTANCE).setFailure(t);
        }

        ChannelFuture regFuture = config().group().register(channel);
        if (regFuture.cause() != null) {
            if (channel.isRegistered()) {
                channel.close();
            } else {
                channel.unsafe().closeForcibly();
            }
        }
        return regFuture;
    }

從而再經過JNI等一步步反射呼叫NioSocketChannel的構造方法。

    public NioSocketChannel() {
        this(DEFAULT_SELECTOR_PROVIDER);
    }

用途

那麼,Channel建立了,也初始化了,那麼它在Netty裡面,地位是啥呢?
先看看一張圖:
Netty工作原理

上圖有以下幾個概念:

  1. 客戶端連線成功,就新建一個Channel來接受(程式碼中是先初始化一個Channel,連線成功後賦值屬性)
  2. EventLoopGroup分配一個EventLoop給Channel,並註冊到該EventLoop,channel生命週期內都和該EventLoop在一起(不變)同時,註冊時獲得selectionKey
  3. Channel進行網路連線、關閉和讀寫,生成相對應的event(改變selectinKey資訊),觸發eventloop排程執行緒進行執行
  4. EventLoop執行排程ChannelPipline來處理使用者業務邏輯

並且,多個Channel可以同時註冊到同一個EventLoop,並按照順序迴圈處理Channel中的邏輯。

總結

Channel就類似於一個容器,或者理解為Socket,通過它與另一端進行通訊,由EventLoop來處理Channel中產生的事件,在事件中實現相關邏輯,接下來文章將逐步學習其他元件。

幫助:

  1. Netty In Action
  2. https://www.cnblogs.com/TomSnail/p/6192885.html
  3. https://blog.csdn.net/u011801189/article/details/54729412