簡介
我們知道channel是netty中用於溝通ByteBuf和Event的橋樑,在netty服務的建立過程中,不管是客戶端的Bootstrap還是伺服器端的ServerBootstrap,都需要呼叫channel方法來指定對應的channel型別。
那麼netty中channel到底有哪些型別呢?他們具體是如何工作的呢?一起來看看。
channel和ServerChannel
Channel在netty中是一個interface,在Channel中定義了很多非常有用的方法。通常來說如果是客戶端的話,對應的channel就是普通的channel。如果是伺服器端的話,對應的channel就應該是ServerChannel。
那麼客戶端channel和伺服器端channel有什麼區別呢?我們先來看下ServerChannel的定義:
public interface ServerChannel extends Channel {
// This is a tag interface.
}
可以看到ServerChannel繼承自Channel,表示服務端的Channel也是Channel的一種。
但是很奇怪的是,你可以看到ServerChannel中並沒有新增任何新的方法。也就是說ServerChannel和Channel在定義上本質是一樣的。你可以把ServerChannel看做是一個tag interface而已。
那麼channel和ServerChannel有什麼聯絡呢?
我們知道在Channel中定義了一個parent方法:
Channel parent();
這個parent方法返回的是該channel的父channel。我們以最簡單的LocalChannel和LocalServerChannel為例,來檢視一下他們的父子關係到底是怎麼建立的。
首先parent的值是通過LocalChannel和LocalServerChannel的公共父類AbstractChannel來實現的:
protected AbstractChannel(Channel parent) {
this.parent = parent;
id = newId();
unsafe = newUnsafe();
pipeline = newChannelPipeline();
}
對於LocalChannel來說,可以通過它的建構函式來設定parent channel:
protected LocalChannel(LocalServerChannel parent, LocalChannel peer) {
super(parent);
config().setAllocator(new PreferHeapByteBufAllocator(config.getAllocator()));
this.peer = peer;
localAddress = parent.localAddress();
remoteAddress = peer.localAddress();
}
我們知道當client端想要連線到server端的時候,需要呼叫client channel的connect方法,對於LocalChannel來說,它的connect方法實際上呼叫的是pipeline的connect方法:
public ChannelFuture connect(SocketAddress remoteAddress) {
return pipeline.connect(remoteAddress);
}
最終會呼叫LocalChannel中的LocalUnsafe.connect方法。
而在LocalUnsafe.connect方法中又會呼叫serverChannel.serve方法。
serverChannel的newLocalChannel方法會建立新的LocalChannel並返回:
protected LocalChannel newLocalChannel(LocalChannel peer) {
return new LocalChannel(this, peer);
}
這裡使用newLocalChannel方法建立的LocalChannel就是serverChannel的子channel。
最後返回的LocalChannel會作為client端LocalChannel的peer channel而存在。
netty中channel的實現
在netty中channel和Serverchannel有很多個實現類,用來完成不同的業務功能。
為了循序漸進一步步瞭解netty中channel的祕密,這裡我們先來探討一下netty中channel的基本實現LocalChannel和LocalServerChannel的工作原理。
下圖是LocalChannel和LocalServerChannel的主要繼承和依賴關係:
<img src="https://img-blog.csdnimg.cn/1d9c19d567084c199dfade76c8a0d52a.png" style="zoom:67%;" />
從圖中可以看到,LocalChannel繼承自AbstractChannel而LocalServerChannel則繼承自AbstractServerChannel。
因為ServerChannel繼承自Channel,所以很自然的AbstractServerChannel又繼承自AbstractChannel。
接下來,我們通過對比分析AbstractChannel和AbstractServerChannel,LocalChannel和LocalServerChannel來一探netty中channel實現的底層原理。
AbstractChannel和AbstractServerChannel
AbstractChannel是對Channel的最基本的實現。先來看下AbstractChannel中都有那些功能。
首先AbstractChannel中定義了Channel介面中要返回的一些和channel相關的基本屬性,包括父channel,channel id,pipline,localAddress,remoteAddress,eventLoop等,如下所示:
private final Channel parent;
private final ChannelId id;
private final DefaultChannelPipeline pipeline;
private volatile SocketAddress localAddress;
private volatile SocketAddress remoteAddress;
private volatile EventLoop eventLoop;
private final Unsafe unsafe;
要注意的是AbstractChannel中還有一個非常中要的Unsafe屬性。
Unsafe本身就是Channel介面中定義的一個內部介面,它的作用就是為各個不同型別的transport提供特定的實現。
從名字可以看出Unsafe是一個不安全的實現,它只是在netty的原始碼中使用,它是不能出現在使用者程式碼中的。或者你可以將Unsafe看做是底層的實現,而包裹他的AbstractChannel或者其他的Channel是對底層實現的封裝,對於普通使用者來說,他們只需要使用Channel就可以了,並不需要深入到更底層的內容。
另外,對於Unsafe來說,除了下面幾個方法之外,剩餘的方法必須從 I/O thread中呼叫:
localAddress()
remoteAddress()
closeForcibly()
register(EventLoop, ChannelPromise)
deregister(ChannelPromise)
voidPromise()
和一些基本的狀態相關的資料:
private volatile boolean registered;
private boolean closeInitiated;
除了基本的屬性設定和讀取之外,我們channel中最終要的方法主要有下面幾個:
- 用於建立伺服器端服務的bind方法:
public ChannelFuture bind(SocketAddress localAddress) {
return pipeline.bind(localAddress);
}
- 用於客戶端建立和伺服器端連線的connect方法:
public ChannelFuture connect(SocketAddress remoteAddress) {
return pipeline.connect(remoteAddress);
}
- 斷開連線的disconnect方法:
public ChannelFuture disconnect() {
return pipeline.disconnect();
}
- 關閉channel的close方法:
public ChannelFuture close() {
return pipeline.close();
}
- 取消註冊的deregister方法:
public ChannelFuture deregister() {
return pipeline.deregister();
}
- 重新整理資料的flush方法:
public Channel flush() {
pipeline.flush();
return this;
}
- 讀取資料的read方法:
public Channel read() {
pipeline.read();
return this;
}
- 寫入資料的方法:
public ChannelFuture write(Object msg) {
return pipeline.write(msg);
}
可以看到這些channel中的讀寫和繫結工作都是由和channel相關的pipeline來執行的。
其實也很好理解,channel只是一個通道,和資料相關的操作,還是需要在管道中執行。
我們以bind方法為例子,看一下AbstractChannel中的pipline是怎麼實現的。
在AbstractChannel中,預設的pipeline是DefaultChannelPipeline,它的bind方法如下:
public void bind(
ChannelHandlerContext ctx, SocketAddress localAddress, ChannelPromise promise) {
unsafe.bind(localAddress, promise);
}
這裡的unsafe實際上就是AbstractChannel中的unsafe,unsafe中的bind方法最終會呼叫AbstractChannel中的dobind方法:
protected abstract void doBind(SocketAddress localAddress) throws Exception;
所以歸根到底,如果是基於AbstractChannel的各種實現,那麼只需要實現它的這些do*方法即可。
好了,AbstractChannel的介紹完畢了。 我們再來看一下AbstractServerChannel。AbstractServerChannel繼承自AbstractChannel並且實現了ServerChannel介面。
public abstract class AbstractServerChannel extends AbstractChannel implements ServerChannel
我們知道ServerChannel和Channel實際上是相同的,所以AbstractServerChannel只是在AbstractChannel的實現上進行了一些調整。
在AbstractServerChannel中,我們一起來觀察一下AbstractServerChannel和AbstractChannel到底有什麼不同。
首先是AbstractServerChannel的建構函式:
protected AbstractServerChannel() {
super(null);
}
建構函式中,super的parent channel是null,表示ServerChannel本身並不存在父channel,這是ServerChannel和client channel
的第一個不同之處。因為server channel可以通過worker event loop來接受client channel,所以server channel是client channel的父channel。
另外,我們還觀察幾個方法的實現:
public SocketAddress remoteAddress() {
return null;
}
對於ServerChannel來說不需要主動連線到遠端的Server,所以並沒有remoteAddress。
另外,因為斷開連線是由client端主動呼叫的,所以server channel的doDisconnect會丟擲不支援該操作的異常:
protected void doDisconnect() throws Exception {
throw new UnsupportedOperationException();
}
同時ServerChannel只是用來負責accept和client channel建立關聯關係,所以server channel本身並不支援向channel內進行的write操作,所以這個doWrite方法也是不支援的:
protected void doWrite(ChannelOutboundBuffer in) throws Exception {
throw new UnsupportedOperationException();
}
最後ServerChannel只支援bind操作,所以DefaultServerUnsafe中的connect方法也會丟擲UnsupportedOperationException.
LocalChannel和LocalServerChannel
LocalChannel和LocalServerChannel是AbstractChannel和AbstractServerChannel的最基本的實現。從名字就可以看出來,這兩個Channel是本地channel,我們來看一下這兩個Channel的具體實現。
首先我們來看一下LocalChannel,LocalChannel有幾點對AbstractChannel的擴充套件。
第一個擴充套件點是LocalChannel中新增了channel的幾個狀態:
private enum State { OPEN, BOUND, CONNECTED, CLOSED }
通過不同的狀態,可以對channel進行更加細粒度的控制。
另外LocalChannel中新增了一個非常重要的屬性:
private volatile LocalChannel peer;
因為LocalChannel表示的是客戶端channel,所以這個peer表示的是client channel對等的server channel。接下來我們看一下具體的實現。
首先是LocalChannel的建構函式:
protected LocalChannel(LocalServerChannel parent, LocalChannel peer) {
super(parent);
config().setAllocator(new PreferHeapByteBufAllocator(config.getAllocator()));
this.peer = peer;
localAddress = parent.localAddress();
remoteAddress = peer.localAddress();
}
LocalChannel可以接受一個LocalServerChannel作為它的parent,還有一個LocalChannel作為它的對等channel。
那麼這個peer是怎麼建立的呢?
我們來看一下LocalUnsafe中connect的邏輯。
if (state != State.BOUND) {
// Not bound yet and no localAddress specified - get one.
if (localAddress == null) {
localAddress = new LocalAddress(LocalChannel.this);
}
}
if (localAddress != null) {
try {
doBind(localAddress);
} catch (Throwable t) {
safeSetFailure(promise, t);
close(voidPromise());
return;
}
}
首先判斷當前channel的狀態,如果是非繫結狀態,那麼需要進行繫結操作。首先根據傳入的LocalChannel建立對應的LocalAddress。
這個LocalAddress只是LocalChannel的一種表現形式,並沒有什麼特別的功能。
我們來看一下這個doBind方法:
protected void doBind(SocketAddress localAddress) throws Exception {
this.localAddress =
LocalChannelRegistry.register(this, this.localAddress,
localAddress);
state = State.BOUND;
}
LocalChannelRegistry中維護了一個static的map,這個map中存放的就是註冊過的Channel.
這裡註冊是為了在後面方便的拿到對應的channel。
註冊好localChannel之後,接下來就是根據註冊好的remoteAddress來獲取對應的LocalServerChannel,最後呼叫LocalServerChannel的serve方法建立一個新的peer channel:
Channel boundChannel = LocalChannelRegistry.get(remoteAddress);
if (!(boundChannel instanceof LocalServerChannel)) {
Exception cause = new ConnectException("connection refused: " + remoteAddress);
safeSetFailure(promise, cause);
close(voidPromise());
return;
}
LocalServerChannel serverChannel = (LocalServerChannel) boundChannel;
peer = serverChannel.serve(LocalChannel.this);
serve方法首先會建立一個新的LocalChannel:
protected LocalChannel newLocalChannel(LocalChannel peer) {
return new LocalChannel(this, peer);
}
如果我們把之前的Localchannel稱為channelA,這裡建立的新的LocalChannel稱為channelB。那麼最後的結果就是channelA的peer是channelB,而channelB的parent是LocalServerChannel,channelB的peer是channelA。
這樣就構成了一個對等channel之間的關係。
接下來我們看下localChannel的read和write到底是怎麼工作的。
首先看一下LocalChannel的doWrite方法:
Object msg = in.current();
...
peer.inboundBuffer.add(ReferenceCountUtil.retain(msg));
in.remove();
...
finishPeerRead(peer);
首先從ChannelOutboundBuffer拿到要寫入的msg,將其加入peer的inboundBuffer中,最後呼叫finishPeerRead方法。
從方法名字可以看出finishPeerRead就是呼叫peer的read方法。
事實上該方法會呼叫peer的readInbound方法,從剛剛寫入的inboundBuffer中讀取訊息:
private void readInbound() {
RecvByteBufAllocator.Handle handle = unsafe().recvBufAllocHandle();
handle.reset(config());
ChannelPipeline pipeline = pipeline();
do {
Object received = inboundBuffer.poll();
if (received == null) {
break;
}
pipeline.fireChannelRead(received);
} while (handle.continueReading());
pipeline.fireChannelReadComplete();
}
所以,對於localChannel來說,它的寫實際上寫入到peer的inboundBuffer中。然後再呼叫peer的讀方法,從inboundBuffer中讀取資料。
相較於localChannel來說,localServerChannel多了一個serve方法,用來建立peer channel,並呼叫readInbound開始從inboundBuffer中讀取資料。
總結
本章詳細介紹了channel和serverChannel的區別,和他們的最簡單的本地實現。希望大家對channel和serverChannel的工作原理有了最基本的瞭解。
本文已收錄於 http://www.flydean.com/04-2-netty-channel-vs-serverchannel-md/
最通俗的解讀,最深刻的乾貨,最簡潔的教程,眾多你不知道的小技巧等你來發現!
歡迎關注我的公眾號:「程式那些事」,懂技術,更懂你!