Netty详解之七:Pipeline与ChannelHandler

来自Channel底层的IO事件,会转发给pipeline来处理,另一方面用户直接调用Channel的IO方法也是通过pipeline达到底层。因此pipepline是Netty内核与业务层之间的传送带,是一个双向的IO事件通道,其中从业务层往底层方向叫"outbound",从底层通往上层,叫“inbound"。

Pipeline本质上是由一个一个ChannelHandler节点组成的双向链表,其中既有Netty预置的节点,也有用户代码添加的节点。每个节点处理特定IO事件,或者将IO事件进行转换再传递给下一个节点。Pipline是典型的责任链模式,方便适应各种业务场景和数据格式。

Pipeline是Netty事件驱动模型的另-个核心抽象(第一个是EventLoop),在这个模型中,所有的IO相关操作,无论是数据读写,还是其他Socket管理操作,Netty都把它当做事件来传递。

这里并没有一个所谓Event类型(据说Netty3是有的,Netty4出于性能原因去掉了),事件传递就是指接口方法调用;在面向对象的世界里,方法调用和事件传递这两个概念本就可以混用。

核心接口

我们了解一下围绕Pipeline的几个核心抽象概念。

Channel(In/Out)boundInvoker

ChannelInboundInvoker表示处理Inbound事件的调用入口,而ChannelOutboundInvoker则表示发起Outbound事件的调用入口。

public interface ChannelInboundInvoker {
    ChannelInboundInvoker fireChannelRegistered();
    ChannelInboundInvoker fireChannelUnregistered();
    ChannelInboundInvoker fireChannelActive();
    ChannelInboundInvoker fireChannelInactive();
    ChannelInboundInvoker fireExceptionCaught(Throwable cause);
    ChannelInboundInvoker fireUserEventTriggered(Object event);
    ChannelInboundInvoker fireChannelRead(Object msg);
    ChannelInboundInvoker fireChannelReadComplete();
    ChannelInboundInvoker fireChannelWritabilityChanged();
}

public interface ChannelOutboundHandler extends ChannelHandler {
    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;
    void read(ChannelHandlerContext ctx) throws Exception;
    void write(ChannelHandlerContext ctx, Object msg, ChannelPromise promise) throws Exception;
    void flush(ChannelHandlerContext ctx) throws Exception;
}

这些接口方法的名字都非常好地表达了它的意思,InBound事件和OutBound事件的触发方法形式有明显的区别:

  • Inbound事件:触发事件的方法叫fireXXX,它的返回值是一个ChannelPipeline,以方便链式调用;底层不会关注上层处理事件的结果,因而此类方法没有Future之类的参数或返回值。
  • OutBound请求:类似传统Socket操作,比如connect,bind,read,write等,基本都有一个ChannelPromise参数来监视操作的完成状态(Netty的IO操作都是异步的)。

这两个接口都叫Invoker,表示它是主动触发者,但是不是处理者,handler才是处理器,名字和功能对应得非常到位。

ChannelPipeline接口

现在可以看看ChannelPipeline的接口定义:


public interface ChannelPipeline
        extends ChannelInboundInvoker, ChannelOutboundInvoker, Iterable> {

	 //在头部添加一个ChannelHandler
    ChannelPipeline addFirst(String name, ChannelHandler handler);

	 //在头部添加一个ChannelHandler,且指定该handler在group内执行
    ChannelPipeline addFirst(EventExecutorGroup group, String name, ChannelHandler handler);

    //此处省略了addLast,addBefore,addAfter

    //移除handler
    ChannelPipeline remove(ChannelHandler handler);

    //按名字移除handler
    ChannelHandler remove(String name);
    
    //此处省略了removeFirst,removeLast,replace

    //头部handler
    ChannelHandler first();

	 //头部节点
    ChannelHandlerContext firstContext();

	 //获取对应handler的节点
    ChannelHandlerContext context(ChannelHandler handler);

	//Pipline所属的Channel
    Channel channel();


	//fireXXX方法来自ChannelInboundInvoker,重新声明以修改返回值类型
    @Override
    ChannelPipeline fireChannelRegistered();
    
    //其他fire方法声明
    ...
    
}

