前言
前面的文章中寫了Channel實例化、Channel初始化、Channel註冊、異步通知機制、客戶端發起連接、事件的輪詢和處理機制。Netty作爲client/server高效通信框架,事件在ChannelPipeline是如何傳遞的,本文就聊聊這事。
一、事件傳遞過程
ChannelPipeline隨着Channel的創建而創建,在 Netty2# Netty組件之Channel初始化 文章中梳理了ChannelPipeline、ChannelHandlerContext、ChannelHandler的關係如下圖。
ChannelPipeline大管道維護了一個ChannelHandlerContext鏈表,頭部爲HeadContext,尾部爲TailContext。事件傳播會沿着鏈表逐級向下傳遞。
當ChannelHandlerContext創建時,它是Inbound還是Outbound,是哪個方向的就確定了。下面分析是這種身份是如何確定的。
AbstractChannelHandlerContext(DefaultChannelPipeline pipeline, EventExecutor executor,
String name, Class<? extends ChannelHandler> handlerClass) {
this.name = ObjectUtil.checkNotNull(name, "name");
this.pipeline = pipeline;
this.executor = executor;
this.executionMask = mask(handlerClass); // 註解@1
ordered = executor == null || executor instanceof OrderedEventExecutor;
}
註解@1 父類的構造函數中有一個mask(handlerClass)方法,這個方法確定了ChannelHandlerContext的身份。如下代碼ChannelInboundHandler.class.isAssignableFrom(handlerType)即如果是ChannelInboundHandler類型mask |= MASK_ALL_INBOUND;反之如果是ChannelOutboundHandler類型,mask |= MASK_ALL_OUTBOUND;通過賦予mask不同的值來區分是哪個方向的Handler。isSkippable中的邏輯判斷主要對加註解@Skip的方法不再進行事件回調。
private static int mask0(Class<? extends ChannelHandler> handlerType) {
int mask = MASK_EXCEPTION_CAUGHT;
try {
if (ChannelInboundHandler.class.isAssignableFrom(handlerType)) {
mask |= MASK_ALL_INBOUND; // 標識爲INBOUND
if (isSkippable(handlerType, "channelRegistered", ChannelHandlerContext.class)){
mask &= ~MASK_CHANNEL_REGISTERED;
}
// ...
}
if (ChannelOutboundHandler.class.isAssignableFrom(handlerType)) {
mask |= MASK_ALL_OUTBOUND; // 標識爲OUTBOUND
if (isSkippable(handlerType, "bind", ChannelHandlerContext.class,
SocketAddress.class, ChannelPromise.class)) {
mask &= ~MASK_BIND;
}
// ...
}
// ...
} catch (Exception e) {
// ...
}
return mask;
}
示例入口
ChannelFuture future = b.connect(HOST, PORT).sync();
future.channel().writeAndFlush("Hi");
以writeAndFlush方法跟蹤下Outbound事件在ChannelPipeline的傳遞過程。
@Override
public final ChannelFuture writeAndFlush(Object msg) {
return tail.writeAndFlush(msg); // 註解@2
}
private void write(Object msg, boolean flush, ChannelPromise promise) {
// ...
final AbstractChannelHandlerContext next = findContextOutbound(flush ?
(MASK_WRITE | MASK_FLUSH) : MASK_WRITE); // 註解@3
final Object m = pipeline.touch(msg, next);
EventExecutor executor = next.executor();
if (executor.inEventLoop()) {
if (flush) {
next.invokeWriteAndFlush(m, promise);
} else {
next.invokeWrite(m, promise);
}
} else {
final WriteTask task = WriteTask.newInstance(next, m, promise, flush);
if (!safeExecute(executor, task, promise, m, !flush)) {
//...
task.cancel();
}
}
}
private AbstractChannelHandlerContext findContextOutbound(int mask) {
AbstractChannelHandlerContext ctx = this;
EventExecutor currentExecutor = executor();
do {
ctx = ctx.prev;
} while (skipContext(ctx, currentExecutor, mask, MASK_ONLY_OUTBOUND)); // 註解@4
return ctx;
}
private static boolean skipContext(
AbstractChannelHandlerContext ctx, EventExecutor currentExecutor, int mask, int onlyMask) {
return (ctx.executionMask & (onlyMask | mask)) == 0 ||
(ctx.executor() == currentExecutor && (ctx.executionMask & mask) == 0); // 註解@5
}
註解@2 從鏈表尾部TailContext開始執行。
註解@3 我們示例writeAndFlush所以findContextOutbound的mask爲(MASK_WRITE | MASK_FLUSH)
註解@4 循環鏈表查找,注意skipContext是判斷的跳過邏輯。我們查找Outbound的ChannelHandlerContext,遇到Inbound的都會跳過。
註解@5 判斷是否跳過,這段邏輯是位操作,不好閱讀。下面示例抽取各個入參的值測試下:如果下一個ChannelHandlerContext爲inBound,則skipContext返回true,從而在查找outBound的do/while循環中跳過。
int executionMask = MASK_EXCEPTION_CAUGHT |= MASK_ALL_INBOUND; // inBound 身份標識
int mask = MASK_WRITE | MASK_FLUSH; // writeAndFlush 標識
int onlyMask = MASK_ONLY_OUTBOUND; // outBound操作集合
System.out.println((executionMask & (onlyMask | mask))==0);
輸出爲:true
小結: outBound事件在ChannelPipeline中傳遞時,只會選擇身份爲outBound的ChannelHandlerContext執行。
以上一篇文章 Netty6# Netty之事件輪詢與處理 當有新的客戶端的連接時觸發unsafe.read()執行。具體爲NioMessageUnsafe#read()方法。具體爲上文中註解@17。
// 入口
pipeline.fireChannelRead(readBuf.get(i));
// 實現
@Override
public final ChannelPipeline fireChannelRead(Object msg) {
AbstractChannelHandlerContext.invokeChannelRead(head, msg);
return this;
}
private void invokeChannelRead(Object msg) {
if (invokeHandler()) {
try {
((ChannelInboundHandler) handler()).channelRead(this, msg);
} catch (Throwable t) {
notifyHandlerException(t);
}
} else {
fireChannelRead(msg); // 註解@6
}
}
註解@6 下面的方法findContextInbound(MASK_CHANNEL_READ),只查找Inbound的ChannelHandlerContext。具體邏輯與Outbound傳遞過程相似,不再重複。
@Override
public ChannelHandlerContext fireChannelRead(final Object msg) {
invokeChannelRead(findContextInbound(MASK_CHANNEL_READ), msg);
return this;
}
小結: inBound事件在ChannelPipeline中傳遞時,只會選擇身份爲inBound的ChannelHandlerContext執行。
二、ChannelPipeline重要API
ChannelPipeline的默認實現爲DefaultChannelPipeline,以下API源碼梳理均來自該實現類。
接口 | 說明 |
---|---|
addFirst | 在ChannelPipeline中,將ChannelHandlerContext添加HeadContext的後面。 |
addLast | 在ChannelPipeline中,將ChannelHandlerContext添加到TailContext的前面。 |
addBefore | 在ChannelPipeline中,將ChannelHandlerContext添加到某ChannelHandlerContext的前面。 |
remove | 在ChannelPipeline中,將ChannelHandlerContext從鏈表中移除。 |
replace | 在ChannelPipeline中,將新newCtx在鏈表中替換就得oldCtx。 |
說明:在ChannelPipeline中,將ChannelHandlerContext添加HeadContext的後面。
@Override
public final ChannelPipeline addFirst(EventExecutorGroup group, String name, ChannelHandler handler) {
final AbstractChannelHandlerContext newCtx;
synchronized (this) {
checkMultiplicity(handler); // 註解@7
name = filterName(name, handler); // 註解@8
newCtx = newContext(group, name, handler);
addFirst0(newCtx); // 註解@9
// ...
}
callHandlerAdded0(newCtx); // 註解@10
return this;
}
註解@7:checkMultiplicity()方法校驗ChannelHandler是否重複。如果ChannelHandler中有註解@Sharable標識,則允許同一個ChannelHandler添加到不同的ChannelPipeline中。未加@Sharable註解的ChannelHandler只允許添加到一個ChannelPipeline。
private static void checkMultiplicity(ChannelHandler handler) {
if (handler instanceof ChannelHandlerAdapter) {
ChannelHandlerAdapter h = (ChannelHandlerAdapter) handler;
if (!h.isSharable() && h.added) { // 判斷@Sharable註解和該Handler是否已經被添加
throw new ChannelPipelineException(
h.getClass().getName() +
" is not a @Sharable handler, so can't be added or removed multiple times.");
}
h.added = true;
}
}
@Sharable使用示例
@Sharable
public class StringEncoder extends MessageToMessageEncoder<CharSequence> {
}
註解@8: ChannelHandler名字判斷,沒有設置Handler名字則自動生成一個;設置了Handler名字,不能與該ChannelPipeline其他ChannelHandler名字重複。
private String filterName(String name, ChannelHandler handler) {
if (name == null) {
return generateName(handler);
}
checkDuplicateName(name);
return name;
}
註解@9 創建DefaultChannelHandlerContext,並加入將其加入到鏈表中。
private void addFirst0(AbstractChannelHandlerContext newCtx) {
AbstractChannelHandlerContext nextCtx = head.next;
newCtx.prev = head;
newCtx.next = nextCtx;
head.next = newCtx;
nextCtx.prev = newCtx;
}
經過上面鏈表的順序調整,addFirst將ChannelHandlerContext添加到了HeadContext的後面。
註解@10 :當ChannelHandler添加ChannelPipeline後,回調該Handler的handlerAdded方法,也是通知機制的常用做法。
說明:在ChannelPipeline中,將ChannelHandlerContext添加到TailContext的前面。
@Override
public final ChannelPipeline addLast(EventExecutorGroup group, String name, ChannelHandler handler) {
final AbstractChannelHandlerContext newCtx;
synchronized (this) {
checkMultiplicity(handler);
newCtx = newContext(group, filterName(name, handler), handler);
addLast0(newCtx); // 註解@11
// ...
}
callHandlerAdded0(newCtx);
return this;
}
註解@11 其他邏輯同addFirst,addLast0將HandlerContext添加到TailContext的前一個位置。
private void addLast0(AbstractChannelHandlerContext newCtx) {
AbstractChannelHandlerContext prev = tail.prev;
newCtx.prev = prev;
newCtx.next = tail;
prev.next = newCtx;
tail.prev = newCtx;
}
說明:在ChannelPipeline中,將ChannelHandlerContext添加到某ChannelHandlerContext的前面。
public final ChannelPipeline addBefore(
EventExecutorGroup group, String baseName, String name, ChannelHandler handler) {
final AbstractChannelHandlerContext newCtx;
final AbstractChannelHandlerContext ctx;
synchronized (this) {
checkMultiplicity(handler);
name = filterName(name, handler);
ctx = getContextOrDie(baseName); // 註解@12
newCtx = newContext(group, name, handler);
addBefore0(ctx, newCtx); // 註解@13
// ...
}
callHandlerAdded0(newCtx);
return this;
}
註解@12 根據傳入的baseName在ChannelPipleline查找對應的HandlerContext。
private AbstractChannelHandlerContext getContextOrDie(String name) {
AbstractChannelHandlerContext ctx = (AbstractChannelHandlerContext) context(name);
if (ctx == null) {
throw new NoSuchElementException(name);
} else {
return ctx;
}
}
註解@13 添加到鏈表中,添加到baseName的前面。
private static void addBefore0(AbstractChannelHandlerContext ctx, AbstractChannelHandlerContext newCtx) {
newCtx.prev = ctx.prev;
newCtx.next = ctx;
ctx.prev.next = newCtx;
ctx.prev = newCtx;
}
說明:在ChannelPipeline中,將ChannelHandlerContext添加到指定的ChannelHandlerContext的後面。
public final ChannelPipeline addAfter(
EventExecutorGroup group, String baseName, String name, ChannelHandler handler) {
final AbstractChannelHandlerContext newCtx;
final AbstractChannelHandlerContext ctx;
synchronized (this) {
checkMultiplicity(handler);
name = filterName(name, handler);
ctx = getContextOrDie(baseName);
newCtx = newContext(group, name, handler);
addAfter0(ctx, newCtx); // 註解@14
}
callHandlerAdded0(newCtx);
return this;
}
註解@14: 調整鏈表順序,調整方式同上。
private static void addAfter0(AbstractChannelHandlerContext ctx, AbstractChannelHandlerContext newCtx) {
newCtx.prev = ctx;
newCtx.next = ctx.next;
ctx.next.prev = newCtx;
ctx.next = newCtx;
}
說明:在ChannelPipeline中,將ChannelHandlerContext從鏈表中移除。
private AbstractChannelHandlerContext remove(final AbstractChannelHandlerContext ctx) {
assert ctx != head && ctx != tail; // 註解@15
synchronized (this) {
atomicRemoveFromHandlerList(ctx); // 註解@16
}
// ...
callHandlerRemoved0(ctx);
return ctx;
}
註解@15 被移除的ChannelHandlerContext不能是HeadContext和TailContext。
註解@16 通過調整前後ChannelHandlerContext的指針指向實現移除操作。
private synchronized void atomicRemoveFromHandlerList(AbstractChannelHandlerContext ctx) {
AbstractChannelHandlerContext prev = ctx.prev;
AbstractChannelHandlerContext next = ctx.next;
prev.next = next;
next.prev = prev;
}
說明:在ChannelPipeline中,將新newCtx在鏈表中替換就得oldCtx。
private ChannelHandler replace(
final AbstractChannelHandlerContext ctx, String newName, ChannelHandler newHandler) {
assert ctx != head && ctx != tail; // 註解@17
final AbstractChannelHandlerContext newCtx;
synchronized (this) {
checkMultiplicity(newHandler);
if (newName == null) {
newName = generateName(newHandler);
} else {
boolean sameName = ctx.name().equals(newName);
if (!sameName) {
checkDuplicateName(newName);
}
}
newCtx = newContext(ctx.executor, newName, newHandler);
replace0(ctx, newCtx); // 註解@18
// ...
}
//...
return ctx.handler();
}
註解@17 被替換的ChannelHandlerContext不能是HeadContext和TailContext。
註解@18 下面替換邏輯中,首先將oldCtx的前後指針暫存;newCtx前後指針指向剛纔的暫存;把暫存的pre的next指向newCtx,暫存的next的prev指向newCtx,此時newCtx已經替換到了鏈表中;將oldCtx的prev和next都指向了newCtx,目的爲了已經進入了oldCtx的數據正確流轉無論是inbound還是outbound數據。
private static void replace0(AbstractChannelHandlerContext oldCtx, AbstractChannelHandlerContext newCtx) {
AbstractChannelHandlerContext prev = oldCtx.prev;
AbstractChannelHandlerContext next = oldCtx.next;
newCtx.prev = prev;
newCtx.next = next;
// Finish the replacement of oldCtx with newCtx in the linked list.
// Note that this doesn't mean events will be sent to the new handler immediately
// because we are currently at the event handler thread and no more than one handler methods can be invoked
// at the same time (we ensured that in replace().)
prev.next = newCtx;
next.prev = newCtx;
// update the reference to the replacement so forward of buffered content will work correctly
oldCtx.prev = newCtx;
oldCtx.next = newCtx;
}
小結: ChannelPipeline提供了方便的API對鏈表中的ChannelHandlerContext進行插入、刪除、添加、替換操作。
本文分享自微信公衆號 - 瓜農老梁(gh_01130ae30a83)。
如有侵權,請聯繫 [email protected] 刪除。
本文參與“OSC源創計劃”,歡迎正在閱讀的你也加入,一起分享。