源码剖析目的:
Netty 中的 ChannelPipeline、ChannelHandler 和 ChannelHandlerContext 是非常核心的组件,从源码分析 Netty 是如何设计这三个核心组件的,并分析是如何创建和协调工作的。
三者关系:
- 每当 ServerSocket 创建一个新的连接,就会创建一个 Socket, 对应的就是目标客户端
- 每一个新创建的 Socket 都将会分配一个新的 ChannelPipeline
- 每一个 ChannelPipeline 内部都含有多个 ChannelHandlerContext
- 他们一组组成双向链表,这些 Context 用于包装我们调用 addLast 方法时添加的 ChannelHandler
- ChannelSocket 和 ChannelPipeline 是一对一的关联关系,而piepline 内部的多个 Context 形成了 链表, Context 只是对 Handler 的封装
- 当一个请求进来的时候,会进入 Socket 对应的 pipeline, 并经过 pipeline 所有的 handler, 即设计模式中的过滤器模式
ChannelPipeline:
- 可以看出该接口继承了 inBound, outBound, Iterable 接口,表示他可以调用 数据 出站和入站 的方法,同时也能遍历内部的链表。 ChannelPipe 代表性的方法 基本上都是针对 handler 链表的插入,追加,删除,替换操作,类似是一个LinkedList。 同时也能返回 channel
- 这是一个 handler 的 list, handler 用于处理或拦截入站事件和出站事件,pipeline 实现了过滤器的高级形式, 以便用户控制事件如何处理以及 handler 在 pipeline 中如何交互
- 上图描述了一个典型的 handler 在 pipeline 中处理 I/O 事件的方式, IO 事件由 inboundHandler 或者 outBoundHandler 处理,并通过调用 ChannelHandlerContext.fireChannelRead 方法转发给其最近的处理程序
@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;
// 知道下一个 Inbound
} while (skipContext(ctx, currentExecutor, mask, MASK_ONLY_INBOUND));
return ctx;
}
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;
}
- 入站事件由入站处理程序以自下而上的方法处理,入站处理程序通常由图底部的 I/O 线程生成入站数据。 入站数据通常从如 SocketChannel.read(ByteBuffer) 获取。
- 通常一个 pipeline 有多个 handler
- 业务程序不能将线程阻塞,会影响 I/O 速度,进而影响整个 Netty 程序的性能。如果业务程序很快,可以放在 I/O 线程中,反之需要异步执行
ChannelHandler:
ChannelHandler 的作用就是处理 I/O 事件或拦截 I/O 事件,并将其转发给下一个处理程序 ChannelHandler。 Handler 处理事件时分入站和出站的,两个操作都是不同的,因此, Netty 定义了 两个子接口继承 ChannelHandler
public interface ChannelHandler {
/**
* 当把 ChannelHandler 添加到 pipeline 时被调用
*/
void handlerAdded(ChannelHandlerContext ctx) throws Exception;
/**
* 当从 pipeline 中移除时调用
*/
void handlerRemoved(ChannelHandlerContext ctx) throws Exception;
/**
* 当处理过程在 pipeline 发生异常时调用
*/
@Deprecated
void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception;
/**
*
*/
@Inherited
@Documented
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@interface Sharable {
// no value
}
}
ChannelInBoundHandler 入站事件接口:
- channelActive 用于当 Channel 处于活动状态时被调用
- chennalRead 当从 Channel 读取数据时被调用
- 程序员需要重写一些方法,当发生关注事件, 需要在方法中实现我们的业务逻辑,因为当事件发生时, Netty 会回调对应的方法
ChannelOutboundHandler 出站事件接口:
- bind 方法: 当请求将 Channel 绑定到本地地址时调用
- close 方法: 当请求关闭 Channel 时调用
- 出站操作都是一些连接和写数据类似的方法
ChannelDuplexHandler 处理出站和入站事件:
- ChannelDuplexHandler 间接事件了 入站接口并实现了出站接口,是一个通用的能够处理 入站事件和出站事件的类,实际开发中尽量少用,避免出站和入站的混淆。
ChannelHandlerContext:
UML图:
- ChannelHandlerContext 继承了出站方法调用接口和入站方法调用接口
- 这两个 invoker 就是针对入站或出站方法来的,就是在入站或出站 handler 的外层包装一层,达到在方法前后拦截并作一些特定操作的目的
- ChannelHandlerContext 不仅仅继承了他们两个的方法,同时也定义了一些自己的方法
- 这些方法能够获取 Context 上下文环境对应的 比如 channel, executor, handler, pipeline,内存分配器,关联的 handler 是否被删除
- Context 就是包装了 handler 相关的一切,以方便 Context 可以在 pipeline 方便的操作 handler
三者 创建过程:
- 任何一个 ChannelSocket 创建的同时都会创建一个 pipeline
- 当用户或系统内部调用 pipelind 的 add*** 方法添加 handler 时,都会创建一个包装这 handler 的 Context。
- 这些 Context 在 pipeline 中组成了双向链表
Socket 创建的时候 创建pipeline:
// AbstractChannel
protected AbstractChannel(Channel parent) {
this.parent = parent;
id = newId();
unsafe = newUnsafe();
pipeline = newChannelPipeline();
}
// AbstractChannel
protected DefaultChannelPipeline newChannelPipeline() {
return new DefaultChannelPipeline(this);
}
// DefaultChannelPipeline
protected DefaultChannelPipeline(Channel channel) {
// 将 channel 赋值给 channel 字段, 用于 pipeline 操作 channel
this.channel = ObjectUtil.checkNotNull(channel, "channel");
// 创建一个 future 和 promise, 用于异步回调使用
succeededFuture = new SucceededChannelFuture(channel, null);
voidPromise = new VoidChannelPromise(channel, true);
// 创建一个 inbound 的 tailContext, 创建一个即是 inbound 类型又是 outbound 类型的 headContext
// tailContext 和 HeadContext 非常的重要,所有 pipeline 中的事件都会流经他们
tail = new TailContext(this);
head = new HeadContext(this);
// 将两个 Context 互相连接, 形成双向链表
head.next = tail;
tail.prev = head;
}
在 add** 添加处理器的时候创建 Context**:
// DefaultChannelPipeline
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;
}
// DefaultChannelPipeline
/**
* @param group 线程池
* @param name null
* @param handler 自定义或者系统 handler
*/
public final ChannelPipeline addLast(EventExecutorGroup group, String name, ChannelHandler handler) {
final AbstractChannelHandlerContext newCtx;
// Netty 为了防止多线程导致安全问题,同步了这段代码
synchronized (this) {
// 检查这个 handler 实例是否是共享的,如果不是,并且已经被别的 pipeline 使用了,则抛出异常
checkMultiplicity(handler);
// 创建一个 Context, 每添加一个 handler 都会创建一个 关联 Context
newCtx = newContext(group, filterName(name, handler), handler);
// 将 Context 添加到链表中
addLast0(newCtx);
// 如果这个通道没有注册到 selector 上,就将这个 Context 添加到这个 pipeline 的代办任务中。 当注册好了以后,就会调用 callHandlerAdded0 方法(默认是什么都不做,用户可以实现这个方法)
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;
}
ChannelPipeline 调度 handler:
首先,当一个请求进来的时候,会第一个调用 pipeline 的相关方法,如果是入站事件,这些方法由 fire 开头,表示开始管道的流动。让后面的 handler 继续处理
package io.netty.channel;
import io.netty.channel.Channel.Unsafe;
import io.netty.util.ReferenceCountUtil;
import io.netty.util.ResourceLeakDetector;
import io.netty.util.concurrent.EventExecutor;
import io.netty.util.concurrent.EventExecutorGroup;
import io.netty.util.concurrent.FastThreadLocal;
import io.netty.util.internal.ObjectUtil;
import io.netty.util.internal.StringUtil;
import io.netty.util.internal.UnstableApi;
import io.netty.util.internal.logging.InternalLogger;
import io.netty.util.internal.logging.InternalLoggerFactory;
import java.net.SocketAddress;
import java.util.ArrayList;
import java.util.IdentityHashMap;
import java.util.Iterator;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.NoSuchElementException;
import java.util.WeakHashMap;
import java.util.concurrent.RejectedExecutionException;
import java.util.concurrent.atomic.AtomicReferenceFieldUpdater;
public class DefaultChannelPipeline implements ChannelPipeline {
public final ChannelPipeline fireChannelRegistered() {
AbstractChannelHandlerContext.invokeChannelRegistered(head);
return this;
}
public final ChannelPipeline fireChannelActive() {
AbstractChannelHandlerContext.invokeChannelActive(head);
return this;
}
public final ChannelPipeline fireChannelInactive() {
AbstractChannelHandlerContext.invokeChannelInactive(head);
return this;
}
public final ChannelPipeline fireExceptionCaught(Throwable cause) {
AbstractChannelHandlerContext.invokeExceptionCaught(head, cause);
return this;
}
public final ChannelPipeline fireUserEventTriggered(Object event) {
AbstractChannelHandlerContext.invokeUserEventTriggered(head, event);
return this;
}
public final ChannelPipeline fireChannelRead(Object msg) {
AbstractChannelHandlerContext.invokeChannelRead(head, msg);
return this;
}
public final ChannelPipeline fireChannelReadComplete() {
AbstractChannelHandlerContext.invokeChannelReadComplete(head);
return this;
}
public final ChannelPipeline fireChannelWritabilityChanged() {
AbstractChannelHandlerContext.invokeChannelWritabilityChanged(head);
return this;
}
}
说明: 可以看出来,这些方法都是 inbound 的方法,也就是入站事件,调用静态方法传入的 inbound 的类型 head handler。这些静态方法则会调用 head 的 ChannelInboundInvoker 接口的方法,再然后调用 handler 的真正方法
// DefaultChannelPipeline
public final ChannelPipeline fireChannelRead(Object msg) {
AbstractChannelHandlerContext.invokeChannelRead(head, msg);
return this;
}
// AbstractChannelHandlerContext
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);
}
});
}
}
// AbstractChannelHandlerContext
private void invokeChannelRead(Object msg) {
if (invokeHandler()) {
try {
// DON'T CHANGE
// Duplex handlers implements both out/in interfaces causing a scalability issue
// see https://bugs.openjdk.org/browse/JDK-8180450
final ChannelHandler handler = handler();
final DefaultChannelPipeline.HeadContext headContext = pipeline.head;
if (handler == headContext) {
headContext.channelRead(this, msg);
} else if (handler instanceof ChannelDuplexHandler) {
((ChannelDuplexHandler) handler).channelRead(this, msg);
} else {
((ChannelInboundHandler) handler).channelRead(this, msg);
}
} catch (Throwable t) {
invokeExceptionCaught(t);
}
} else {
fireChannelRead(msg);
}
}
pipeline 的 outbound 的 fire 方法实现:
package io.netty.channel;
import io.netty.channel.Channel.Unsafe;
import io.netty.util.ReferenceCountUtil;
import io.netty.util.ResourceLeakDetector;
import io.netty.util.concurrent.EventExecutor;
import io.netty.util.concurrent.EventExecutorGroup;
import io.netty.util.concurrent.FastThreadLocal;
import io.netty.util.internal.ObjectUtil;
import io.netty.util.internal.StringUtil;
import io.netty.util.internal.UnstableApi;
import io.netty.util.internal.logging.InternalLogger;
import io.netty.util.internal.logging.InternalLoggerFactory;
import java.net.SocketAddress;
import java.util.ArrayList;
import java.util.IdentityHashMap;
import java.util.Iterator;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.NoSuchElementException;
import java.util.WeakHashMap;
import java.util.concurrent.RejectedExecutionException;
import java.util.concurrent.atomic.AtomicReferenceFieldUpdater;
public class DefaultChannelPipeline implements ChannelPipeline {
@Override
public final ChannelFuture bind(SocketAddress localAddress) {
return tail.bind(localAddress);
}
@Override
public final ChannelFuture connect(SocketAddress remoteAddress) {
return tail.connect(remoteAddress);
}
@Override
public final ChannelFuture connect(SocketAddress remoteAddress, SocketAddress localAddress) {
return tail.connect(remoteAddress, localAddress);
}
@Override
public final ChannelFuture disconnect() {
return tail.disconnect();
}
@Override
public final ChannelFuture close() {
return tail.close();
}
@Override
public final ChannelFuture deregister() {
return tail.deregister();
}
}
这些都是 出站的实现,但是调用的是 outbound 类型的tail handler 来进行处理,因为这些都是 outbound 事件
public final ChannelFuture bind(SocketAddress localAddress) {
return tail.bind(localAddress);
}
出站是 tail 开始,入站从 head 开始。 因为出站是从内部外面写, 从tail 开始,能够让前面的 handler 进行处理,防止 handler 被遗漏,比如编码。 反之,入站当然是从 head 往内部输入,让后面的 handler 能够这些输入数据。比如解码。 因此虽然 head 也实现了 outbound 接口,但不是从 head 开始执行出站任务
Pipeline 过滤器调用过程:
- pipeline 首先会调用 Context 的静态方法 fire***, 并传入 Context
- 然后,静态方法调用 Context 的 invoker 方法,而 invoker 方法内部会调用该 Context 所包含的 Handler 的真正的 *** 方法,调用结束后,如果还需要继续向后传递,就调用 Context 的 fire***方法,循环往复。