上面的的代码省略了一些功能类似的方法的声明,它主要包含以下几类方法:

  • handler链表管理:也就是增删改查接口,可以给handler赋予一个有意义的name,便于管理;
  • 获取ChannelHandlerContext:可以获取handler对应的节点对象;
  • 传递Inbound事件的方法:来自ChannelInboundInvoker;
  • 发起OutBound请求的方法:来自父接口ChannelOutboundInvoker;

虽然我们一直认为Channel PipeLine应该在限定在单个EventGroup内的单个EventLoop内执行,但是Pipeline显然支持更灵活的形式。方法addFirst(EventExecutorGroup group, String name, ChannelHandler handler);说明了这一点。

ChannelHandlerContext

ChannelHandlerContext是Pipeline的内部节点类型,它的接口定义如下:

public interface ChannelHandlerContext extends AttributeMap, ChannelInboundInvoker, ChannelOutboundInvoker {
	 //所属的Channel
    Channel channel();
    //所属的pipeline
    ChannelPipeline pipeline();
    //handler应该执行的EventExecutor
    EventExecutor executor();
    //所包装的ChannelHandler
    ChannelHandler handler();
    //该节点是否已经从pipeline移除
    boolean isRemoved();
}

ChannelHandlerContext有三个作用:

  • 一个是将Channel包装为pipeline所需的节点类型;
  • 封装了调用Channelhandler的细节;
  • 作为handler方法的参数,为handler提供必要的上下文信息。

我们注意到:ChannelHandlerContext和ChannelPipeline一样,都扩展自ChannelInboundInvoker和ChannelOutboundInvoker接口,这是很容易理解的,因为pipeline基本上将所有IO事件都转发给它的节点。 但二者表达的语义是不一样的,pipeline作为invoker调动整个流水线来处理事件;而ChannelHandlerContext作为invoker,调用本节点(及后续节点)来处理事件。

ChannelHandler

ChannelHandler是事件处理器接口,它的定义如下:

public interface ChannelHandler {
    void handlerAdded(ChannelHandlerContext ctx) throws Exception;
    void handlerRemoved(ChannelHandlerContext ctx) throws Exception;
    void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception;
    @Inherited
    @Documented
    @Target(ElementType.TYPE)
    @Retention(RetentionPolicy.RUNTIME)
    @interface Sharable {
        // no value
    }
}

这一层ChannelHandler定义的方法不多,其功能可望文生义。

它定义了一个Sharable注解,标注那些可以被共享的handler,被共享意味着该handler可以被添加到多个pipeline,或在一个pipeline中添加多次。一般handler如果是无状态的,或做好了相关状态管理机制,才可被共享。

Channel(In/Out)boundHandler

ChannelHandler有两个重要的子接口,第一个是ChannelInboundHandler,代表处理Inbound事件的handler,它的定义如下:

public interface ChannelInboundHandler extends ChannelHandler {
    void channelRegistered(ChannelHandlerContext ctx) throws Exception;
    void channelUnregistered(ChannelHandlerContext ctx) throws Exception;
    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;
}

ChannelInboundHandler与ChannelInboundInvoker相呼应。而另一个子接口ChannelOutboundHandler,代表处理Outbound事件的handler,与ChannelOutboundInvoker相呼应。

public interface ChannelOutboundHandler extends ChannelHandler {
    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;
    void read(ChannelHandlerContext ctx) throws Exception;
    void write(ChannelHandlerContext ctx, Object msg, ChannelPromise promise) throws Exception;
    void flush(ChannelHandlerContext ctx) throws Exception;
}

Pipeline的实现

DefaultChannelPipeline

非常幸运,pipeline的继承层次没有这么复杂,具体类DefaultChannelPipeline直接实现了它,并且被大部分Channel直接使用,所以我们只需要掌握DefaultChannelPipeline就够了。

先看看该类的重要成员字段:

public class DefaultChannelPipeline implements ChannelPipeline {

