netty源码分析系列——ChannelHandler系列

总体介绍

使用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及核心子类类图

223256_slwq_113011.png

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<Class<?>, 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<I> 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个在互联网,移动互联网广泛使用的协议。

转载于:https://my.oschina.net/ywbrj042/blog/956680

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值