文章目录
系列文章目录
Netty核心源码分析(一),Netty的Server端启动过程源码分析
Netty核心源码分析(二),Netty的Server端接收请求过程源码分析
Netty核心源码分析(三)业务请求执行关键——ChannelPipeline、ChannelHandler、ChannelHandlerContext源码分析
Netty核心源码分析(四)心跳检测源码分析
Netty核心源码分析(五)核心组件EventLoop源码分析
一、ChannelPipeline、ChannelHandler、ChannelHandlerContext
1、三者关系
- 每当 ServerSocket 创建一个新的连接,就会创建一个 Socket,对应的就是目标客户端。
2)每一个新创建的 Socket 都将会分配一个全新的 ChannelPipeline (以下简称 pipeline) - 每一个 ChannelPipeline 内部都含有多个 ChannelHandlerContext (以下简称 Context)
4)他们一起组成了双向链表,这些 Context 用于包装我们调用 addLast 方法时添加的 ChannelHandler (以下简称
handler)
上图中: ChannelSocket 和 ChannelPipeline 是一对一的关联关系,而 pipeline 内部的多个 Context 形成了链表,Context 只是对 Handler 的封装。
当一个请求进来的时候,会进入 Socket 对应的 pipeline,并经过 pipeline 所有的 handler,就是设计模式中的过滤器模式。
二、ChannelPipeline源码分析
1、ChannelPipeline接口设计
我们可以看到,ChannelPipeline继承了ChannelInboundInvoker、ChannelOutboundInvoker、Iterable接口,表示可以调用数据出站、入站的方法,同时支持迭代遍历。
ChannelPipeline内部的方法基本都是针对handler链表的增删改查。
2、ChannelPipeline处理事件
ChannelPipeline接口上提供了一张图:
这是一个handler的list,handler用于处理或拦截入站事件和出站事件,pipeline实现了过滤器的高级形式,以便用户控制事件如何处理以及handler在pipeline中如何交互。
上图描述了一个典型的handler在pipeline中处理IO事件的方式,IO事件由inboundHandler或者outBoundHandler处理,并通过调用ChannelHandlerContext.fireChannelRead方法转发给其最近的处理程序。
// 在ChannelInboundHandlerAdapter中的channelRead方法中有着对该方法的默认实现,默认就是调用了ChannelHandlerContext的fireChannelRead方法:
@Override
public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
ctx.fireChannelRead(msg);
}
// io.netty.channel.AbstractChannelHandlerContext#fireChannelRead
@Override
public ChannelHandlerContext fireChannelRead(final Object msg) {
invokeChannelRead(findContextInbound(), msg);
return this;
}
其中,findContextInbound方法,通过循环的方式查找下一个inboundHandler:
// io.netty.channel.AbstractChannelHandlerContext#findContextInbound
private AbstractChannelHandlerContext findContextInbound() {
AbstractChannelHandlerContext ctx = this;
do {
ctx = ctx.next;
} while (!ctx.inbound);
return ctx;
}
invokeChannelRead用于执行查找出的下一个Handler的channelRead方法,相当于一个执行器链。
// io.netty.channel.AbstractChannelHandlerContext#invokeChannelRead(io.netty.channel.AbstractChannelHandlerContext, java.lang.Object)
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);
}
});
}
}
// io.netty.channel.AbstractChannelHandlerContext#invokeChannelRead(java.lang.Object)
private void invokeChannelRead(Object msg) {
if (invokeHandler()) {
try {
((ChannelInboundHandler) handler()).channelRead(this, msg);
} catch (Throwable t) {
notifyHandlerException(t);
}
} else {
fireChannelRead(msg);
}
}
outBound处理同理,只不过是从尾往前进行遍历。
而这些Handler,正是保存在Pipeline中的:
通常一个pipeline有多个handler,例如,一个典型的服务器在每个通道的pipeline都会有以下处理程序:编解码器+业务处理程序。
业务程序不能将线程阻塞,会影响IO的速度,进而影响整个Netty程序的性能。如果你的业务程序很快,就可以放在IO线程中,反之,需要异步执行(使用MQ)。或者在添加Handler的时候添加一个线程池,例如:
// 下面这个任务执行的时候,将不会阻塞IO线程,执行的线程来自group线程池
pipeline.addLast(group, "handler", new MyHandler());
三、ChannelHandler源码分析
1、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
}
}
Netty中的@Sharable注解用于标识某个ChannelHandler是否可以在多个ChannelPipeline之间共享使用,也即表示该ChannelHandler是否是无状态的。使用@Sharable注解标记的ChannelHandler可以在多个ChannelPipeline之间共享使用,从而降低资源的消耗。在Netty中,每次调用ChannelHandler的构造函数或添加到ChannelPipeline时,都会创建一个新的实例。因此,对于有状态的ChannelHandler,将不能使用@Sharable进行标记。
需要注意的是,在进行ChannelHandler的复用时,考虑到线程安全问题。对于使用@Sharable注解的ChannelHandler类,在多个ChannelPipeline之间共享时需要保证线程安全性。假如ChannelHandler类中包含非线程安全的成员变量,需要在成员变量上进行同步,以防止出现线程安全问题。因此,这也是使用@Sharable注解时需要仔细考虑的一点。
ChannelHandler的作用就是处理IO事件或拦截IO事件,并将其转发给下一个处理程序ChannelHandler。Handler处理事件时分入站和出站的,两个方向的操作都是不同的,因此,Netty定义了两个子接口继承ChannelHandler,分别是ChannelInboundHandler和ChannelOutboundHandler。
2、ChannelInboundHandler入站接口
public interface ChannelInboundHandler extends ChannelHandler {
// channel注册时调用
void channelRegistered(ChannelHandlerContext ctx) throws Exception;
// channel取消注册时调用
void channelUnregistered(ChannelHandlerContext ctx) throws Exception;
// channel处于活动状态时调用
void channelActive(ChannelHandlerContext ctx) throws Exception;
// channel非活动状态时调用
void channelInactive(ChannelHandlerContext ctx) throws Exception;
// 从channel读取数据时被调用
void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception;
// 数据读取完成时被调用
void channelReadComplete(ChannelHandlerContext ctx) throws Exception;
// 用户事件触发
void userEventTriggered(ChannelHandlerContext ctx, Object evt) throws Exception;
// 通道可写状态被触发时调用
void channelWritabilityChanged(ChannelHandlerContext ctx) throws Exception;
// 发生异常时调用
@Override
@SuppressWarnings("deprecation")
void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception;
}
我们需要重写其中的一些方法,用于关注某些我们需要的事件,在重写的方法中实现我们自己的逻辑。当Netty事件发生时,Netty会回调对应重写的方法。
3、ChannelOutboundHandler出站接口
public interface ChannelOutboundHandler extends ChannelHandler {
// 当请求将channel绑定到本地端口时调用
void bind(ChannelHandlerContext ctx, SocketAddress localAddress, ChannelPromise promise) throws Exception;
// 发生连接时调用
void connect(
ChannelHandlerContext ctx, SocketAddress remoteAddress,
SocketAddress localAddress, ChannelPromise promise) throws Exception;
// 断开连接时调用
void disconnect(ChannelHandlerContext ctx, ChannelPromise promise) throws Exception;
// 当请求关闭channel时调用
void close(ChannelHandlerContext ctx, ChannelPromise promise) throws Exception;
// 取消注册时调用
void deregister(ChannelHandlerContext ctx, ChannelPromise promise) throws Exception;
void read(ChannelHandlerContext ctx) throws Exception;
// 在进行写操作时调用。写操作将通过ChannelPipeline写入消息。一旦调用Channel.flush(),它们就准备好被刷新到实际的Channel
void write(ChannelHandlerContext ctx, Object msg, ChannelPromise promise) throws Exception;
// 在执行刷新操作时调用。flush操作将尝试flush所有以前写的挂起的消息
void flush(ChannelHandlerContext ctx) throws Exception;
}
出站操作都是一些连接或者写出数据的方法。
4、ChannelDuplexHandler处理出站和入站事件
ChannelDuplexHandler继承了ChannelInboundHandlerAdapter类,实现了ChannelOutboundHandler接口,可以处理入站和出站所有的事件。
我们尽量避免使用ChannelDuplexHandler处理事件,入站出站事件最好由独立的类处理,否则容易混淆。
四、ChannelHandlerContext源码分析
1、ChannelHandlerContext接口
public interface ChannelHandlerContext extends AttributeMap, ChannelInboundInvoker, ChannelOutboundInvoker
ChannelHandlerContext接口继承了AttributeMap, ChannelInboundInvoker, ChannelOutboundInvoker接口。
ChannelHandlerContext不仅仅继承了ChannelInboundInvoker和ChannelOutboundInvoker接口的方法,同时也定义了一些自己的方法,这些方法能够获取context上下文环境中对应的比如channel、executor、handler、pipeline、内存分配器、关联的handler是否被删除。
Context就是包装了handler相关的一切类,以便Context可以在pipeline方便地操作handler。
2、ChannelInboundInvoker和ChannelOutboundInvoker接口
这两个invoker就是针对入站或出站方法来的,就是在入站或出站handler的外层再包装一层,达到在方法前后拦截并做一些特定操作的目的。
五、创建过程源码分析
1、SocketChannel创建过程创建pipeline
在上一篇文章我们分析到,doReadMessages方法会创建NioSocketChannel:
Netty核心源码分析(二),Netty的Server端接收请求过程源码分析
// io.netty.channel.socket.nio.NioServerSocketChannel#doReadMessages
@Override
protected int doReadMessages(List<Object> buf) throws Exception {
// 实际上调用NIO的的accept方法,获取SocketChannel
SocketChannel ch = SocketUtils.accept(javaChannel());
try {
if (ch != null) {
// 将NIO的SocketChannel包装成NioSocketChannel
buf.add(new NioSocketChannel(this, ch));
return 1;
}
} catch (Throwable t) {
logger.warn("Failed to create a new channel from an accepted socket.", t);
try {
ch.close();
} catch (Throwable t2) {
logger.warn("Failed to close a socket.", t2);
}
}
return 0;
}
在new NioSocketChannel()的过程中,一直往父类找,会找到AbstractChannel的构造方法:
protected AbstractChannel(Channel parent) {
this.parent = parent;
id = newId();
unsafe = newUnsafe();
pipeline = newChannelPipeline();
}
protected DefaultChannelPipeline newChannelPipeline() {
return new DefaultChannelPipeline(this);
}
此时会创建一个DefaultChannelPipeline,在DefaultChannelPipeline的构造方法中会做Pipeline初始化。
protected DefaultChannelPipeline(Channel channel) {
// 将channel赋值给channel字段
this.channel = ObjectUtil.checkNotNull(channel, "channel");
// 创建future和promise,用于异步回调使用
succeededFuture = new SucceededChannelFuture(channel, null);
voidPromise = new VoidChannelPromise(channel, true);
// 创建inbound的TailContext
tail = new TailContext(this);
// 创建outbound的HeadContext(实际上实现了inbound和outbound两者)
head = new HeadContext(this);
// 形成双向链表
head.next = tail;
tail.prev = head;
}
2、pipeline的addLast方法
在服务端通过pipeline的addLiast方法添加Handler时的源码:
// io.netty.channel.DefaultChannelPipeline#addLast(io.netty.channel.ChannelHandler...)
@Override
public final ChannelPipeline addLast(ChannelHandler... handlers) {
return addLast(null, handlers);
}
// io.netty.channel.DefaultChannelPipeline#addLast(io.netty.util.concurrent.EventExecutorGroup, io.netty.channel.ChannelHandler...)
@Override
public final ChannelPipeline addLast(EventExecutorGroup executor, ChannelHandler... handlers) {
if (handlers == null) {
throw new NullPointerException("handlers");
}
for (ChannelHandler h: handlers) {
if (h == null) {
break;
}
addLast(executor, null, h);
}
return this;
}
// io.netty.channel.DefaultChannelPipeline#addLast(io.netty.util.concurrent.EventExecutorGroup, java.lang.String, io.netty.channel.ChannelHandler)
@Override
public final ChannelPipeline addLast(EventExecutorGroup group, String name, ChannelHandler handler) {
final AbstractChannelHandlerContext newCtx;
synchronized (this) {
// 多线程安全问题
// 检查这个handler实例是否是共享的,如果不是,并且已经被别的pipeline使用了,则抛出异常
checkMultiplicity(handler);
// 创建一个Context,我们可以看出,每添加一个Handler都会关联一个Context
newCtx = newContext(group, filterName(name, handler), handler);
// 将Context追加到链表中
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.
/*
如果这个通道还没有注册到selector上,就将这个Context添加到这个pipeline的待办任务中。
当注册好了以后,就会调用callHandlerAdded0方法(默认是什么都不做,用户可以实现这个方法)
*/
if (!registered) {
newCtx.setAddPending();
callHandlerCallbackLater(newCtx, true);
return this;
}
EventExecutor executor = newCtx.executor();
if (!executor.inEventLoop()) {
newCtx.setAddPending();
executor.execute(new Runnable() {
@Override
public void run() {
callHandlerAdded0(newCtx);
}
});
return this;
}
}
callHandlerAdded0(newCtx);
return this;
}
// io.netty.channel.DefaultChannelPipeline#addLast0
// 实际上是插在了尾的前一个,而不是尾,因为尾部的Handler用于框架做最后的处理用
private void addLast0(AbstractChannelHandlerContext newCtx) {
AbstractChannelHandlerContext prev = tail.prev;
newCtx.prev = prev;
newCtx.next = tail;
prev.next = newCtx;
tail.prev = newCtx;
}
3、小总结
到这里,针对三个对象创建过程就结束了,每当创建 ChannelSocket 的时候都会创建个绑定的 pipeline,一对一的关系,创建 pipeline 的时候也会创建 tail 节点和 head 节点,形成最初的链表。
tail是入站 inbound 类型的 handler, head 既是 inbound 也是 outbound 类型的 handler。
在调用 pipeline 的 addLast方法的时候,会根据给定的 handler 创建一个 Context,然后,将这个 Context 插入到链表的尾端(tail 前面)到此就 OK 了。
Context包装handler,多个Context在pipeline中形成了双向链表。
入站方法叫inbound,由head节点开始。出站方法叫outbound,由tail节点开始。
六、ChannelPipeline调度Handler源码分析
当一个请求进来的时候,ChannelPipeline会第一个调用pipeline的相关方法,如果是入站事件,这些方法由fire开头,表示开始管道的流动。让后面的handler继续处理。
上面我们分析过了,ChannelPipeline创建的是一个DefaultChannelPipeline。
上面我们也分析了,DefaultChannelPipeline中包含着一系列的事件方法,触发某一个事件之后,会执行其相关的方法。
1、inbound——fireChannelRead
我们以fireChannelRead方法为例,追一下源代码。
当client业务数据发送过来之后,会触发DefaultChannelPipeline的fireChannelRead方法,表示有数据可读。
// io.netty.channel.DefaultChannelPipeline#fireChannelRead
@Override
public final ChannelPipeline fireChannelRead(Object msg) {
// 将head传入
AbstractChannelHandlerContext.invokeChannelRead(head, msg);
return this;
}
// io.netty.channel.AbstractChannelHandlerContext#invokeChannelRead(io.netty.channel.AbstractChannelHandlerContext, java.lang.Object)
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); // 执行器调度ChannelRead事件
} else {
executor.execute(new Runnable() {
@Override
public void run() {
next.invokeChannelRead(m);
}
});
}
}
此时,handler()方法返回this,也就是当前的ChannelHandler,并且调用该Handler的channelRead方法。
// io.netty.channel.AbstractChannelHandlerContext#invokeChannelRead(java.lang.Object)
private void invokeChannelRead(Object msg) {
if (invokeHandler()) {
try {
((ChannelInboundHandler) handler()).channelRead(this, msg);
} catch (Throwable t) {
notifyHandlerException(t);
}
} else {
fireChannelRead(msg);
}
}
上面我们分析到,pipeline的第一个Handler是系统创建的HeadContext,此时会调用HeadContext的channelRead方法(方法实现在其父类DefaultChannelPipeline中):
// io.netty.channel.DefaultChannelPipeline.HeadContext#channelRead
@Override
public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
ctx.fireChannelRead(msg);
}
// io.netty.channel.AbstractChannelHandlerContext#fireChannelRead
@Override
public ChannelHandlerContext fireChannelRead(final Object msg) {
invokeChannelRead(findContextInbound(), msg);
return this;
}
// io.netty.channel.AbstractChannelHandlerContext#findContextInbound
private AbstractChannelHandlerContext findContextInbound() {
AbstractChannelHandlerContext ctx = this;
do {
ctx = ctx.next;
} while (!ctx.inbound);
return ctx;
}
// io.netty.channel.AbstractChannelHandlerContext#invokeChannelRead(io.netty.channel.AbstractChannelHandlerContext, java.lang.Object)
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中也有个fireChannelRead方法,该方法调用了findContextInbound方法查找了pipeline的next下一个Handler,然后调用下一个Handler的channelRead方法,如此重复。
也就是说,在Handler的channelRead方法中,调用ChannelHandlerContext的fireChannelRead方法,就会将消息传递给下一个Handler。
2、outbound——connect
我们以connect方法来分析出站源码:
// io.netty.channel.DefaultChannelPipeline#connect(java.net.SocketAddress, java.net.SocketAddress, io.netty.channel.ChannelPromise)
@Override
public final ChannelFuture connect(
SocketAddress remoteAddress, SocketAddress localAddress, ChannelPromise promise) {
// 从tail开始调用connect事件
return tail.connect(remoteAddress, localAddress, promise);
}
// io.netty.channel.AbstractChannelHandlerContext#connect(java.net.SocketAddress, java.net.SocketAddress, io.netty.channel.ChannelPromise)
@Override
public ChannelFuture connect(
final SocketAddress remoteAddress, final SocketAddress localAddress, final ChannelPromise promise) {
if (remoteAddress == null) {
throw new NullPointerException("remoteAddress");
}
if (isNotValidPromise(promise, false)) {
// cancelled
return promise;
}
final AbstractChannelHandlerContext next = findContextOutbound();
EventExecutor executor = next.executor();
if (executor.inEventLoop()) {
next.invokeConnect(remoteAddress, localAddress, promise);
} else {
safeExecute(executor, new Runnable() {
@Override
public void run() {
next.invokeConnect(remoteAddress, localAddress, promise);
}
}, promise, null);
}
return promise;
}
// io.netty.channel.AbstractChannelHandlerContext#invokeConnect
private void invokeConnect(SocketAddress remoteAddress, SocketAddress localAddress, ChannelPromise promise) {
if (invokeHandler()) {
try {
((ChannelOutboundHandler) handler()).connect(this, remoteAddress, localAddress, promise);
} catch (Throwable t) {
notifyOutboundHandlerException(t, promise);
}
} else {
connect(remoteAddress, localAddress, promise);
}
}
我们发现,也是链式的调用ChannelOutboundHandler的connect方法,与入站如出一辙。
3、总结
Context包装handler,多个Context在pipeline中形成了双向链表,入站方向叫inbound,由head节点开始,出站方法叫outbound,由tail节点开始。
节点中间的传递通过AbstractChannelHandlerContext内部的fire系列方法,找到当前节点的下一个节点不断地进行传播,以链式完成对handler的调度。
到此时,我们Netty从请求建立、请求接收、请求处理的整个过程源码已经全部分析完毕