	 //内部双向链表的Head节点,和tail节点
    final AbstractChannelHandlerContext head;
    final AbstractChannelHandlerContext tail;

    //所属channel
    private final Channel channel;

	 //由于handler可以在多个EventExecutorGroup内执行,这里记住每个EventExecutorGroup所选择的EventExecutor
    private Map childExecutors;
    
    //估计数据对象尺寸的handler,由ChannelConfig提供
    private volatile MessageSizeEstimator.Handle estimatorHandle;
}

再看构造方法:

protected DefaultChannelPipeline(Channel channel) {
    this.channel = ObjectUtil.checkNotNull(channel, "channel");

	 //创建head和tail节点
    tail = new TailContext(this);
    head = new HeadContext(this);

    //头尾相连,构成双向链表
    head.next = tail;
    tail.prev = head;
}

链表管理:addHandler

我们以addFirst为例,看一下pipeline handler链管理的相关方法,

@Override
public final ChannelPipeline addFirst(EventExecutorGroup group, String name, ChannelHandler handler) {
    final AbstractChannelHandlerContext newCtx;
    synchronized (this) {
    
        //检查handler是否被多次添加,对于非sharable的handler,是不允许的
        checkMultiplicity(handler);
         
        //检查nam的重复,如果name=null,生成一个,
        name = filterName(name, handler);
	
		 //实际就是创建DefaultChannelHandlerContext,一个链表节点
        newCtx = newContext(group, name, handler);

		 //纯粹链表操作,看代码即可
        addFirst0(newCtx);

		 //如果Channel此时还没register(到EventLoop),那么handler的HandlerAdded回调不能执行,callHandlerCallbackLater创建延迟的回调任务
        if (!registered) {
            newCtx.setAddPending();
            callHandlerCallbackLater(newCtx, true);
            return this;
        }
		 
		 //到这里,说明Channel已经完成register,执行HandlerAdded回调
		 //当前不在对应的EventLoop内,执行callHandlerAddedInEventLoop
        EventExecutor executor = newCtx.executor();
        if (!executor.inEventLoop()) {
            callHandlerAddedInEventLoop(newCtx, executor);
            return this;
        }
    }
    //当前在对应的EventLoop内,直接执行HandlerAdded回调
    callHandlerAdded0(newCtx);
    return this;
}

private void addFirst0(AbstractChannelHandlerContext newCtx) {
    AbstractChannelHandlerContext nextCtx = head.next;
    newCtx.prev = head;
    newCtx.next = nextCtx;
    head.next = newCtx;
    nextCtx.prev = newCtx;
}

callHandlerAdded0方法其实就是执行ChannelHandher.callHandlerAdded,以及一些异常处理逻辑;而callHandlerAddedInEventLoop,在指定的EventLoop执行callHandlerAdded0,它们的代码就不贴了。

触发inbound事件

触发inbound事件的方法来自ChannelInboundInvoker接口,以fireChannelActive为例:

@Override
public final ChannelPipeline fireChannelActive() {
    AbstractChannelHandlerContext.invokeChannelActive(head);
    return this;
}

出人意料地简单,直接传递给head的invokeChannelActive方法节点即可;其他fireXXX方法的实现也是类似地专递给节点的invokeXXX方法。

执行outbound请求

再看outbound事件,以connect为例:

@Override
public final ChannelFuture connect(SocketAddress remoteAddress) {
    return tail.connect(remoteAddress);
}

转发给tail节点即可;其他诸如bind,write,close,flush,read都是类似的。

重要结论:事件在pipline中的传递

pipeline处理inbound事件的实现是交给head节点,处理oubound事件是交给tail节点,这意味着inbound事件,从head–>tail方向传递outbound事件,从tail–>head方向传递。

ChannelHandlerContext实现

DefaultChannelHandlerContext是ChanelPipeline使用的默认节点类型,它本身没什么逻辑,全部在基类AbstractChannelHandlerContext里。

abstract class AbstractChannelHandlerContext implements ChannelHandlerContext, ResourceLeakHint {
    
    //前驱和后驱节点指针
    volatile AbstractChannelHandlerContext next;
    volatile AbstractChannelHandlerContext prev;
    
