Netty源碼分析:ChannelPipeline
我們在知道NioServerSocketChannel這個類的構造函數的調用鏈如下:
public NioServerSocketChannel() {
this(newSocket(DEFAULT_SELECTOR_PROVIDER));//newSocket的功能爲:利用SelectorProvider產生一個SocketChannelImpl對象。
}
public NioServerSocketChannel(ServerSocketChannel channel) {
super(null, channel, SelectionKey.OP_ACCEPT);
config = new NioServerSocketChannelConfig(this, javaChannel().socket());
}
//父類AbstractNioMessageChannel的構造函數
protected AbstractNioMessageChannel(Channel parent, SelectableChannel ch, int readInterestOp) {
super(parent, ch, readInterestOp);
}
//父類 AbstractNioChannel的構造函數
protected AbstractNioChannel(Channel parent, SelectableChannel ch, int readInterestOp) {
super(parent);
this.ch = ch;
this.readInterestOp = readInterestOp;//SelectionKey.OP_ACCEPT
try {
ch.configureBlocking(false);//設置當前的ServerSocketChannel爲非阻塞的
} 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的構造函數
protected AbstractChannel(Channel parent) {
this.parent = parent;
unsafe = newUnsafe();
pipeline = new DefaultChannelPipeline(this);
}
在如上的AbstractChannel構造函數中, 我們看到,使用 DefaultChannelPipeline類的實例初始化了一個 pipeline 屬性。
下面首先看下DefaultChannelPipeline的構造函數。
public DefaultChannelPipeline(AbstractChannel channel) {
if (channel == null) {
throw new NullPointerException("channel");
}
this.channel = channel;
tail = new TailContext(this);
head = new HeadContext(this);
head.next = tail;
tail.prev = head;
}
在如上的構造函數中,首先將與之關聯的Channel保存在屬性channel中,然後實例化了兩個對象:一個是TailContext 實例tail,一個是HeadContext 實例 head。然後將head和tail相互指向,構成了一個雙向鏈表。
HeadContext、TailContext的繼承體系結構如下:
從HeadContext和TailContext的繼承結構可以看到:這兩個類具有Context和Handler的雙重屬性,可以這麼說:head和tail既是一個ChannelHandlerContext也是一個ChannelHandler,原因在於:
1、這兩個類均繼承的是AbstractChannelHandlerContext這個類
2、均實現了ChannelHandler接口,只是HeadContext實現的是ChannelOutboundHandler,而TailContext實現的是ChannelInboundHandler接口。
而AbstractChannelHandlerContext類有如下兩個屬性:
abstract class AbstractChannelHandlerContext extends DefaultAttributeMap implements ChannelHandlerContext {
volatile AbstractChannelHandlerContext next;
volatile AbstractChannelHandlerContext prev;
因此,可以得到其實在 DefaultChannelPipeline 中, 維護了一個以 AbstractChannelHandlerContext 爲節點的雙向鏈表,其中此鏈表是 以head(HeadContext)作爲頭,以tail(TailContext)作爲尾的雙向鏈表,這個鏈表是 Netty 實現 Pipeline 機制的關鍵.
下面看下HeadContext、TailContext這兩個類的構造函數
HeadContext(DefaultChannelPipeline pipeline) {
super(pipeline, null, HEAD_NAME, false, true);
unsafe = pipeline.channel().unsafe();
}
TailContext(DefaultChannelPipeline pipeline) {
super(pipeline, null, TAIL_NAME, true, false);
}
AbstractChannelHandlerContext(DefaultChannelPipeline pipeline, EventExecutorGroup group, String name,
boolean inbound, boolean outbound) {
if (name == null) {
throw new NullPointerException("name");
}
channel = pipeline.channel;
this.pipeline = pipeline;
this.name = name;
if (group != null) {
// Pin one of the child executors once and remember it so that the same child executor
// is used to fire events for the same channel.
EventExecutor childExecutor = pipeline.childExecutors.get(group);
if (childExecutor == null) {
childExecutor = group.next();
pipeline.childExecutors.put(group, childExecutor);
}
executor = childExecutor;
} else {
executor = null;
}
this.inbound = inbound;
this.outbound = outbound;
}
HeadContext、TailContext這兩個構造函數都是調用了父類AbstractChannelHandlerContext如上的構造函數,只是參數有所不同。 有兩個參數需要額外注意:
1、對於HeadContex,傳入的參數:inbound=false,outbound=true;
2、對於TailContext,傳入的參數與HeadContext相反:inbound=true,outbound=false;
可以這麼來理解:inbound和outbound這兩個標誌用來區分鏈表的節點到底是inboundHandler還是outboundHandler。例如:從上面HeadContext和TailContext的繼承結構中可以得到HeadContext就是outboundHandler,而TailContext就是InboundHandler
自定義的handler是如何被添加到Pipeline所持有的雙向鏈表中的呢
一般情況,我們在初始化Bootstrap中,經常會看到如下類似的代碼,
b.group(bossGroup, workerGroup)
.channel(NioServerSocketChannel.class)
.handler(new SimpleServerHandler())
.childHandler(new ChannelInitializer<SocketChannel>() {
@Override
public void initChannel(SocketChannel ch) throws Exception {
}
});
其中SimpleServerHandler是我們自定義的Handler,那麼這個SimpleServerHandler是如何被添加到上面所介紹的Pipeline的雙向鏈表中去呢?
在博文 Netty源碼分析:服務端啓動全過程中可以看到在initAndRegister()方法中調用的init(channel)中有如下代碼:
final ChannelFuture initAndRegister() {
final Channel channel = channelFactory().newChannel();
try {
init(channel);
}
//...
}
ServerBootstrap.java
@Override
void init(Channel channel) throws Exception {
//...
ChannelPipeline p = channel.pipeline();
if (handler() != null) {
p.addLast(handler());
}
//...
}
結合前面的分析,我們知道 ChannelPipeline p = channel.pipeline();
得到的就是channel所持有的pipeline對象,而handler()方法返回就是通過b.handler(new SimpleServerHandler())設置的我們自定義的 SimpleServerHandler對象。然後將此Handler插入到Pipeline的雙向鏈表中。
下面我們來跟下addLast方法
DefaultChannelPipeline.java
@Override
public ChannelPipeline addLast(ChannelHandler... handlers) {
return addLast(null, handlers);
}
@Override
public ChannelPipeline addLast(EventExecutorGroup executor, ChannelHandler... handlers) {
if (handlers == null) {
throw new NullPointerException("handlers");
}
for (ChannelHandler h: handlers) {
if (h == null) {
break;
}
addLast(executor, generateName(h), h);
}
return this;
}
@Override
public ChannelPipeline addLast(EventExecutorGroup group, final String name, ChannelHandler handler) {
synchronized (this) {
checkDuplicateName(name);//檢查是否有重複的名字
AbstractChannelHandlerContext newCtx = new DefaultChannelHandlerContext(this, group, name, handler);
addLast0(name, newCtx);
}
return this;
}
這是addLast的調用鏈,下面主要分析最後一個addLast這個重載的方法。
基本邏輯爲:
1、首先調用 checkDuplicateName方法來檢查是否有重複名字的handler,如果有則拋異常。
private void checkDuplicateName(String name) {
if (name2ctx.containsKey(name)) {
throw new IllegalArgumentException("Duplicate handler name: " + name);
}
}
其中:
private final Map<String, AbstractChannelHandlerContext> name2ctx =
new HashMap<String, AbstractChannelHandlerContext>(4);
在DefaultChannelPipeline中搜索此字段,可以發現在所有添加handler的方法中addFirst0、addLast0等中出現瞭如下代碼語句:
name2ctx.put(name, newCtx);//重點
上面的name2ctx是一個Map,key爲handler的名字,而value則是handler本身。
即name2ctx中保存的是Pipeline中存在的所有handler,因此就可以在checkDuplicateName方法中利用 name2ctx.containsKey(name)
來判斷是否重明。
既然介紹到了這裏,就有必要說下handler的name是如何產生的?從上面的第2個addLast重載方法中可以看到是通過調用generateName(h)方法產生的,下面來看下這個方法:
private String generateName(ChannelHandler handler) {
WeakHashMap<Class<?>, String> cache = nameCaches[(int) (Thread.currentThread().getId() % nameCaches.length)];
Class<?> handlerType = handler.getClass();
String name;
synchronized (cache) {
name = cache.get(handlerType);
if (name == null) {
name = generateName0(handlerType);
cache.put(handlerType, name);
}
}
synchronized (this) {
// It's not very likely for a user to put more than one handler of the same type, but make sure to avoid
// any name conflicts. Note that we don't cache the names generated here.
if (name2ctx.containsKey(name)) {
String baseName = name.substring(0, name.length() - 1); // Strip the trailing '0'.
for (int i = 1;; i ++) {
String newName = baseName + i;
if (!name2ctx.containsKey(newName)) {
name = newName;
break;
}
}
}
}
return name;
}
private static String generateName0(Class<?> handlerType) {
return StringUtil.simpleClassName(handlerType) + "#0";
}
該函數的功能爲:如果nameCache中沒有該handler類的名字,則調用generatName0方法產生,產生的名字爲:類名+“#0”,例如在本例中SimpleServerHandler這個handler所產生的名字爲:SimpleServerHandler#0;如果nameCache中有該handler類的名字且name2ctx Map中有包括此名字的handler(添加了多個同類型的Handler到Pipeline中就會出現這種情況),則遞增後面的那個數字作爲名字直至不重複。
2、將handler包裝爲Context
爲什麼要利用如下的語句獎handler包裝成Context呢?
AbstractChannelHandlerContext newCtx = new DefaultChannelHandlerContext(this, group, name, handler);
原因在於:Pipeline中是以AbstractChannelHandlerContext爲節點的雙向鏈表。
下面我們來看下DefaultChannelHandlerContext這個類如下的構造函數
DefaultChannelHandlerContext(
DefaultChannelPipeline pipeline, EventExecutorGroup group, String name, ChannelHandler handler) {
super(pipeline, group, name, isInbound(handler), isOutbound(handler));
if (handler == null) {
throw new NullPointerException("handler");
}
this.handler = handler;
}
從DefaultChannelHandlerContext的繼承關係圖中可以得到該類繼承了AbstractChannelHandlerContext,與前面所介紹的HeadContext、TailContext一樣。還記不記得如下的一段話:
HeadContext、TailContext這兩個構造函數都是調用了父類AbstractChannelHandlerContext如上的構造函數,只是參數有所不同。 有兩個參數需要額外注意:
1)、對於HeadContex,傳入的參數:inbound=false,outbound=true;
2)、對於TailContext,傳入的參數與HeadContext相反:inbound=true,outbound=false;
類似,這裏的DefaultChannelHandlerContext構造函數也是調用了父類AbstractChannelHandlerContext的構造函數,只是,inbound、outbound這兩個參數是通過函數isInbound(handler)和isOutbound(handler)來得到,當handler實現了ChannelInboundHandler接口,則isInbound方法返回true,當handler實現了ChannelOutboundHandler接口,則isOunbound方法返回true。
private static boolean isInbound(ChannelHandler handler) {
return handler instanceof ChannelInboundHandler;
}
private static boolean isOutbound(ChannelHandler handler) {
return handler instanceof ChannelOutboundHandler;
}
就如前面所說:inbound和outbound這兩個標誌用來區分鏈表的節點到底是inboundHandler還是outboundHandler。
在我們的例子中SimpleServerHandler實現的是ChannelInboundHandlerAdapter類,該類的繼承結構如下,即可得我們自定義的SimpleServerHandler所對應的 DefaultChannelHandlerContext 的 inbound = true, outbound = false 。記住這一點,後面有用
private static class SimpleServerHandler extends ChannelInboundHandlerAdapter {
@Override
public void channelActive(ChannelHandlerContext ctx) throws Exception {
System.out.println("channelActive");
}
@Override
public void channelRegistered(ChannelHandlerContext ctx) throws Exception {
System.out.println("channelRegistered");
}
@Override
public void handlerAdded(ChannelHandlerContext ctx) throws Exception {
System.out.println("handlerAdded");
}
}
3、調用addLast0方法將handler加入到Pipeline的雙向鏈表的末尾
private void addLast0(final String name, AbstractChannelHandlerContext newCtx) {
checkMultiplicity(newCtx);
AbstractChannelHandlerContext prev = tail.prev;
newCtx.prev = prev;
newCtx.next = tail;
prev.next = newCtx;
tail.prev = newCtx;
name2ctx.put(name, newCtx);
callHandlerAdded(newCtx);
}
addLast0方法中就是典型的在鏈表中插入節點的代碼。即當調用了 addLast 方法後, 此 handler 就被添加到雙向鏈表中 tail 元素之前的位置了。
注意:上面addLast0方法中的最後一行代碼:callHandlerAdded(newCtx);
就是第一次使用我們自定義的handler(SimpleServerHandler)的地方,稍後將進行分析。
以上就分析了我們自定義的handler是如何被添加的Pipeline中去的。
那麼問題來了,那我們自定義的handler在哪裏被使用了呢?
自定義的handler在哪使用了呢
一般服務器端的代碼如下所示:
public final class SimpleServer {
public static void main(String[] args) throws Exception {
EventLoopGroup bossGroup = new NioEventLoopGroup(1);
EventLoopGroup workerGroup = new NioEventLoopGroup();
try {
ServerBootstrap b = new ServerBootstrap();
b.group(bossGroup, workerGroup)
.channel(NioServerSocketChannel.class)
.handler(new SimpleServerHandler())
.childHandler(new ChannelInitializer<SocketChannel>() {
@Override
public void initChannel(SocketChannel ch) throws Exception {
}
});
ChannelFuture f = b.bind(8887).sync();
f.channel().closeFuture().sync();
} finally {
bossGroup.shutdownGracefully();
workerGroup.shutdownGracefully();
}
}
private static class SimpleServerHandler extends ChannelInboundHandlerAdapter {
@Override
public void channelActive(ChannelHandlerContext ctx) throws Exception {
System.out.println("channelActive");
}
@Override
public void channelRegistered(ChannelHandlerContext ctx) throws Exception {
System.out.println("channelRegistered");
}
@Override
public void handlerAdded(ChannelHandlerContext ctx) throws Exception {
System.out.println("handlerAdded");
}
}
}
在運行此代碼時,在控制檯會輸出如下的內容:
handlerAdded
channelRegistered
channelActive
既然這三行的內容被打印出來了,就說明我們自定義的SimpleServerHandler的這些方法在某處依次被調用了,是吧,下面就主要看下。
1、SimpleServerHandler 的 handlerAdded 是在哪裏被調用了呢
在本博文稍前面一點,我說過如下的一段話:
上面addLast0方法中的最後一行代碼:callHandlerAdded(newCtx);
就是第一次使用我們自定義的handler(SimpleServerHandler)的地方,稍後將進行分析。
下面將來分析下callHandlerAdded(newCtx)方法
DefaultChannelPipiline.java
private void callHandlerAdded(final ChannelHandlerContext ctx) {
if (ctx.channel().isRegistered() && !ctx.executor().inEventLoop()) {
ctx.executor().execute(new Runnable() {
@Override
public void run() {
callHandlerAdded0(ctx);
}
});
return;
}
callHandlerAdded0(ctx);
}
上面方法主要是調用了callHandlerAdded0(ctx);
方法,代碼如下:
private void callHandlerAdded0(final ChannelHandlerContext ctx) {
ctx.handler().handlerAdded(ctx);
//...
}
如前面所介紹我們知道:ctx就是封裝了我們自定義的handler(SimpleServerHandler)的DefaultChannelHandlerContext實例,ctx.handler()返回的就是SimpleServeHandler實例,進行調用的此類中如下的 handlerAdded方法,進而在控制檯輸出:handlerAdded
@Override
public void handlerAdded(ChannelHandlerContext ctx) throws Exception {
System.out.println("handlerAdded");
}
2、SimpleServerHandler 的 channelRegistered 是在哪裏被調用了呢
跟蹤b.bind(8887)
代碼邏輯,發現在如下的initAndRegister()方法的註冊階段調用的register0()方法中出現瞭如下的代碼片段:
final ChannelFuture initAndRegister() {
final Channel channel = channelFactory().newChannel();
init(channel);
ChannelFuture regFuture = group().register(channel);
//...省略了一些此時不關注的代碼邏輯
}
AbstractChannel.java
private void register0(ChannelPromise promise) {
//...
doRegister();
registered = true;
safeSetSuccess(promise);
pipeline.fireChannelRegistered();
if (isActive()) {
pipeline.fireChannelActive();
}
//...
}
先看下:pipeline.fireChannelRegistered();
DefaultChannelPipeline.java
@Override
public ChannelPipeline fireChannelRegistered() {
head.fireChannelRegistered();
return this;
}
這裏直接調用了head(HeadContext實例)的fireChannelRegistered()方法,如下:
AbstractChannelHandlerContext.java
@Override
public ChannelHandlerContext fireChannelRegistered() {
final AbstractChannelHandlerContext next = findContextInbound();//分析
EventExecutor executor = next.executor();
if (executor.inEventLoop()) {
next.invokeChannelRegistered();
} else {
executor.execute(new OneTimeTask() {
@Override
public void run() {
next.invokeChannelRegistered();
}
});
}
return this;
}
private AbstractChannelHandlerContext findContextInbound() {
AbstractChannelHandlerContext ctx = this;
do {
ctx = ctx.next;
} while (!ctx.inbound);
return ctx;
}
該方法首先是調用如上的findContextInbound()方法從鏈頭head開始尋找第一個inbound=true的節點,看到沒,這就是前面特別強調inbound、outbound這兩個變量。很明顯,這裏找到的就是封裝我們自定義的SimpleServerHandler的DefaultChannelHandlerContext實例。
下面將看下 next.invokeChannelRegistered();
方法
private void invokeChannelRegistered() {
try {
((ChannelInboundHandler) handler()).channelRegistered(this);
} catch (Throwable t) {
notifyHandlerException(t);
}
}
顯然,封裝我們自定義的SimpleServerHandler的DefaultChannelHandlerContext實例對象調用如上方法中的handler()方法的就是我們自定義的 SimpleServerHandler 實例。然後調用此類的如下代碼的channelRegistered方法,這樣就在控制檯輸出了: channelRegistered。
@Override
public void channelRegistered(ChannelHandlerContext ctx) throws Exception {
System.out.println("channelRegistered");
}
3、SimpleServerHandler 的 channelActive方法 是在哪裏被調用了呢
接下來,看下:pipeline.fireChannelActive();要說明的是在register0()方法中isActive()方法返回的false,因此在register0()方法中是不會執行此語句的,至於在哪裏執行了該語句,可以參考博文 Netty源碼分析:服務端啓動全過程
DefaultChannelPipeline.java
@Override
public ChannelPipeline fireChannelActive() {
head.fireChannelActive();
if (channel.config().isAutoRead()) {
channel.read();
}
return this;
}
@Override
public ChannelHandlerContext fireChannelActive() {
final AbstractChannelHandlerContext next = findContextInbound();
EventExecutor executor = next.executor();
if (executor.inEventLoop()) {
next.invokeChannelActive();
} else {
executor.execute(new OneTimeTask() {
@Override
public void run() {
next.invokeChannelActive();
}
});
}
return this;
}
//AbstractChannelHandlerContext
private void invokeChannelActive() {
try {
((ChannelInboundHandler) handler()).channelActive(this);
} catch (Throwable t) {
notifyHandlerException(t);
}
}
pipeline.fireChannelActive()這個方法與上面分析的fireChannelRegistered()方法類似,也是通過調用head.fireChannelActive();
語句來從Pipeline的頭節點開始找到第一個Inbound=true的節點:包裝了我們自定義handler的DefaultChannelHandlerContext實例。然後調用此實例的invokeChannelActive()方法進而調用了我們自定的SimpleServerHandler的 channelActive方法,進而在控制檯輸出:channelActive。
@Override
public void channelActive(ChannelHandlerContext ctx) throws Exception {
System.out.println("channelActive");
}
小結
本博文從源碼的角度分析了ChannelPipeline這個類。瞭解了我們自定義的handler是被加入到Pipeline所持有的雙向鏈表中的,瞭解了我們自定義的handler中重載的幾種方法在哪裏被調用的。
需要記住的幾點如下:
1、在 Netty 中每個 Channel 都有且僅有一個 ChannelPipeline 與之對應。
2、ChannelPipeline是一個維護了一個以 AbstractChannelHandlerContext 爲節點的雙向鏈表,其中此鏈表是 以head(HeadContext)作爲頭,以tail(TailContext)作爲尾的雙向鏈表.
如上兩點關係用圖表示如下:(注:圖片截圖於參考資料)
在最後,感謝參考資料所列出的博文的作者,寫作思路相當清晰,自己看的很爽,然後自己也對照的源碼過了這整個過程,理解的更深刻了一些。