channel介绍:

  netty中channel分为NioServerScoketChannel和NioSocketChannel,分别对应java nio中的ServerScoketChannel和SocketChannel

netty channel存到redis可以跨应用吗 netty channelcontext_自定义

 channel、pipeline、context、handler关系

  ScoketChannel都会注册到EventLoop上的selector中,每个channel内部都会有一个pipeline,pipeline管道 里面由多个handler连接成一个双向链表结构,而handler又由ChannelHandlerContext包裹着,context相当于一个handler上下文,做到了承上启下的作用,

从context可以得到handler,自然也能得到channel和pipeline,context内部有两个指针分别指向前 后context,handler在pipeline向前或向后进行传递,当然顺序只能由一个方向传递

netty channel存到redis可以跨应用吗 netty channelcontext_监听器_02

head和tail表示头尾,每一次有io事件进入,netty称为入站 它始终从head入站像后进行传递,反之io事件出去称为出站,也是从head出去,图中入站出站看似形成了一个完整的双向链表,但实际可能还没到tail就结束了,

context分为inbound和outbound,入站和出站会判断当前context是否符合 也是判断context中handler是inboundhandler还是outboundhandler,入站只执行入站的context,出站也是如此,在初始化pipeline会默认创建head、tail,

它们分别表示头 尾 位置固定不许修改,head和tail同时为inbound、outbound

先来看看handler是如何先加入pipeline中的,handler添加顺序无论怎么添加,头都是head 尾都是tail pipeline初始化就固定了

bossGroup.childHandler(new ChannelInitializer<SocketChannel>() {
     @Override
     public void initChannel(SocketChannel ch) throws Exception {
         ChannelPipeline p = ch.pipeline();
     // 像pipeline添加一个handler
         p.addLast(new EchoServerHandler());
     }
 });

// 将context添加到pipeline的"最后"
private void addLast0(AbstractChannelHandlerContext newCtx) {
AbstractChannelHandlerContext prev = tail.prev;
    newCtx.prev = prev;
    newCtx.next = tail;
    prev.next = newCtx;
   // 重新指向 依然保持着 tail》context》tail
    tail.prev = newCtx;
}

在创建ServerBootGroup时会给workerGroup分配一个handler,后面每一个NioSocketChannel都会添加一个该handler, ChannelInitializer可以理解为帮助我们创建channel,它是一个inboundhandler,在第一次注册时会调用initChannel来执行我们自定义的实现

public final void channelRegistered(ChannelHandlerContext ctx) throws Exception {// 这里会调用我们实现的initChannel方法,并把ctx中的channel传过去
        if (initChannel(ctx)) {
            // 初始化成功,这里就是我们入站的入口了,它是从pipeline发起的
            ctx.pipeline().fireChannelRegistered();
        } else {
            // 如果已经初始化过了,说明已经进入pipeline了,直接向后传递
            ctx.fireChannelRegistered();
        }
    }

 

下面分别列举ChannelInboundHandler和ChannelOutboundHandler的方法让大家知道哪些是入站执行的,哪些是出站执行的

 

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;

    // channel可写状态改变
    void channelWritabilityChanged(ChannelHandlerContext ctx) throws Exception;

    // 发生异常
    void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception;
}

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;

    // 暂时不知道这一步干啥的,按理说read操作是入站操作
    void read(ChannelHandlerContext ctx) throws Exception;

    // 写
    void write(ChannelHandlerContext ctx, Object msg, ChannelPromise promise) throws Exception;

    // 刷新刚执行的操作
    void flush(ChannelHandlerContext ctx) throws Exception;
}

还有一个特殊的handler ChannelDuplexHandler,它同时继承ChannelInboundHandler和ChannelOutboundHandler,但不推荐用它,容易混淆,建议我们自己写的时候把入站和出站的handler分开

public class ChannelDuplexHandler extends ChannelInboundHandlerAdapter implements ChannelOutboundHandler

ChannelHandlerContext传播行为

  前面我们讲入站是像后进行事件传递,出站是向前进行事件传递,那么事件入口是如何进来的、怎么出去的,怎么保证执行的顺序

如读事件,发生read事件时,会交给NioUnsafe方法,Unsafe随后会有介绍 它是netty中用来操作jdk中的nio,因为事件操作还是都交给jdk中的nio来,读取数据时会调用pipeline.fireChannelRead(readBuf.get(i)),这样就进入pipeline了 来开始事件传播

private final class NioMessageUnsafe extends AbstractNioUnsafe {
    @Override
    public void read() {
        ~~~~~~~~~~~~~
        int size = readBuf.size();
        for (int i = 0; i < size; i ++) {
            readPending = false;
            // >>>>>>>>>进入入站读操作
            pipeline.fireChannelRead(readBuf.get(i));
        }
        readBuf.clear();
        allocHandle.readComplete();
        // 完成操作
        pipeline.fireChannelReadComplete();
        ~~~~~~~~
    }
}

pipeline开启事件传播

// 调用pipeline该方法开始事件传播
public final ChannelPipeline fireChannelRead(Object msg) {
    AbstractChannelHandlerContext.invokeChannelRead(head, msg);
    return this;
}