    //节点状态,指handlerAdded回调尚未调用
    private static final int ADD_PENDING = 1;
    //节点状态,指handlerAdded回调已经调用
    private static final int ADD_COMPLETE = 2;
    //节点状态,指handlerRemove回调已经调用,节点已经从pipepline移除
    private static final int REMOVE_COMPLETE = 3;
 	//节点状态,默认初始
    private static final int INIT = 0;

	 //节点所属的pipeline
    private final DefaultChannelPipeline pipeline;
    //节点名字
    private final String name;
    
    //一个标记,执行节点的EventLoop按顺序执行事件,也即扩展自OrderedEventExecutor接口
    private final boolean ordered;
    
    //一个关于ChannelHandler处理事件的未掩码,通过这个掩码pipeLine能知道handler能处理那些事件,进而跳过不必要的节点,提高执行效率
    private final int executionMask;

 	 //执行handler的EventLoop,可为null,即使用Channel注册的executor
    final EventExecutor executor;
    private ChannelFuture succeededFuture;

	 //如果handler需要在其他executor执行,需要将执行逻辑封装为一个task
    private Tasks invokeTasks;

	 //初始化state
    private volatile int handlerState = INIT;

    AbstractChannelHandlerContext(DefaultChannelPipeline pipeline, EventExecutor executor,
                                  String name, Class handlerClass) {
        this.name = ObjectUtil.checkNotNull(name, "name");
        this.pipeline = pipeline;
        this.executor = executor;
        this.executionMask = mask(handlerClass);
        ordered = executor == null || executor instanceof OrderedEventExecutor;
    }
}

处理inbound事件

前面已经展示了,pipeline调用head节点的invokeXXX来处理inbound事件,我们来看AbstractChannelHandlerContext如何实现相关方法,以ChannelActive事件为例。

//静态方法,让handler在对应的EventLoop内执行
static void invokeChannelActive(final AbstractChannelHandlerContext next) {
    EventExecutor executor = next.executor();
    //下面是老套路,如果当前线程在所属eventLoop内,就地执行,否则包装为一个task提交给eventLoop。
    if (executor.inEventLoop()) {
        next.invokeChannelActive();
    } else {
        executor.execute(new Runnable() {
            @Override
            public void run() {
                next.invokeChannelActive();
            }
        });
    }
}

//执行handler方法处理事件
private void invokeChannelActive() {
    //依据节点状态判断是否执行
    if (invokeHandler()) {
        try {
        	  //调用handler.channelActive方法
            ((ChannelInboundHandler) handler()).channelActive(this);
        } catch (Throwable t) {
            notifyHandlerException(t);
        }
    } else {
    	 //否则继续传递事件
        fireChannelActive();
    }
}

//ADD_COMPLETE状态的节点,才执行handler; 
// (!ordered && handlerState == ADD_PENDING) 这个条件暂时没理解什么意思
private boolean invokeHandler() {
    int handlerState = this.handlerState;
    return handlerState == ADD_COMPLETE || (!ordered && handlerState == ADD_PENDING);
}

//向下一个节点传递ChannelActive事件
@Override
public ChannelHandlerContext fireChannelActive() {
    invokeChannelActive(findContextInbound(MASK_CHANNEL_ACTIVE));
    return this;
}

//顺着Inbound方向,搜寻下一个能处理对应事件的handler节点
private AbstractChannelHandlerContext findContextInbound(int mask) {
    AbstractChannelHandlerContext ctx = this;
    do {
        ctx = ctx.next;
    } while ((ctx.executionMask & mask) == 0);
    return ctx;
}

上面代码虽然不长,信息量挺大:

  • handler可以在设定的eventLoop内执行,这样一来事件的传递过程也就异步化了,如果链表中的每个handler都在不同的eventLoop,那么事件在不同的线程中不断传递,肯定影响性能;
  • 如果一个handler打算处理某个事件,那么它就拥有了该事件进一步传递与否的权利,换句话说,它可以决定该事件处理是否完结;
  • ChannelHandlerContext.fireXXX方法,向下一个节点继续传递事件;

