2019独角兽企业重金招聘Python工程师标准>>>
总体介绍
使用netty开发网络应用,用户接触最多的就是ChannelHandler,它是用于处理接收到的消息以及发送处理后的结果给远端的,这些是与具体的应用层协议和业务逻辑相关的部分。
上图体现了ChannelHandler相关的几个类之间的关系和流程。
初始化阶段
当Channel对象在构造的时候会同时创建一个ChannelPipeline对象,两个对象相互关联,是一对一的关系,ChannelPipeline不会被多个Channel共享。ChannelPipeline对象创建之后会调用它的各种添加handler的方法向链中加入ChannelHandler对象,而在加入ChannelHandler对象的同时,会自动给每个ChannelHandler包装一个ChannelHandlerContext对象。
ChannelHandlerContext是ChannelHandler的上下文信息,它使得ChannelHandler可以和ChannelPipeline以及其它的ChannelHandler对象进行交互操作。通过ChannelHandlerContext对象,ChannelHandler可以通知同一个pipeline中的其他ChannelHandler,也可以在运行时动态改变ChannelPipeline中的内容。
输入消息处理
当输入消息触发的时候,例如registred,active,read或readComplete等输入的消息触发的时候,会通过Channel调用对应的ChannelPipeline的对应方法来处理,输入消息会首先通过head找到下一个ChanneInbountHandler来处理输入消息,然后逐一传递到下一个ChanneInbountHandler消息,直至到最后一个内置的tail处理器。
输出消息处理
当输入消息触发的时候,例如bind,connect,write等输出消息触发的时候,会通过Channel调用对应的ChannelPipeline的对应方法来处理,输入消息会首先通过tail找到下一个ChanneOutbountHandler来处理输入消息,然后逐一传递到下一个ChanneOutbountHandler消息,直至到最后一个内置的head处理器。
ChannelHandler相关类类图
上图展示的是ChannelHandler相关的一些类的关系,我们将从输入和输出的处理流程来逐一走查一遍相关的源码。
Channel
/**
* Return the assigned {@link ChannelPipeline}.
*/
ChannelPipeline pipeline();
Channel接口中定义了一个返回关联的pipeline对象的方法,该方法使得Channel和ChannelPipeline关联在一起了,每个Channel都会有一个ChannelPipeline对象与之关联。
AbstractChannel
AbstractChannel类是Chanel的一个顶层抽象类,定义了公共的属性和方法,我们将分析它和pipeline产生关联部分的典型代码。
构造函数及pipeline()
在构造函数中对pipline属性进行了初始化。
protected AbstractChannel(Channel parent) {
this.parent = parent;
id = newId();
unsafe = newUnsafe();
pipeline = newChannelPipeline();//创建一个pipeline对象。
}
protected DefaultChannelPipeline newChannelPipeline() {
return new DefaultChannelPipeline(this);//直接构造一个默认的DefaultChannelPipeline类的对象。
}
public ChannelPipeline pipeline() {
return pipeline;//实现了Channel接口定义的该方法,返回构造的pipeline对象。
}
通过构造一个默认的实现类DefaultChannelPipeline的对象作为与Channel关联的pipeline对象,实现了pipeline方法,直接返回该对象。
Channel read()
通过该方法来代替所有的输入流处理方法。
public Channel read() {
pipeline.read();
return this;
}
调用了pipeline.read方法进行后续处理,接着进入道pipeline类的该方法继续分析。
write(Object msg)
通过该方法来代替所有的输出流处理方法。
public ChannelFuture write(Object msg) {
return pipeline.write(msg);
}
调用了pipeline.write方法进行后续处理,接着进入道pipeline类的该方法继续分析。
DefaultChannelPipeline
DefaultChannelPipeline类作为ChannelPipeline接口默认且唯一的实现类,它包含了大部分核心处理逻辑,是我们要重点分析的类。
重要属性列表
final AbstractChannelHandlerContext head;//双向链表的头部。
final AbstractChannelHandlerContext tail;//双向链表的尾巴。
private final Channel channel;//关联的Channel对象,这两个对象是相互关联的。
/**
* Set to {@code true} once the {@link AbstractChannel} is registered.Once set to {@code true} the value will never
* change.
*/
private boolean registered;//关联的Chanel是否已经注册。
head和tail是pipeline中最重要的两个属性,它表示了构造pipline是有一个双向链表来实现的,head是链表的头部,指向下一个ChannelContext对象,context中包含了一个ChannelHandler对象;tail是链表的尾部,它的上一个节点是最后一个ChannelContext对象;
构造函数
protected DefaultChannelPipeline(Channel channel) {//受保护的构造函数,非公开。
this.channel = ObjectUtil.checkNotNull(channel, "channel");//检查channel对象不能空。
succeededFuture = new SucceededChannelFuture(channel, null);//成功。
voidPromise = new VoidChannelPromise(channel, true);
tail = new TailContext(this);//头部节点构造。
head = new HeadContext(this);//尾部节点构造。
head.next = tail;//初始化链表。头部节点的下一个节点是尾部节点。
tail.prev = head;//初始化链表。尾部节点的上一个节点是头部节点。
}
构造函数是关联一个channel对象,另外对链表的头部尾部节点进行了初始化。tail和head节点的功能下面章节ChannelHandlerContext会进行介绍。
链表节点维护
初始化后的链表是一个只有默认的head及tail节点的空链表,这2个节点未实现任何有价值的逻辑,如果我们要利用netty来处理输入输出事件,则需要往pipeline的链表里添加一些有意义的ChannelHandler。
pipeline中有一系列的增删改链表节点的方法,我们不逐一分析,只选择其中一个有代表性的addFirst方法来进行分析。
@Override
public final ChannelPipeline addFirst(EventExecutorGroup group, String name, ChannelHandler handler) {
final AbstractChannelHandlerContext newCtx;
synchronized (this) {//同步方法块,避免并发安全性问题。
checkMultiplicity(handler);//检查handler是否重复添加。无@Shareable标记的ChannelHandler不允许共享被添加多次。
name = filterName(name, handler);//过滤名称。检查名称不能重复。
newCtx = newContext(group, name, handler);//给handler包装一个新的ChannelHandlerContext对象,这里默认创建了DefaultChannelHandlerContext对象。
addFirst0(newCtx);//将新生成的context节点加入道双向链表中,从head节点开始修改原来的链表引用关系。
//如果registered的值是false,意味着channel通道还没有注册到eventloop对象上,这种情况下我们添加的context状态是ADD_PENDING,并且会添加一个回调处理器,在channel被成功注册到eventloop后会回调ChannelHandler.handlerAdded(...)方法。
if (!registered) {
newCtx.setAddPending();
callHandlerCallbackLater(newCtx, true);
return this;
}
//如果不在eventloop中,则开启新的线程执行。
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;
}
//典型的将新节点插入到头节点之后,需要修改双向链表的prev和next值。
private void addFirst0(AbstractChannelHandlerContext newCtx) {
AbstractChannelHandlerContext nextCtx = head.next;//暂存头节点的下一个节点值。
newCtx.prev = head;//新节点的上一个节点改为head;
newCtx.next = nextCtx;//新节点的下一个节点为原来head的next节点。
head.next = newCtx;//头节点的下一个节点现在是新节点。
nextCtx.prev = newCtx;//新节点的上一个几点是头节点。
}
该方法是添加节点到头节点之后作为第一个节点。它的步骤如下:
1.检查名称是否重复,非共享的ChannelHandler是否被重复添加。
2.构造一个ChannelHandlerContext节点,对ChannelHandler进行包装,并将它添加到第一个节点。
3.完成添加后更新添加状态。
fireChannelRead()
读消息的处理方法,以该方法来代表所有的输入事件处理;若Channel读取到新的消息的时候,它触发调用pipeline的fireChannelRead方法,它负责读取消息后的处理。
public final ChannelPipeline fireChannelRead(Object msg) {
AbstractChannelHandlerContext.invokeChannelRead(head, msg);
return this;
}
它直接调用了AbstractChannelHandlerContext的invokeChannelRead方法,传入头节点,从这儿可以看出来,输入消息的处理是从head到tail的传递过程。下一节会介绍该方法的实现。
write()
写消息的处理方法,用该方法来代表所有的输出事件处理;
public final ChannelFuture write(Object msg) {
return tail.write(msg);
}
该方法调用了尾节点write方法,而tail也是一个AbstractChannelHandlerContext类型的对象,证明输出事件是从尾节点开始处理,传到上一个节点,直到head节点,下一节详细分析其源码。
AbstractChannelHandlerContext
ChannelHandlerContext对ChannelHandler进行了包装,pipeline直接对它进行操作。从上面类图我们可以看出来AbstractChannelHandlerContext是ChannelHandler接口的一个抽象实现,它实现了绝大多数的逻辑,因此我们分析该类就足够了。
属性列表
private final boolean inbound; //是否输入处理器。
private final boolean outbound;//是否输出处理器。
private final DefaultChannelPipeline pipeline;//关联的pipline
private final String name;//名称。
private final boolean ordered;//是否排序。
// Will be set to null if no child executor should be used, otherwise it will be set to the
// child executor.
final EventExecutor executor;//子执行器。
invokeChannelRead
执行读取消息方法。
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()) {//在eventLoop中直接执行,
next.invokeChannelRead(m);//执行next对象的方法。
} else {
executor.execute(new Runnable() {//创建一个任务执行。
@Override
public void run() {
next.invokeChannelRead(m);
}
});
}
}
上面的代码最终还是调用了另外一个对象方法invokeChannelRead来执行其逻辑。
private void invokeChannelRead(Object msg) {
if (invokeHandler()) {//是否执行处理器。
try {
((ChannelInboundHandler) handler()).channelRead(this, msg);//调用context包装的一个输入处理器处理消息。
} catch (Throwable t) {
notifyHandlerException(t);
}
} else {
fireChannelRead(msg);
}
}
write
写入消息方法。
public ChannelFuture write(Object msg) {
return write(msg, newPromise());//调用重载方法。
}
@Override
public ChannelFuture write(final Object msg, final ChannelPromise promise) {
if (msg == null) {//检查消息。
throw new NullPointerException("msg");
}
try {
if (!validatePromise(promise, true)) {//promise不合法。
ReferenceCountUtil.release(msg);//释放消息引用计数。
// cancelled
return promise;
}
} catch (RuntimeException e) {
ReferenceCountUtil.release(msg);释放消息引用计数,抛出一场。
throw e;
}
write(msg, false, promise);
return promise;
}
private void write(Object msg, boolean flush, ChannelPromise promise) {//写消息私有方法。
AbstractChannelHandlerContext next = findContextOutbound();//查找下一个输出处理器上下文。
final Object m = pipeline.touch(msg, next);
EventExecutor executor = next.executor();
if (executor.inEventLoop()) {
if (flush) {//调用下一个输出处理器的执行invokeWrite 方法。
next.invokeWriteAndFlush(m, promise);
} else {
next.invokeWrite(m, promise);
}
} else {
AbstractWriteTask task;
if (flush) {
task = WriteAndFlushTask.newInstance(next, m, promise);
} else {
task = WriteTask.newInstance(next, m, promise);
}
safeExecute(executor, task, promise, m);
}
}
从源码流程来看它是查找下一个输出context独享,然后调用该context的invokeWrite方法。
private 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);//调用处理器的write方法来处理。
} catch (Throwable t) {
notifyOutboundHandlerException(t, promise);
}
}
最后又会调用handler的write方法来处理写入消息事件。
DefaultChannelHandlerContext
该类作为抽象类AbstractChannelHandlerContext的唯一默认实现类。它实现了几个抽象方法,它只是增加了一个属性handler来引用context包装的handler对象,然后就是对于该对象的初始化和读取它的值实现。
private final ChannelHandler handler;//context包装的handler对象。
DefaultChannelHandlerContext(
DefaultChannelPipeline pipeline, EventExecutor executor, String name, ChannelHandler handler) {
super(pipeline, executor, name, isInbound(handler), isOutbound(handler));
if (handler == null) {
throw new NullPointerException("handler");
}
this.handler = handler;//设置handler。
}
@Override
public ChannelHandler handler() {
return handler;
}
private static boolean isInbound(ChannelHandler handler) {
return handler instanceof ChannelInboundHandler;//判断handler是否是输入处理器。
}
private static boolean isOutbound(ChannelHandler handler) {
return handler instanceof ChannelOutboundHandler;//判断handler是否是输出处理器。
}
HeadContext
final class HeadContext extends AbstractChannelHandlerContext
implements ChannelOutboundHandler, ChannelInboundHandler {
private final Unsafe unsafe;
HeadContext(DefaultChannelPipeline pipeline) {
super(pipeline, null, HEAD_NAME, false, true);
unsafe = pipeline.channel().unsafe();
setAddComplete();
}
@Override
public ChannelHandler handler() {
return this;
}
@Override
public void read(ChannelHandlerContext ctx) {
unsafe.beginRead();
}
@Override
public void write(ChannelHandlerContext ctx, Object msg, ChannelPromise promise) throws Exception {
unsafe.write(msg, promise);
}
}
HeadContext作为ping的ipeline中首个节点,它提供了一些默认的实现,它持有一个unsafe对象的应用,read和write方法都是调用unsafe对应的方法来实现,而unsafe是从channel()中获取的,实际上就是调用了channel底层的读写处理方法,对于底层网络连接通道进行输入输出的处理。
TailContext
final class TailContext extends AbstractChannelHandlerContext implements ChannelInboundHandler {
TailContext(DefaultChannelPipeline pipeline) {
super(pipeline, null, TAIL_NAME, true, false);
setAddComplete();
}
@Override
public ChannelHandler handler() {
return this;
}
@Override
public void channelRegistered(ChannelHandlerContext ctx) throws Exception { }
@Override
public void channelUnregistered(ChannelHandlerContext ctx) throws Exception { }
.....
}
TailContext是pipeline尾部节点,它的处理方法几乎都是空的,没有实际的逻辑处理。
ChannelHandler及核心子类类图
ChannelHandler系列核心的接口及实现类如上图所示,我们将按照继承关系逐一研究这些类的源码,该部分不包含具体应用层协议的编码解码器,后面系列可能会分析一下某些协议的编码解码器。例如http和http2。
ChannelHandler
void handlerAdded(ChannelHandlerContext ctx) throws Exception;//handler被加入到pipeline事件处理。
void handlerRemoved(ChannelHandlerContext ctx) throws Exception;//handler从pipeline中删除事件处理。
@Deprecated
void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception;//异常捕获事件处理。
@Inherited
@Documented
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@interface Sharable {//定义了一个注解,标记Handler是可共享的。
// no value
}
ChannelHandler是顶层的一个接口,它定义了三个事件处理方法。还定义了一个注解Sharable,该注解标记了Handler对象是可共享的,意味着如果一个Handler实现类标记了Sharable注解则它的同一个对象可以多次被加入到多个不同的pipeline中,而默认未标注注解的话则每个pipeline都会生成新的Handler对象,被不同的Channel共享,因此它可能会在多个线程中执行,所以标记未Sharable的Handler类的处理方法要实现线程安全,考虑多线程环境下能够安全地运行,最好做到无线程共享状态,若不能做到无状态,则需要通过锁等技术来控制并发。
ChannelOutboundHandler
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;//断开远程连接。
void close(ChannelHandlerContext ctx, ChannelPromise promise) throws Exception;//关闭。
void deregister(ChannelHandlerContext ctx, ChannelPromise promise) throws Exception;//取消注册到EventLoop上。
void read(ChannelHandlerContext ctx) throws Exception;//拦截Context触发的read事件。
void write(ChannelHandlerContext ctx, Object msg, ChannelPromise promise) throws Exception;//写消息,该方法将消息写入到待发送的缓冲区中。
void flush(ChannelHandlerContext ctx) throws Exception;//将本地缓冲区中待发送的消息刷到物理连接上,真正触发发送消息。
ChannelOutboundHandler是ChannelHandler接口的子接口,它的职责是负责接收输出的I/O事件,并处理这些输出事件。它增加了几个输出事件处理方法。
ChannelInboundHandler
void channelRegistered(ChannelHandlerContext ctx) throws Exception;//连接注册到EventLoop的事件处理。
void channelUnregistered(ChannelHandlerContext ctx) throws Exception;//取消注册到EventLoop的事件处理。
void channelActive(ChannelHandlerContext ctx) throws Exception;//远程连接激活事件。
void channelInactive(ChannelHandlerContext ctx) throws Exception;//远程连接断开事件。
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;//连接可写状态变更事件,例如限流可导致连接不可写,当连接重新可写情况下则会发送给事件通知客户端。
void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception;//异常捕获事件。
ChannelOutboundHandler是ChannelHandler接口的子接口,它的职责是负责接收输入的I/O事件,并处理这些输入事件。它增加了几个输入事件处理方法。
ChannelHandlerAdapter
//是否添加标志,不使用volatile关键字,因为它仅用于完整性检查。
boolean added;
/**
* 如果当前实现类被注解@Sharable标记,则它可以被加入到不同的ChannelPipeline中,表示可共享。
*/
public boolean isSharable() {
Class> clazz = getClass();
//使用线程变量来缓存结果。
Map, Boolean> cache = InternalThreadLocalMap.get().handlerSharableCache();
Boolean sharable = cache.get(clazz);
if (sharable == null) {
sharable = clazz.isAnnotationPresent(Sharable.class);
cache.put(clazz, sharable);
}
return sharable;
}
/**
* 空实现。
*/
@Override
public void handlerAdded(ChannelHandlerContext ctx) throws Exception {
// NOOP
}
...
/**
* 直接调用 ChannelHandlerContext#fireExceptionCaught(Throwable)}方法传递到ChannelPipeline中的下一个ChannelHandler对象。
*/
@Override
public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {
ctx.fireExceptionCaught(cause);
}
ChannelHandlerAdapter是ChannelHandler接口的一个适配器抽象实现,它提供了所有方法的默认实现,多数方法都是空实现,里面什么都不干,还提供了一个公共的属性added,提供一个公共方法isSharable()可判断当前类是否可共享。子类继承它之后无需实现所有方法,只需要覆盖那些有必要覆盖的方法。
ChannelOutboundHandlerAdapter
public class ChannelOutboundHandlerAdapter extends ChannelHandlerAdapter implements ChannelOutboundHandler {
/**
* 调用ChannelHandlerContext#bind(SocketAddress, ChannelPromise)}传递给ChannelPipeline中的下一个ChannelOutboundHandler对象。
* 子类可以覆盖该方法以改变它的行为
*/
@Override
public void bind(ChannelHandlerContext ctx, SocketAddress localAddress,
ChannelPromise promise) throws Exception {
ctx.bind(localAddress, promise);
}
...
}
ChannelOutboundHandlerAdapter是ChannelOutboundHandler类的适配器实现类,它对ChannelOutboundHandler的所有方法的实现都是直接调用了ChannelHandlerContext类的对应方法来处理,它的子类都无需实现这些方法了,简化了子类的实现。
ChannelInboundHandlerAdapter
public class ChannelInboundHandlerAdapter extends ChannelHandlerAdapter implements ChannelInboundHandler {
/**
* 调用ChannelHandlerContext#fireChannelRegistered()}方法传递给ChannelPipeline中的下一个ChannelInboundHandler对象。
*子类可覆盖该方法以改变它的行为。
*/
@Override
public void channelRegistered(ChannelHandlerContext ctx) throws Exception {
ctx.fireChannelRegistered();
}
}
ChannelInboundHandlerAdapter是ChannelInboundHandler类的适配器实现类,它对ChannelInboundHandler的所有方法的实现都是直接调用了ChannelHandlerContext类的对应方法来处理,它的子类都无需实现这些方法了,简化了子类的实现。注意该实现类在调用了channelRead方法后不会自动释放message对象引用,而SimpleChannelInboundHandler实现类可以。
SimpleChannelInboundHandler
public abstract class SimpleChannelInboundHandler extends ChannelInboundHandlerAdapter {
private final TypeParameterMatcher matcher;//类型参数匹配器。
private final boolean autoRelease;//是否自动释放。
/**
* 构造方法,它会试图使用当前类查找一个类型参数匹配器。
* @param autoRelease true表示如果处理消息后,会自动调用ReferenceCountUtil#release(Object)}方法来释放引用。 false表示不会自动释放。
*/
protected SimpleChannelInboundHandler(boolean autoRelease) {
matcher = TypeParameterMatcher.find(this, SimpleChannelInboundHandler.class, "I");
this.autoRelease = autoRelease;
}
@Override
public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
boolean release = true;
try {
if (acceptInboundMessage(msg)) {//是否可接收的输入消息类型。
@SuppressWarnings("unchecked")
I imsg = (I) msg;//转换为类型参数指定的类。
channelRead0(ctx, imsg);//调用子类的方法来实现。
} else {
release = false;//不自动释放该消息引用。
ctx.fireChannelRead(msg);//传递给下一个handler处理。
}
} finally {
if (autoRelease && release) {//如果全局标志和当前消息释放标志都是true,则可调用方法释放。
ReferenceCountUtil.release(msg);//释放当前消息的应用。
}
}
}
/**
* 抽象方法,真正的消息读取处理,交给子类实现该方法。该方法中无需考虑msg引用释放的问题,因为在调用该方法的魔板方法中已经处理了。
* 请注意该方法已经被重名为messageReceived(ChannelHandlerContext, I)}在netty5.0中。
*/
protected abstract void channelRead0(ChannelHandlerContext ctx, I msg) throws Exception;
}
SimpleChannelInboundHandler是我们常用的一个实现类,它对ChannelInboundHandlerAdapter作了扩展,它主要增加的特性是覆盖了父类的channelRead方法,增加了自动释放消息引用的处理逻辑,在处理完消息后,会调用ReferenceCountUtil.release自动释放消息应用,从而简化子类的实现,子类的实现只需要关心业务逻辑,而无需关注消息的引用释放问题,也避免了消息忘记释放的问题。
总结
至此,我们将ChannelHandler相关的类都分析了一遍,我们理清楚了Channel,ChannePipeline,ChannelHandlerContext以及ChannelHandler之间的关系,还分析了一遍ChannelHandler几个基础接口及适配器实现类的代码,可以说对于ChannelHandler相关类有了一个进一步的认识,我们可以更好地去使用ChannelHandler相关适配器实现类来实现自己的协议,对于各方法也有了进一步的认知。
接下来我们会分析netty内部自带的一些协议的实现,例如http和http2这2个在互联网,移动互联网广泛使用的协议。