這一篇是 ChannelHandler 和 ChannelPipeline 的番外篇,主要從原始碼的角度來學習 ChannelHandler、ChannelHandler 和 ChannelPipeline 相互之間是如何建立聯絡和執行的。
一、新增 ChannelHandler
從上一篇的 demo 中可以看到在初始化 Server 和 Client 的時候,都會通過 ChannelPipeline 的 addLast 方法將 ChannelHandler 新增進去
// Server.java
// 部分程式碼片段
ServerBootstrap serverBootstrap = new ServerBootstrap();
NioEventLoopGroup group = new NioEventLoopGroup();
serverBootstrap.group(group)
.channel(NioServerSocketChannel.class)Channel
.localAddress(new InetSocketAddress("localhost", 9999))
.childHandler(new ChannelInitializer<SocketChannel>() {
protected void initChannel(SocketChannel socketChannel) throws Exception {
// 新增ChannelHandler
socketChannel.pipeline().addLast(new OneChannelOutBoundHandler());
socketChannel.pipeline().addLast(new OneChannelInBoundHandler());
socketChannel.pipeline().addLast(new TwoChannelInBoundHandler());
}
});
在上面的程式碼片段中,socketChannel.pipeline()方法返回的是一個型別是 DefaultChannelPipeline 的例項,DefaultChannelPipeline 實現了 ChannelPipeline 介面
DefaultChannelPipeline 的 addLast 方法實現如下:
// DefaultChannelPipeline.java
@Override
public final ChannelPipeline addLast(ChannelHandler... handlers) {
return addLast(null, handlers);
}
@Override
public final ChannelPipeline addLast(EventExecutorGroup executor, ChannelHandler... handlers) {
ObjectUtil.checkNotNull(handlers, "handlers");
for (ChannelHandler h: handlers) {
if (h == null) {
break;
}
addLast(executor, null, h);
}
return this;
}
經過一系列過載方法呼叫,最終進入到下面的 addLast 方法
// DefaultChannelPipeline.java
@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);
// If the registered is false it means that the channel was not registered on an eventLoop yet.
// In this case we add the context to the pipeline and add a task that will call
// ChannelHandler.handlerAdded(...) once the channel is registered.
if (!registered) {
newCtx.setAddPending();
callHandlerCallbackLater(newCtx, true);
return this;
}
EventExecutor executor = newCtx.executor();
if (!executor.inEventLoop()) {
callHandlerAddedInEventLoop(newCtx, executor);
return this;
}
}
callHandlerAdded0(newCtx);
return this;
}
在這個方法實現中,利用傳進來的 ChannelHandler 在 newContext 建立了一個 AbstractChannelHandlerContext 物件。newContext 方法實現如下:
// DefaultChannelPipeline.java
private AbstractChannelHandlerContext newContext(EventExecutorGroup group, String name, ChannelHandler handler) {
return new DefaultChannelHandlerContext(this, childExecutor(group), name, handler);
}
這裡建立並返回了一個型別為 DefaultChannelHandlerContext 的物件。從傳入的引數可以看到,在這裡將 ChannelHandlerContext、ChannelPipeline(this)和 ChannelHandler 三者建立了關係。
最後再看看 addLast0 方法實現:
// DefaultChannelPipeline.java
private void addLast0(AbstractChannelHandlerContext newCtx) {
AbstractChannelHandlerContext prev = tail.prev;
newCtx.prev = prev;
newCtx.next = tail;
prev.next = newCtx;
tail.prev = newCtx;
}
這裡出現了 AbstractChannelHandlerContext 的兩個屬性 prev 和 next,而 DefaultChannelPipeline 有一個屬性 tail。從實現邏輯上看起來像是建立了一個雙向連結串列的結構。下面的程式碼片段是關於 tail 和另一個相關屬性 head:
// DefaultChannelPipeline.java
public class DefaultChannelPipeline implements ChannelPipeline {
final AbstractChannelHandlerContext head;
final AbstractChannelHandlerContext tail;
// ......
protected DefaultChannelPipeline(Channel channel) {
// ......
tail = new TailContext(this);
head = new HeadContext(this);
head.next = tail;
tail.prev = head;
}
// ......
}
// HeaderContext.java
final class HeadContext extends AbstractChannelHandlerContext implements ChannelOutboundHandler, ChannelInboundHandler {
// ......
@Override
public ChannelHandler handler() {
return this;
}
//......
}
// TailContext.java
final class TailContext extends AbstractChannelHandlerContext implements ChannelInboundHandler {
// ......
@Override
public ChannelHandler handler() {
return this;
}
// ......
}
DefaultChannelPipeline 內部維護了兩個 AbstractChannelHandlerContext 型別的屬性 head、tail,而這兩個屬性又都實現了 ChannelHandler 的子介面。構造方法裡將這兩個屬性維護成了一個雙向連結串列。結合上面的 addLast0 方法實現,可以知道在新增 ChannelHandler 的時候,其實是在對 ChannelPipeline 內部維護的雙向連結串列做插入操作。
下面是 ChannelHandlerContext 相關類的結構
所以,對 ChannelPipeline 做 add 操作新增 ChannelHandler 後,內部結構大體是這樣的:
所有的 ChannelHandlerContext 組成了一個雙向連結串列,頭部是 HeadContext,尾部是 TailContext,因為它們都實現了 ChannelHandler 介面,所以它們內部的 Handler 也是自己。每次新增一個 ChannelHandler,將會新建立一個 DefaultChannelHandler 關聯,並按照一定的順序插入到連結串列中。
在 AbstractChannelHandlerContext 類裡有一個屬性 executionMask,在構造方法初始化時會對它進行賦值
// AbstractChannelHandlerContext.java
// 省略部分程式碼
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);
// Its ordered if its driven by the EventLoop or the given Executor is an instanceof OrderedEventExecutor.
ordered = executor == null || executor instanceof OrderedEventExecutor;
}
// 省略部分程式碼
mask 是一個靜態方法,來自於 ChannelHandlerMask 類
// ChannelHandlerMask.java
// 省略部分程式碼
/**
* Return the {@code executionMask}.
*/
static int mask(Class<? extends ChannelHandler> clazz) {
// Try to obtain the mask from the cache first. If this fails calculate it and put it in the cache for fast
// lookup in the future.
Map<Class<? extends ChannelHandler>, Integer> cache = MASKS.get();
Integer mask = cache.get(clazz);
if (mask == null) {
mask = mask0(clazz);
cache.put(clazz, mask);
}
return mask;
}
/**
* Calculate the {@code executionMask}.
*/
private static int mask0(Class<? extends ChannelHandler> handlerType) {
int mask = MASK_EXCEPTION_CAUGHT;
try {
if (ChannelInboundHandler.class.isAssignableFrom(handlerType)) {
mask |= MASK_ALL_INBOUND;
if (isSkippable(handlerType, "channelRegistered", ChannelHandlerContext.class)) {
mask &= ~MASK_CHANNEL_REGISTERED;
}
if (isSkippable(handlerType, "channelUnregistered", ChannelHandlerContext.class)) {
mask &= ~MASK_CHANNEL_UNREGISTERED;
}
if (isSkippable(handlerType, "channelActive", ChannelHandlerContext.class)) {
mask &= ~MASK_CHANNEL_ACTIVE;
}
if (isSkippable(handlerType, "channelInactive", ChannelHandlerContext.class)) {
mask &= ~MASK_CHANNEL_INACTIVE;
}
if (isSkippable(handlerType, "channelRead", ChannelHandlerContext.class, Object.class)) {
mask &= ~MASK_CHANNEL_READ;
}
if (isSkippable(handlerType, "channelReadComplete", ChannelHandlerContext.class)) {
mask &= ~MASK_CHANNEL_READ_COMPLETE;
}
if (isSkippable(handlerType, "channelWritabilityChanged", ChannelHandlerContext.class)) {
mask &= ~MASK_CHANNEL_WRITABILITY_CHANGED;
}
if (isSkippable(handlerType, "userEventTriggered", ChannelHandlerContext.class, Object.class)) {
mask &= ~MASK_USER_EVENT_TRIGGERED;
}
}
if (ChannelOutboundHandler.class.isAssignableFrom(handlerType)) {
mask |= MASK_ALL_OUTBOUND;
if (isSkippable(handlerType, "bind", ChannelHandlerContext.class,
SocketAddress.class, ChannelPromise.class)) {
mask &= ~MASK_BIND;
}
if (isSkippable(handlerType, "connect", ChannelHandlerContext.class, SocketAddress.class,
SocketAddress.class, ChannelPromise.class)) {
mask &= ~MASK_CONNECT;
}
if (isSkippable(handlerType, "disconnect", ChannelHandlerContext.class, ChannelPromise.class)) {
mask &= ~MASK_DISCONNECT;
}
if (isSkippable(handlerType, "close", ChannelHandlerContext.class, ChannelPromise.class)) {
mask &= ~MASK_CLOSE;
}
if (isSkippable(handlerType, "deregister", ChannelHandlerContext.class, ChannelPromise.class)) {
mask &= ~MASK_DEREGISTER;
}
if (isSkippable(handlerType, "read", ChannelHandlerContext.class)) {
mask &= ~MASK_READ;
}
if (isSkippable(handlerType, "write", ChannelHandlerContext.class,
Object.class, ChannelPromise.class)) {
mask &= ~MASK_WRITE;
}
if (isSkippable(handlerType, "flush", ChannelHandlerContext.class)) {
mask &= ~MASK_FLUSH;
}
}
if (isSkippable(handlerType, "exceptionCaught", ChannelHandlerContext.class, Throwable.class)) {
mask &= ~MASK_EXCEPTION_CAUGHT;
}
} catch (Exception e) {
// Should never reach here.
PlatformDependent.throwException(e);
}
return mask;
}
@SuppressWarnings("rawtypes")
private static boolean isSkippable(
final Class<?> handlerType, final String methodName, final Class<?>... paramTypes) throws Exception {
return AccessController.doPrivileged(new PrivilegedExceptionAction<Boolean>() {
@Override
public Boolean run() throws Exception {
Method m;
try {
m = handlerType.getMethod(methodName, paramTypes);
} catch (NoSuchMethodException e) {
if (logger.isDebugEnabled()) {
logger.debug(
"Class {} missing method {}, assume we can not skip execution", handlerType, methodName, e);
}
return false;
}
return m != null && m.isAnnotationPresent(Skip.class);
}
});
}
// 省略部分程式碼
以上程式碼實現邏輯是這樣的:當建立一個 ChannelHandlerContext 時,會與一個 ChannelHandler 繫結,同時會將傳遞進來的 ChannelHandler 進行解析,解析當前 ChannelHandler 支援哪些回撥方法,並通過位運算得到一個結果儲存在 ChannelHandlerContext 的 executionMask 屬性裡。注意 m.isAnnotationPresent(Skip.class)這裡,ChannelHandler 的基類 ChannelInboundHandlerAdapter 和 ChannelOutboundHandlerAdapter 裡的回撥方法上都有@Skip 註解,當繼承了這兩個類並重寫了某個回撥方法後,這個方法上的註解就會被覆蓋掉,解析時就會被認為當前 ChannelHandler 支援這個回撥方法。
下面是每個回撥方法對應的掩碼
// ChannelHandlerMask.java
final class ChannelHandlerMask {
// Using to mask which methods must be called for a ChannelHandler.
static final int MASK_EXCEPTION_CAUGHT = 1;
static final int MASK_CHANNEL_REGISTERED = 1 << 1;
static final int MASK_CHANNEL_UNREGISTERED = 1 << 2;
static final int MASK_CHANNEL_ACTIVE = 1 << 3;
static final int MASK_CHANNEL_INACTIVE = 1 << 4;
static final int MASK_CHANNEL_READ = 1 << 5;
static final int MASK_CHANNEL_READ_COMPLETE = 1 << 6;
static final int MASK_USER_EVENT_TRIGGERED = 1 << 7;
static final int MASK_CHANNEL_WRITABILITY_CHANGED = 1 << 8;
static final int MASK_BIND = 1 << 9;
static final int MASK_CONNECT = 1 << 10;
static final int MASK_DISCONNECT = 1 << 11;
static final int MASK_CLOSE = 1 << 12;
static final int MASK_DEREGISTER = 1 << 13;
static final int MASK_READ = 1 << 14;
static final int MASK_WRITE = 1 << 15;
static final int MASK_FLUSH = 1 << 16;
static final int MASK_ONLY_INBOUND = MASK_CHANNEL_REGISTERED |
MASK_CHANNEL_UNREGISTERED | MASK_CHANNEL_ACTIVE | MASK_CHANNEL_INACTIVE | MASK_CHANNEL_READ |
MASK_CHANNEL_READ_COMPLETE | MASK_USER_EVENT_TRIGGERED | MASK_CHANNEL_WRITABILITY_CHANGED;
private static final int MASK_ALL_INBOUND = MASK_EXCEPTION_CAUGHT | MASK_ONLY_INBOUND;
static final int MASK_ONLY_OUTBOUND = MASK_BIND | MASK_CONNECT | MASK_DISCONNECT |
MASK_CLOSE | MASK_DEREGISTER | MASK_READ | MASK_WRITE | MASK_FLUSH;
private static final int MASK_ALL_OUTBOUND = MASK_EXCEPTION_CAUGHT | MASK_ONLY_OUTBOUND;
}
二、ChannelHandler 處理訊息
我們以訊息讀取和寫入為例,來看看在 ChannelPipeline 裡的各個 ChannelHandler 是如何按照順序處理訊息和事件的。
讀取訊息
當 Channel 讀取到訊息後,會在以下地方呼叫 ChannelPipeline 的 fireChannelRead 方法:
// AbstractNioMessageClient.java
private final class NioMessageUnsafe extends AbstractNioUnsafe {
// 省略程式碼
@Override
public void read() {
// ......
for (int i = 0; i < size; i ++) {
readPending = false;
pipeline.fireChannelRead(readBuf.get(i));
}
// ......
}
// 省略程式碼
}
// DefaultChannelPipeline.java
// 省略程式碼
@Override
public final ChannelPipeline fireChannelRead(Object msg) {
AbstractChannelHandlerContext.invokeChannelRead(head, msg);
return this;
}
// 省略程式碼
可以看到,通過 AbstractChannelHandlerContext 的 invokeChannelRead 方法,傳遞 head,從頭部開始觸發讀取事件。
// AbstractChannelHandlerContext.java
// 省略程式碼
static void invokeChannelRead(final AbstractChannelHandlerContext next, Object msg) {
final Object m = next.pipeline.touch(ObjectUtil.checkNotNull(msg, "msg"), next);
EventExecutor executor = next.executor();
if (executor.inEventLoop()) {
next.invokeChannelRead(m);
} else {
executor.execute(new Runnable() {
@Override
public void run() {
next.invokeChannelRead(m);
}
});
}
}
private void invokeChannelRead(Object msg) {
if (invokeHandler()) {
try {
((ChannelInboundHandler) handler()).channelRead(this, msg);
} catch (Throwable t) {
invokeExceptionCaught(t);
}
} else {
fireChannelRead(msg);
}
}
/**
* Makes best possible effort to detect if {@link ChannelHandler#handlerAdded(ChannelHandlerContext)} was called
* yet. If not return {@code false} and if called or could not detect return {@code true}.
*
* If this method returns {@code false} we will not invoke the {@link ChannelHandler} but just forward the event.
* This is needed as {@link DefaultChannelPipeline} may already put the {@link ChannelHandler} in the linked-list
* but not called {@link ChannelHandler#handlerAdded(ChannelHandlerContext)}.
*/
private boolean invokeHandler() {
// Store in local variable to reduce volatile reads.
int handlerState = this.handlerState;
return handlerState == ADD_COMPLETE || (!ordered && handlerState == ADD_PENDING);
}
// 省略程式碼
在這裡通過 invokeHandler 方法對當前 ChannelHandler 進行狀態檢查,通過了就將呼叫當前 ChannelHandler 的 channelRead 方法,沒有通過將呼叫 fireChannelRead 方法將事件傳遞到下一個 ChannelHandler 上。而 head 的型別是 HeadContext,本身也實現了 ChannelInBoundHandler 介面,所以這裡呼叫的是 HeadContext 的 channelRead 方法。
// DefaultChannelPipeline.java
final class HeadContext extends AbstractChannelHandlerContext implements ChannelOutboundHandler, ChannelInboundHandler {
@Override
public void channelRead(ChannelHandlerContext ctx, Object msg) {
ctx.fireChannelRead(msg);
}
}
這裡對訊息沒有做任何處理,直接將讀取訊息傳遞下去。接下來看看 ChannelHandlerContext 的 fireChannelRead 做了什麼
// AbstractChannelHandlerContext.java
@Override
public ChannelHandlerContext fireChannelRead(final Object msg) {
invokeChannelRead(findContextInbound(MASK_CHANNEL_READ), msg);
return this;
}
private AbstractChannelHandlerContext findContextInbound(int mask) {
AbstractChannelHandlerContext ctx = this;
EventExecutor currentExecutor = executor();
do {
ctx = ctx.next;
} while (skipContext(ctx, currentExecutor, mask, MASK_ONLY_INBOUND));
return ctx;
}
private static boolean skipContext(
AbstractChannelHandlerContext ctx, EventExecutor currentExecutor, int mask, int onlyMask) {
// Ensure we correctly handle MASK_EXCEPTION_CAUGHT which is not included in the MASK_EXCEPTION_CAUGHT
return (ctx.executionMask & (onlyMask | mask)) == 0 ||
// We can only skip if the EventExecutor is the same as otherwise we need to ensure we offload
// everything to preserve ordering.
//
// See https://github.com/netty/netty/issues/10067
(ctx.executor() == currentExecutor && (ctx.executionMask & mask) == 0);
}
這裡實現的邏輯是這樣的:在雙向連結串列中,從當前 ChannelHandlerContext 節點向後尋找,直到找到匹配 MASK_CHANNEL_READ 這個掩碼的 ChannelHandlerContext。從上面的章節裡可以直到 ChannelHandlerContext 的屬性裡儲存了當前 ChannelHandler 支援(重寫)的所有方法掩碼的位運算值,通過位運算的結果來找到實現了對應方法的最近的 ChannelHandlerContext。
連結串列最後一個節點是 TailContext
// DefaultChannelPipeline.java
final class TailContext extends AbstractChannelHandlerContext implements ChannelInboundHandler {
@Override
public void channelRead(ChannelHandlerContext ctx, Object msg) {
onUnhandledInboundMessage(ctx, msg);
}
/**
* Called once a message hit the end of the {@link ChannelPipeline} without been handled by the user
* in {@link ChannelInboundHandler#channelRead(ChannelHandlerContext, Object)}. This method is responsible
* to call {@link ReferenceCountUtil#release(Object)} on the given msg at some point.
*/
protected void onUnhandledInboundMessage(ChannelHandlerContext ctx, Object msg) {
onUnhandledInboundMessage(msg);
if (logger.isDebugEnabled()) {
logger.debug("Discarded message pipeline : {}. Channel : {}.",
ctx.pipeline().names(), ctx.channel());
}
}
/**
* Called once a message hit the end of the {@link ChannelPipeline} without been handled by the user
* in {@link ChannelInboundHandler#channelRead(ChannelHandlerContext, Object)}. This method is responsible
* to call {@link ReferenceCountUtil#release(Object)} on the given msg at some point.
*/
protected void onUnhandledInboundMessage(Object msg) {
try {
logger.debug(
"Discarded inbound message {} that reached at the tail of the pipeline. " +
"Please check your pipeline configuration.", msg);
} finally {
ReferenceCountUtil.release(msg);
}
}
}
可以看到,tail 節點的 channelRead 方法沒有將事件繼續傳遞下去,只是釋放了 msg。
寫入訊息
我們通過 OneChannelInBoundHandler 的 channelReadComplete 方法裡的 ctx.write 方法來看
// AbstractChannelHandlerContext.java
// 省略程式碼
@Override
public ChannelFuture write(Object msg) {
return write(msg, newPromise());
}
@Override
public ChannelFuture write(final Object msg, final ChannelPromise promise) {
write(msg, false, promise);
return promise;
}
private void write(Object msg, boolean flush, ChannelPromise promise) {
ObjectUtil.checkNotNull(msg, "msg");
try {
if (isNotValidPromise(promise, true)) {
ReferenceCountUtil.release(msg);
// cancelled
return;
}
} catch (RuntimeException e) {
ReferenceCountUtil.release(msg);
throw e;
}
final AbstractChannelHandlerContext next = findContextOutbound(flush ?
(MASK_WRITE | MASK_FLUSH) : MASK_WRITE);
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)) {
// We failed to submit the WriteTask. We need to cancel it so we decrement the pending bytes
// and put it back in the Recycler for re-use later.
//
// See https://github.com/netty/netty/issues/8343.
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));
return ctx;
}
// 省略程式碼
通過呼叫一系列過載的 write 方法後,通過 findContextOutbound 方法在雙向連結串列裡向前尋找最近的實現了 write 或 writeAndFlush 方法的 ChannelHandlerContext,呼叫它的 invokeWrite 或 invokeWriteAndFlush 方法。
// AbstractChannelHandlerContext.java
// 省略程式碼
void invokeWrite(Object msg, ChannelPromise promise) {
if (invokeHandler()) {
invokeWrite0(msg, promise);
} else {
write(msg, promise);
}
}
private void invokeWrite0(Object msg, ChannelPromise promise) {
try {
((ChannelOutboundHandler) handler()).write(this, msg, promise);
} catch (Throwable t) {
notifyOutboundHandlerException(t, promise);
}
}
// 省略程式碼
同理於讀取訊息,這裡經過 invokeHandler 方法檢查通過後呼叫找到的 ChannelHandlerContext 的 ChannelHandler,沒有通過檢查,則繼續向前傳遞寫入事件。當寫入訊息傳遞到頭部,呼叫 HeadContext 的 write 方法
// DefaultChannelPipeline.java
final class HeadContext extends AbstractChannelHandlerContext implements ChannelOutboundHandler, ChannelInboundHandler {
private final Unsafe unsafe;
// 省略程式碼
@Override
public void write(ChannelHandlerContext ctx, Object msg, ChannelPromise promise) {
unsafe.write(msg, promise);
}
@Override
public void flush(ChannelHandlerContext ctx) {
unsafe.flush();
}
// 省略程式碼
}
最終通過呼叫 unsafe 的 write 方法寫入訊息。
最後,從上面的實現裡可以發現,在將 ChannelHandler 加入到 ChannelPipeline 時,要把 ChannelOutBoundHandler 型別的 ChannelHandler 進來新增在前面,否則在 ChannelInBoundHandler 寫入訊息時,在它後面的 ChannelOutBoundHandler 將無法獲取到事件。