我们可以提前瞥一眼ChannelInboundHandlerAdapter(InboundHandler抽象基类)的channelActive方法实现:没有做任何处理,调用ChannelHandlerContext.fireChannelActive传递给下一个节点。

public class ChannelInboundHandlerAdapter {
    ...
    
    @Override
    public void channelActive(ChannelHandlerContext ctx) throws Exception {
        ctx.fireChannelActive();
    }
    
    ...
}

处理outbound请求

outbound请求是从tail节点开始处理的,看AbstractChannelHandlerContext如何实现connect,相关方法如下:

@Override
public ChannelFuture connect(final SocketAddress remoteAddress, final SocketAddress localAddress, final ChannelPromise promise) {
		
	 //找到第一个第一个能处理connect的节点
    final AbstractChannelHandlerContext next = findContextOutbound(MASK_CONNECT);
    
    //老套路,不解释
    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, false);
    }
    return promise;
}

private AbstractChannelHandlerContext findContextOutbound(int mask) {
    AbstractChannelHandlerContext ctx = this;
    do {
        ctx = ctx.prev;
    } while ((ctx.executionMask & mask) == 0);
    return ctx;
}


private void invokeConnect(SocketAddress remoteAddress, SocketAddress localAddress, ChannelPromise promise) {
    if (invokeHandler()) {
        try {
        	  //调用handler的connect方法来处理请求
            ((ChannelOutboundHandler) handler()).connect(this, remoteAddress, localAddress, promise);
        } catch (Throwable t) {
            notifyOutboundHandlerException(t, promise);
        }
    } else {
        connect(remoteAddress, localAddress, promise);
    }
}

//这个所谓safeExecute,只是为了适应lazy参数,只有AbstractEventExecutor支持lazyExecute
//还记得否?lazyExecute是指如果当前EventLoop处于阻塞状态,暂时不唤醒,等有其他task激活Eventloop再执行
private static boolean safeExecute(EventExecutor executor, Runnable runnable,  ChannelPromise promise, Object msg, boolean lazy) {
    try {
        if (lazy && executor instanceof AbstractEventExecutor) {
            ((AbstractEventExecutor) executor).lazyExecute(runnable);
        } else {
            executor.execute(runnable);
        }
        return true;
    } catch (Throwable cause) {
        try {
            promise.setFailure(cause);
        } finally {
            if (msg != null) {
                ReferenceCountUtil.release(msg);
            }
        }
        return false;
    }
}

执行的套路和inbound是类似的,但是有一个明显差别,inbound从head节点开始,头节点处理不了再传递给后继节点;而outbound一上来就查找下一个节点,实际上跳过了tail节点(这只是一个性能优化)。

HeadContext

上面看到connect请求是由OutboundHandler来处理的,但是我们编写netty程序,从来不会编写处理connect请求的handler;大家应该能想到肯定是Netty内置了一个这样的handler,确实是这样,这个handler就是pipeline的头节点:HeadContext。

HeadContext是DefaultChannelPipeline的内部类,定义如下:

