1.4 Unsafe 欄位的初始化
我們簡單地提到了,在例項化NioSocketChannel 的過程中,會在父類 AbstractChannel 的構造方法中呼叫newUnsafe()來獲取一個 unsafe 例項。那麼 unsafe 是怎麼初始化的呢? 它的作用是什麼?
其實 unsafe 特別關鍵,它封裝了對 Java 底層 Socket 的操作,因此實際上是溝通Netty 上層和 Java 底層的重要的橋樑。那麼我們下面看一下 Unsafe 介面所提供的方法吧:
interface Unsafe {RecvByteBufAllocator.Handle recvBufAllocHandle();SocketAddress localAddress();SocketAddress remoteAddress();void register(EventLoop eventLoop, ChannelPromise promise);void bind(SocketAddress localAddress, ChannelPromise promise);void connect(SocketAddress remoteAddress, SocketAddress localAddress, ChannelPromise promise);void disconnect(ChannelPromise promise);void close(ChannelPromise promise);void closeForcibly();void deregister(ChannelPromise promise);void beginRead();void write(Object msg, ChannelPromise promise);void flush();ChannelPromise voidPromise();ChannelOutboundBuffer outboundBuffer();}
從原始碼中可以看出, 這些方法其實都是對應到相關的 Java 底層的 Socket 的操作。
繼續回到 AbstractChannel 的構造方法中,在這裡呼叫了 newUnsafe()獲取一個新的 unsafe 物件,而 newUnsafe()方法在NioSocketChannel 中被重寫了。來看程式碼:
protected AbstractNioUnsafe newUnsafe() {return new NioSocketChannelUnsafe();}
NioSocketChannel 的 newUnsafe()方法會返回一個NioSocketChannelUnsafe 例項。從這裡我們就可以確定了,在例項化的 NioSocketChannel 中的 unsafe 欄位,其實是一個NioSocketChannelUnsafe 的例項。
1.5 Pipeline 的初始化
上面我們分析了NioSocketChannel 的大體初始化過程, 但是我們漏掉了一個關鍵的部分,即 ChannelPipeline 的初始化。在 Pipeline 的註釋說明中寫到“Each channel has its own pipeline and it is created automatically when a newchannel is created.”,我們知道,在例項化一個 Channel 時,必然都要例項化一個 ChannelPipeline。而我們確實在AbstractChannel 的構造器看到了 pipeline 欄位被初始化為DefaultChannelPipeline 的例項。那麼我們就來看一下,DefaultChannelPipeline 構造器做了哪些工作。
protected DefaultChannelPipeline(Channel channel) {this.channel = ObjectUtil.checkNotNull(channel, "channel");succeededFuture = new SucceededChannelFuture(channel, null);voidPromise = new VoidChannelPromise(channel, true);tail = new TailContext(this);head = new HeadContext(this);head.next = tail;tail.prev = head;}
DefaultChannelPipeline 的構造器需要傳入一個 channel,而這個 channel 其實就是我們例項化的NioSocketChannel,DefaultChannelPipeline 會將這個 NioSocketChannel 物件儲存在 channel 欄位中。DefaultChannelPipeline 中還有兩個特殊的欄位,即 head 和 tail,這兩個欄位是雙向連結串列的頭和尾。其實在DefaultChannelPipeline 中,維護了一個以AbstractChannelHandlerContext 為節點元素的雙向連結串列,這個連結串列是Netty 實現 Pipeline 機制的關鍵。關於DefaultChannelPipeline 中的雙向連結串列以及它所起的作用,本節我們暫不講解,後續再做深入分析。先看看HeadContext的類繼承層次結構如下所示:
TailContext 的繼承層次結構如下所示:
我們可以看到,連結串列中 head 是一個 ChannelOutboundHandler,而 tail 則是一個 ChannelInboundHandler。接著看
HeadContext 的構造器:
HeadContext(DefaultChannelPipeline pipeline) {super(pipeline, null, HEAD_NAME, false, true);unsafe = pipeline.channel().unsafe();setAddComplete();}
它呼叫了父類 AbstractChannelHandlerContext 的構造器,並傳入引數 inbound = false,outbound = true。而TailContext 的構造器與 HeadContext 的相反,它呼叫了父類 AbstractChannelHandlerContext 的構造器,並傳入引數inbound = true,outbound = false。即 header 是一個 OutBoundHandler,而 tail 是一個 InBoundHandler,關於這一特徵,大家要特別注意,先記住,後續我們分析到Netty 的 Pipeline 時,我們會反覆用到 inbound 和 outbound 這兩個屬性。