// >>>AbstractChannelHandlerContext.invokeChannelRead(head, msg) 由pipeline调用 然后进入context事件传递
static void invokeChannelRead(final AbstractChannelHandlerContext next, Object msg) {
    final Object m = next.pipeline.touch(ObjectUtil.checkNotNull(msg, "msg"), next);
    EventExecutor executor = next.executor();
    // 判断是否在当前线程,如果在当前线程直接调用,否则当成任务交给executor执行
    if (executor.inEventLoop()) {
        next.invokeChannelRead(m);
    } else {
        executor.execute(new Runnable() {
            @Override
            public void run() {
                next.invokeChannelRead(m);
            }
        });
    }
}
    
private void invokeChannelRead(Object msg) {
    // 判断handler是否已经被调用
    if (invokeHandler()) {
        try {
             /**
             * 调用handler的读操作,用户通过实现该方法完成自定义读事件逻辑,如果读取完后需要向后传递,需要在channelRead自定义方法中继续调用context.fireChannelRead(msg)
             * 
             **/
            ((ChannelInboundHandler) handler()).channelRead(this, msg);
        } catch (Throwable t) {
            notifyHandlerException(t);
        }
    } else {
        // 已经被调用 调过当前handler向后传递
        fireChannelRead(msg);
    }
}

public ChannelHandlerContext fireChannelRead(final Object msg) {
    // 找出下一个入站handler继续向后传递
    invokeChannelRead(findContextInbound(), msg);
    return this;
}

private AbstractChannelHandlerContext findContextInbound() {
    AbstractChannelHandlerContext ctx = this;
    // 不停地获取下一个handler直到是inboundhandler返回 
    do {
        ctx = ctx.next;
    } while (!ctx.inbound);
    return ctx;
}

netty channel存到redis可以跨应用吗 netty channelcontext_抛出异常_03

这就是读数据入站的大致传播流程,从head入站直至tail或中途停止事件传播, 出站流程类似 我就不贴了

在讲解服务端和客户端启动流程前我们还需要再熟悉几个重要类

ChannelFuture、Promise、Unsafe

我们先来channelfuture和promise的继承结构

netty channel存到redis可以跨应用吗 netty channelcontext_自定义_04

  • ChannelFuture:nio既然是异步执行的,那么必定有异步执行结果,跟线程池一样,netty也对应有一个future
  • Promise:Promise也继承于future,听起来是不是和channelfuture功能重复了是不是,它两主要区别是,channelfuture可以得到对应的channel,promise可以主动设置异步执行状态,实际使用的ChannelPromise的实现类DefaultChannelPromise,channelPromise又同时继承channelfuture和实现promise,我个人不太理解为啥有channelfuture还多设计一个promise,直接在channelfuture加一个设置异步状态的接口不就好了,我猜想可能是promise可以当做一个脱离channel普通的异步实现,优秀的框架内部“果然都是可高度自定义重写的”
  • Unsafe:unsafe读过jdk源码的人应该很熟悉了,它是一个可直接进行原子化操作的工具,netty中的unsafe是用来操作jdk中的nio操作,我们前面说过netty就是一个在jdk原生nio上进行封装优化,所以内部网络通信肯定还是依靠jdk的nio实现的
public interface Future<V> extends java.util.concurrent.Future<V> {

    // 是否成功
    boolean isSuccess();

    // 是否取消
    boolean isCancellable();

    // 执行时候发生的异常
    Throwable cause();

    // 添加监听器
    Future<V> addListener(GenericFutureListener<? extends Future<? super V>> listener);

    // 批量添加监听器
    Future<V> addListeners(GenericFutureListener<? extends Future<? super V>>... listeners);

    // 移除监听器
    Future<V> removeListener(GenericFutureListener<? extends Future<? super V>> listener);

    // 移除多个监听器
    Future<V> removeListeners(GenericFutureListener<? extends Future<? super V>>... listeners);

    // 同步阻塞,内部先执行await() 如果执行任务有发生异常会重新抛出
    Future<V> sync() throws InterruptedException;

    // 与sync区别是,如果等待过程中发生中断,会将当前线程也一起中断,不响应中断
    Future<V> syncUninterruptibly();

    // 同步阻塞,它与sync区别是,任务执行失败不会抛出异常
    Future<V> await() throws InterruptedException;

    // 同理
    Future<V> awaitUninterruptibly();

    boolean await(long timeout, TimeUnit unit) throws InterruptedException;

    boolean await(long timeoutMillis) throws InterruptedException;

    boolean awaitUninterruptibly(long timeout, TimeUnit unit);

    boolean awaitUninterruptibly(long timeoutMillis);

    // 获得执行结果,但不阻塞
    V getNow();

    // 取消任务,并调用通知唤醒阻塞,然后调用监听器,mayInterruptIfRunning值好像没啥作用
    boolean cancel(boolean mayInterruptIfRunning);
}

public interface Promise<V> extends Future<V> {

    // 设置任务结果为成功 如果任务已经完成则抛出异常
    Promise<V> setSuccess(V result);

    // 设置任务结果为成功,返回设置结果 不抛出异常
    boolean trySuccess(V result);

    // 设置任务结果为失败 如果任务已经完成则抛出异常
    Promise<V> setFailure(Throwable cause);

    // 设置任务结果为失败,返回设置结果 不抛出异常
    boolean tryFailure(Throwable cause);
}

public interface ChannelFuture extends Future<Void> {
    
    // 返回当前channel
    Channel channel();
}

我们这节先大致介绍channel、handler、ChannelHandlerContext、pipeline 作用和方法,具体如何创建并使用,它们之前是怎么相互配合工作,我们下节通过对服务端和客户端启动过程进行分析过就会清晰许多