//HeadContext即是ChannelHandlerContext,也是Channel(In/Out)boundHandler,它没有其他用途,实现更紧凑
final class HeadContext extends AbstractChannelHandlerContext
            implements ChannelOutboundHandler, ChannelInboundHandler {

	 //持有Channel的unsafe
    private final Unsafe unsafe;

    HeadContext(DefaultChannelPipeline pipeline) {
        super(pipeline, null, HEAD_NAME, HeadContext.class);
        unsafe = pipeline.channel().unsafe();
        
        //初始化就进入ADD_COMPLETE状态,无论Channel注册与否
        setAddComplete();
    }

    //Outbound请求处理
    
    @Override
    public void bind(
            ChannelHandlerContext ctx, SocketAddress localAddress, ChannelPromise promise) {
        unsafe.bind(localAddress, promise);
    }

    @Override
    public void connect(
            ChannelHandlerContext ctx,
            SocketAddress remoteAddress, SocketAddress localAddress,
            ChannelPromise promise) {
        unsafe.connect(remoteAddress, localAddress, promise);
    }
    @Override
    public void disconnect(ChannelHandlerContext ctx, ChannelPromise promise) {
        unsafe.disconnect(promise);
    }
    @Override
    public void close(ChannelHandlerContext ctx, ChannelPromise promise) {
        unsafe.close(promise);
    }
    @Override
    public void deregister(ChannelHandlerContext ctx, ChannelPromise promise) {
        unsafe.deregister(promise);
    }
    @Override
    public void read(ChannelHandlerContext ctx) {
        unsafe.beginRead();
    }
    @Override
    public void write(ChannelHandlerContext ctx, Object msg, ChannelPromise promise) {
        unsafe.write(msg, promise);
    }
    @Override
    public void flush(ChannelHandlerContext ctx) {
        unsafe.flush();
    }
    
    //Inbound事件处理
    @Override
    public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) {
        ctx.fireExceptionCaught(cause);
    }
    @Override
    public void channelRegistered(ChannelHandlerContext ctx) {
        invokeHandlerAddedIfNeeded();
        ctx.fireChannelRegistered();
    }
    
	 ...
}

可见Head节点处理事件的方式如下:

  • outbound请求:全部调用Channel.unsafe的对应执行方法,是请求链表的最后一环;一般来说,对于connect,bind这样的指令类请求,也不需要其他handler来参与(除非有什么过滤拦截之类的需求)。
  • inbound事件:全部转发给下一节点,对某些特殊事件,有少量处理逻辑:比如channelActive:如果channel配置了autoread,发出一个outbound请求。

TailContext

TailContext是pipeline tail节点的实现,它是一个占位符型的节点,没有具体逻辑。

ChannelHandler

ChannelHandler在Netty这个事件驱动框架中,担任事件处理器的角色。我们实际写代码的时候,很多情况都是在编自定义的handler。

Channel(In/out)boundHandlerAdapter

Netty提供了ChannelInboundHandlerAdapter和ChannelOutboundHandlerAdapter两个基类,作为实现具体handler的基础。

public class ChannelInboundHandlerAdapter extends ChannelHandlerAdapter implements ChannelInboundHandler {

    @Skip
    @Override
    public void channelRegistered(ChannelHandlerContext ctx) throws Exception {
        ctx.fireChannelRegistered();
    }

    @Skip
    @Override
    public void channelUnregistered(ChannelHandlerContext ctx) throws Exception {
        ctx.fireChannelUnregistered();
    }
    
    //其他ChannelInboundHandler方法的实现都是类似的
}

有了上面的知识,这个类的功能很容易看明白,对于所有的inbound事件,不做任何处理,转发给下一个节点。@Skip注解标注该时间处理方法可以跳过,提高pipline的性能。

ChannelOutboundHandlerAdapter的实现,如出一辙:

public class ChannelOutboundHandlerAdapter extends ChannelHandlerAdapter implements ChannelOutboundHandler {
    @Skip
    @Override
    public void bind(ChannelHandlerContext ctx, SocketAddress localAddress,
            ChannelPromise promise) throws Exception {
        ctx.bind(localAddress, promise);
    }
    @Skip
    @Override
    public void connect(ChannelHandlerContext ctx, SocketAddress remoteAddress,
            SocketAddress localAddress, ChannelPromise promise) throws Exception {
        ctx.connect(remoteAddress, localAddress, promise);
    }
    
    //其他ChannelOutboundHandler方法的实现都是类似的
}

一个肩负具体功能的ChannelHandler,一般仅对某个或某几个事件感兴趣,对于不管兴趣的事件传递给下一个节点即可,从Channel(In/out)boundHandlerAdapter继承刚好满足该需求。

ChannelDuplexHandler

ChannelDuplexHandler代表一个双向的ChannelHandler,它的定义如下:

public class ChannelDuplexHandler extends ChannelInboundHandlerAdapter implements ChannelOutboundHandler {

    @Skip
    @Override
    public void bind(ChannelHandlerContext ctx, SocketAddress localAddress,
                     ChannelPromise promise) throws Exception {
        ctx.bind(localAddress, promise);
    }
    //其他ChannelOutboundHandler方法
}

由于java没有多继承,它只能继承ChannelInboundHandlerAdapter,并将ChannelOutboundHandlerAdapter的功能复制一遍。

预定义Handler

Netty为用户预置了很多ChannelHandler,可以开箱即用,大体分为以下几类:

  • 编解码器

用户实现自定义Handler最常见的功能需求是数据的编解码,实现业务数据和字节数据之间的互相转换;编码器一般叫xxxEncoder,解码器叫xxxDecoder,而同时实现编解码功能的handler一般叫xxxCodec。
比如LengthFieldBasedFrameDecoder和LengthFieldPrepender,分别实现了(长度+body)格式消息解码器和编码器。

  • 应用层网络协议
    比如HttpServerCodec(它同时也是一个针对http对象的编解码器)实现对http消息的处理,而WebSocketServerProtocolHandler实现了websocket握手协议;这样一来,用户可以直接处理上层协议数据对象,而不需要处理协议细节。

  • 状态检测
    比如IdleStateHandler,能够检测通道的空闲状态(无读、写持续一段时间),发出一个IdleStateEvent事件到Pipeline的下一个节点,我们可以通过另外一个handler来处理该事件,比如可认为通道已经失效,或发送心跳包。

更多的Handler大家请阅读源码里面的示例,基本上很多偏底层的功能Netty都有一些预定义handler能用上

WebSocket

这里倒不是为了刻意介绍WebSocket,而是想以“基于Netty构建WebSocket服务端”来体验本章学习的知识。

WebSocket是基于HTPP协议工作的,HTTP本来是短连接模式(每个请求建立一个连接,完成后就断开),而WebSocket协议在能够将短连接升级为长连接。换句话说,WebSocket通过HTTP协议的格式来传输数据,客户端和服务端之间通过握手数据包约定升级为长连接。

使用Netty启动WebSocket的示例代码如下:

ServerBootstrap bootstrap = new ServerBootstrap();
NioEventLoopGroup bossGroup = new NioEventLoopGroup(1);
NioEventLoopGroup workerGroup = new NioEventLoopGroup(5);
bootstrap.group(bossGroup, workerGroup);
bootstrap.channel(NioServerSocketChannel.class);
bootstrap.childHandler(new ChannelInitializer() {
    protected void initChannel(SocketChannel ch) {
        ch.pipeline().addLast(new HttpServerCodec());
        ch.pipeline().addLast(new HttpObjectAggregator(65536));
        ch.pipeline().addLast(new WebSocketServerProtocolHandler("/", (String)null, true));
        ch.pipeline().addLast(new CustomHandler());
    }
})
bootstrap.bind(port).sync();
  • 第一个handler是HttpServerCodec,因为webSocket是基于Http的,所以要把Http协议对象的编解码器放在最前面;
  • 第二个是HttpObjectAggregator,这是因为WebSocketServerProtocolHandler只能处理FullHttpRequest,而不能处理一般的HttpObject;
  • 第三个就是WebSocketServerProtocolHandler,完成握手协议,以及websocket数据帧的解析;
  • 最后一个是客户自己的Handler,用来处理WebSocketFrame数据对象。

这四个handler组成了pipeline(还有隐藏的Head和Tail),读数据的时候,底层字节流按inbound方向,字节数据经过HttpServerCodec、HttpObjectAggregator、WebSocketServerProtocolHandler,到达CustomHandler的时候已经变成了WebSocketFrame的形式。而写数据的时候,WebSocketFrame按outhound方向,经过WebSocketServerProtocolHandler、HttpObjectAggregator、HttpServerCodec,变成字节流注入底层socket。

通过websocket的配置案例可知,netty对应用层协议的支持,就是通过提供对应的ChannelHandler来实现的;而我们自己的Handler一般放在最后面,用来处理应用层协议数据。如果我们使用完全自定义的数据协议,那么我们的handler就需要直面字节流了,实现编解码功能了。下一章节住专门讲解“编解码器”。

你可能感兴趣的:(Netty,java,netty,tcpip)