架构师-Netty(六)

channel

Channel是Netty抽象出来的网络操作抽象类

JDK NIO原生的Channel缺点:

  • JDK的SocketChannelServerSocketChannel没有统一的Channel接口
  • JDK的SocketChannelServerSocketChannel的主要职责就是网络I/O操作,SPI接口,具体有虚拟接厂家提供,实现其抽象类,其工作量和重新开发一个新的Channel功能类差不多
  • Netty的Channel需要能够Netty的整体架构融合一起。如I/O模型,基于ChannelPipeline的定制模型,TCP参数配置,这是JDK的Channel没有提供,需要重新封装
  • 自定Channel,功能实现更加灵活

Channel设计原理:

  • Channel接口层,采用Facade模式统一封装,将网络I/O操作、网络I/O等封装,统一对外提供
  • Channel接口定义尽量大而全,为SocketChannelServerSocketChannel提供统一视图,有不同的子类实现不同的功能,公共功能在抽象父类实现
  • 具体实现采用聚合而非包含方式,将相关的功能聚合在Channel中

Channel 网络I/O操作

  • Channel read();从当前的Channel中读取到第一个inbound缓冲区中,如果数据被成功读取,触发ChannelHandler.channelRead(ChannelHandlerContext,Object)事件。读取操作API调用完成之后,紧接者会触发ChannelHandler.channelReadComplete(ChannelHandlerContent)事件,这也业务的ChannelHandler可以决定是否需要继续读取数据。如果已经有读操作请求被挂起,则后续的读操作会被忽略。

  • ChannelFuture write(Object msg) 请求将当前的msg通过ChannelPipeline写入到目标Channel中。write操作只是将消息存入到消息发送环形数组中,并没有真正被发送,只有调用flush操作才会写入到Channel中,发送给对方

  • ChannelFuture write(Object msg, ChannelPromise promise) ChannelPromise参数负责设置写入操作的结果

  • ChannelFuture close(ChannelPromise promise) 主动关闭当前连接 ,ChannelPromise获取操作结果的通知消息,会触发ChannelHandler.close(ChannelHandlerContext,ChannelPromise)事件

  • ChannelFuture disconnect(ChannelPromise promise); 请求断开与远程通信端的连接 触发ChannelHandler.disconnect(ChannelHandlerContext,ChannelPromise)

  • ChannelFuture connect(SocketAddress remoteAddress, ChannelPromise promise); 客户端发起连接请求

  • ChannelFuture bind(SocketAddress localAddress); 服务端绑定端口监听服务

  • ChannelConfig config();获取当前Channel的配置信息

  • boolean isOpen(); 判断当前Channel是否打开

  • boolean isActive(); 判断当前Channel是否处于激活状态

  • boolean isRegistered(); 判断当前Channel是否已经注册到EventLoop

  • ChannelMetadata metadata(); 获取当前的Channel的元数据描述信息,如TCP参数配置

  • EventLoop eventLoop(); 获取到Channel注册的EventLoop(多路复用器),用于处理I/O事件,本质处理网络读写的事件的Reactor线程

NioServerSocketChannel 服务端

在这里插入图片描述

AbstractChannel

public abstract class AbstractChannel extends DefaultAttributeMap implements Channel {

    private static final InternalLogger logger = InternalLoggerFactory.getInstance(AbstractChannel.class);
	// 链路已经关闭已经异常
    static final ClosedChannelException CLOSED_CHANNEL_EXCEPTION = new ClosedChannelException();
    // 物理链路尚未建立异常
    static final NotYetConnectedException NOT_YET_CONNECTED_EXCEPTION = new NotYetConnectedException();

    static {
        CLOSED_CHANNEL_EXCEPTION.setStackTrace(EmptyArrays.EMPTY_STACK_TRACE);
        NOT_YET_CONNECTED_EXCEPTION.setStackTrace(EmptyArrays.EMPTY_STACK_TRACE);
    }

    private MessageSizeEstimator.Handle estimatorHandle;

    private final Channel parent;
    private final ChannelId id;
    private final Unsafe unsafe;
    // pipeLine
    private final DefaultChannelPipeline pipeline;
    private final ChannelFuture succeededFuture = new SucceededChannelFuture(this, null);
    private final VoidChannelPromise voidPromise = new VoidChannelPromise(this, true);
    private final VoidChannelPromise unsafeVoidPromise = new VoidChannelPromise(this, false);
    private final CloseFuture closeFuture = new CloseFuture(this);

    private volatile SocketAddress localAddress;
    private volatile SocketAddress remoteAddress;
    // 当前Channel注册的EventLoop
    private volatile PausableChannelEventLoop eventLoop;
    private volatile boolean registered;

    /** Cache for the string representation of this channel */
    private boolean strValActive;
    private String strVal;
    /**
     * Creates a new instance.
     *
     * @param parent
     *        the parent of this channel. {@code null} if there's no parent.
     */
    protected AbstractChannel(Channel parent) {
        this.parent = parent;
        id = DefaultChannelId.newInstance();
        unsafe = newUnsafe();
        // pipeline
        pipeline = new DefaultChannelPipeline(this);
    }    
    // 网络IO操作,会触发ChannelPipeline对应的事件 Netty的驱动事件在pipeline中传播
    @Override
    public ChannelFuture bind(SocketAddress localAddress) {
        return pipeline.bind(localAddress);
    }

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

    @Override
    public ChannelFuture connect(SocketAddress remoteAddress, SocketAddress localAddress) {
        return pipeline.connect(remoteAddress, localAddress);
    }   

AbstractNioChannel

/**
 * Abstract base class for {@link Channel} implementations which use a Selector based approach.
 */
public abstract class AbstractNioChannel extends AbstractChannel {

    private static final InternalLogger logger =
            InternalLoggerFactory.getInstance(AbstractNioChannel.class);
	// java nio SelectableChannel 
    private final SelectableChannel ch;	
    // 代表JDK SelectionKey的OP_READ
    protected final int readInterestOp;
    volatile SelectionKey selectionKey;
    private volatile boolean inputShutdown;
    private volatile boolean readPending;

    /**
     * The future of the current connection attempt.  If not null, subsequent
     * connection attempts will fail.
     */
     // 操作结果
    private ChannelPromise connectPromise;
    // 连接操作定时器
    private ScheduledFuture<?> connectTimeoutFuture;
    // 请求通信地址
    private SocketAddress requestedRemoteAddress;

    /**
     * Create a new instance
     *
     * @param parent            the parent {@link Channel} by which this instance was created. May be {@code null}
     * @param ch                the underlying {@link SelectableChannel} on which it operates
     * @param readInterestOp    the ops to set to receive data from the {@link SelectableChannel}
     */
    protected AbstractNioChannel(Channel parent, SelectableChannel ch, int readInterestOp) {
        super(parent);
        this.ch = ch;
        this.readInterestOp = readInterestOp;
        try {
            ch.configureBlocking(false);
        } catch (IOException e) {
            try {
                ch.close();
            } catch (IOException e2) {
                if (logger.isWarnEnabled()) {
                    logger.warn(
                            "Failed to close a partially initialized socket.", e2);
                }
            }

            throw new ChannelException("Failed to enter non-blocking mode.", e);
        }
    }
    ....
}

Channel注册

  protected void doRegister() throws Exception {
        boolean selected = false;
        for (;;) {
            try {
                selectionKey = javaChannel().register(((NioEventLoop) eventLoop().unwrap()).selector, 0, this);
                return;
            } catch (CancelledKeyException e) {
                if (!selected) {
                    // Force the Selector to select now as the "canceled" SelectionKey may still be
                    // cached and not removed because no Select.select(..) operation was called yet.
                    ((NioEventLoop) eventLoop().unwrap()).selectNow();
                    selected = true;
                } else {
                    // We forced a select operation on the selector before but the SelectionKey is still cached
                    // for whatever reason. JDK bug ?
                    throw e;
                }
            }
        }
    }

    @Override
    protected void doDeregister() throws Exception {
        ((NioEventLoop) eventLoop().unwrap()).cancel(selectionKey());
    }
   @Override
   // 准备处理读操作之前需要设置网络操作位读
    protected void doBeginRead() throws Exception {
        // Channel.read() or ChannelHandlerContext.read() was called
        // 判断Channel是否关闭
        if (inputShutdown) {
            return;
        }

        final SelectionKey selectionKey = this.selectionKey;
        // 判断当前SelectionKey 是否可用
        if (!selectionKey.isValid()) {
            return;
        }

        readPending = true;
		// 	设置读操作位 与JDK NIO SelectionKey 的 isReadable()相等
        final int interestOps = selectionKey.interestOps();
        if ((interestOps & readInterestOp) == 0) {
        	// 设置读操作位 可以监听网络读事件 
            selectionKey.interestOps(interestOps | readInterestOp);
        }
    }

NioSocketChannel 客户端

NioSocketChannel 基于NIO jdk SocketChannel 和Netty的ByteBuf封装
AbstractNioByteChannel 写半包
flushTask 负责写半包消息

UnSafe

都是channel的内部实现类 复制接口 IO读写操作

在这里插入图片描述

ChannelPipeline和ChannelHandler

Netty的ChannelPipeline和ChannelHandler机制类似于Servlet和Filter过滤器,责任链模式的变形,主要方面事件的拦截和用户业务逻辑的定制

Netty的Channel过滤器实现原理与Servlet Filter机制一致,它将Channel的数据管道抽象为ChannelPipeline,消息在ChannelPipeline中流动和传递。ChannelPipeline持有I/O事件拦截器ChannelHandler的链表,由ChannelHandler
对I/O事件进行拦截和处理,通过新增和删除ChannelHandler来实现不同的业务逻辑定制

ChannelPipelineChannelHandler的容器,负责ChannelHandler管理和事件拦截和调度

  • 底层SocketChannel read()方法读取ByteBuf,触发ChannelRead事件,有I/O线程NioEventLoop调用ChannelPipeline的fireChannelRead(Object msg)方法,将消息(ByteBuf)传输到ChannelPipeline中
  • 消息依次被HeadHandler、ChannelHandler1、ChannelHandler2…TailHandler拦截和处理,在这个过程中任何ChannelHandler都可以中断当前流程,结束消息的传递
  • 调用ChannelHandlerContext的write方法发送消息,消息从TailHandler开始,途经ChannelHandlerN…ChannelHandler1、HeadHandler,最终被添加到消息发送缓冲区中等待刷新和发送,在此过程中也可以中断消息的传递,例如当编码失败时,就需要中断流程,构造异常的Future返回

Netty中的事件分为inbound事件和outbound事件
inbound事件通常有I/O线程触发,如TCP链路建立事件,链路关闭事件,读事件、异常通知事件等
ChannelHandlerContext#fireChannelRegistered Channel注册事件
ChannelHandlerContext#fireChannelActive TCP链路建立成功,Channel激活事件
ChannelHandlerContext#fireChannelInactive TCP连接关闭,链路不可用通知事件
ChannelHandlerContext#fireChannelRead 读事件
ChannelHandlerContext#fireChannelReadComplete 读操作完成通知事件
ChannelHandlerContext#fireExceptionCaught 异常通知事件
ChannelHandlerContext#fireUserEventTriggered 用户自定义事件
ChannelHandlerContext#fireChannelWritabilityChanged Channel的可写状态变化通知事件

Outbound事件通常是由用主动发起的网络I/O操作,如用户发起的连接操作、绑定操作、消费发送等操作

ChannelHandlerContext#bind(SocketAddress,ChannelPromise) 绑定本地地址事件
ChannelHandlerContext#connect(java.net.SocketAddress, java.net.SocketAddress, ChannelPromise) 连接服务端事件
ChannelHandlerContext#write(Object,ChannelPromise) 发送事件
ChannelHandlerContext#flush() 刷新事件
ChannelHandlerContext#read() 读事件
ChannelHandlerContext#disconnect(ChannelPromise) 断开连接事件
ChannelHandlerContext#close(ChannelPromise) 关闭当前Channel事件

在这里插入图片描述
一个 Channel 包含了一个 ChannelPipeline,而 ChannelPipeline 中又维护了一个由 ChannelHandlerContext 组成的双向链表。这个链表的头是 HeadContext,链表的尾是 TailContext,并且每个 ChannelHandlerContext 中又关联着一个 ChannelHandler

ChannelPipeline 的初始化

通过 NioSocketChannel 的父类 AbstractChannel 的构造器

protected AbstractChannel(Channel parent) { 
	this.parent = parent; 
	id = newId(); 
	unsafe = newUnsafe(); 
	pipeline = newChannelPipeline(); 
}

AbstractChannel 有一个 pipeline 字段,在构造器中会初始化它为 DefaultChannelPipeline 的实例
DefaultChannelPipeline 构造器中

protected DefaultChannelPipeline(Channel channel) { 
	this.channel = ObjectUtil.checkNotNull(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; 
}
	 

在 DefaultChannelPipeline 的构造方法中,将传入的 channel 赋值给字段 this.channel,接着又实例化了两个特殊的字段:tail 与 head,这两个字段是一个双向链表的头和尾。其实在 DefaultChannelPipeline 中,维护了一个 以AbstractChannelHandlerContext 为节点的双向链表,这个链表是 Netty 实现 Pipeline 机制的关键

特别注意的是:我们在开始的示意图中 head 和 tail 并没有包含 ChannelHandler,这是因为 HeadContext 和 TailContext 继承于 AbstractChannelHandlerContext 的同时也实现了 ChannelHandler 接口了,因此它们有 Context 和 Handler 的双重属性

head 和 tail 的类层次结构:
在这里插入图片描述
从类层次结构图中可以很清楚地看到,head 实现了 ChannelInboundHandler,而 tail 实现了 ChannelOutboundHandler 接口,并且它们都实现了 ChannelHandlerContext 接口, 因此可以说 head 和 tail 即是一个 ChannelHandler,又是一个 ChannelHandlerContext

HeadContext 构造器中的代码:

HeadContext(DefaultChannelPipeline pipeline) { 
	super(pipeline, null, HEAD_NAME, false, true); 
	unsafe = pipeline.channel().unsafe(); 
	setAddComplete(); 
}

它调用了父类 AbstractChannelHandlerContext 的构造器,并传入参数 inbound = false,outbound = true。而 TailContext 的构造器与 HeadContext 正好相反,它调用了父类 AbstractChannelHandlerContext 的构造器,并传入参 数 inbound = true,outbound = false。也就是说 header 是一个 OutBoundHandler,而 tail 是一个 InboundHandler

ChannelInitializer 的添加

最开始的时候 ChannelPipeline 中含有两个 ChannelHandlerContext(同时也是 ChannelHandler),但是这个 Pipeline 并不能实现什么特殊的功能,因为我们还 没有给它添加自定义的 ChannelHandler。通常来说,我们在初始化 Bootstrap,会添加我们自定义的 ChannelHandler, 就以我们具体的客户端启动代码片段来举例

Bootstrap bootstrap = new Bootstrap(); 
bootstrap.group(group)
	.channel(NioSocketChannel.class) 
	.option(ChannelOption.SO_KEEPALIVE, true) 
	.handler(new ChannelInitializer<SocketChannel>() { 
		@Override protected void initChannel(SocketChannel ch) throws Exception { 
			ChannelPipeline pipeline = ch.pipeline(); 
			pipeline.addLast(new ChatClientHandler(nickName)); 
		}
	}
);

在调用 handler 时,传入了 ChannelInitializer 对象,它提供了一个 initChannel()方法给我我们初始化 ChannelHandler

Pipeline 的事件传播机制

AbstractChannelHandlerContext 中有 inbound 和 outbound 两个 boolean 变量,分别用 于标识 Context 所对应的 handler 的类型,即:
* 1、inbound 为 true 是,表示其对应的 ChannelHandler 是 ChannelInboundHandler 的子类。
* 2、outbound 为 true 时,表示对应的 ChannelHandler 是 ChannelOutboundHandler 的子类
Netty 中的传播事件可以分为两种:Inbound 事件和 Outbound 事件。如下是从 Netty 官网针对这两个事件的说明:

  I/O Request
                                          Channel
                                        ChannelHandlerContext
                                                       |
   +---------------------------------------------------+---------------+
   |                           ChannelPipeline         |               |
   |                                                  \|/              |
   |    +----------------------------------------------+----------+    |
   |    |                   ChannelHandler  N                     |    |
   |    +----------+-----------------------------------+----------+    |
   |              /|\                                  |               |
   |               |                                  \|/              |
   |    +----------+-----------------------------------+----------+    |
   |    |                   ChannelHandler N-1                    |    |
   |    +----------+-----------------------------------+----------+    |
   |              /|\                                  .               |
   |               .                                   .               |
   | ChannelHandlerContext.fireIN_EVT() ChannelHandlerContext.OUT_EVT()|
   |          [method call]                      [method call]         |
   |               .                                   .               |
   |               .                                  \|/              |
   |    +----------+-----------------------------------+----------+    |
   |    |                   ChannelHandler  2                     |    |
   |    +----------+-----------------------------------+----------+    |
   |              /|\                                  |               |
   |               |                                  \|/              |
   |    +----------+-----------------------------------+----------+    |
   |    |                   ChannelHandler  1                     |    |
   |    +----------+-----------------------------------+----------+    |
   |              /|\                                  |               |
   +---------------+-----------------------------------+---------------+
                   |                                  \|/
   +---------------+-----------------------------------+---------------+
   |               |                                   |               |
   |       [ Socket.read() ]                    [ Socket.write() ]     |
   |                                                                   |
   |  Netty Internal I/O Threads (Transport Implementation)            |
   +-------------------------------------------------------------------+

从上图可以看出,inbound 事件和 outbound 事件的流向是不一样的,inbound 事件的流行是从下至上,而 outbound 刚好相反,是从上到下。并且 inbound 的传递方式是通过调用相应的 ChannelHandlerContext.fireIN_EVT()方法,而outbound 方法的的传递方式是通过调用 ChannelHandlerContext.OUT_EVT()方法。例如:ChannelHandlerContext 的 fireChannelRegistered()调用会发送一个 ChannelRegistered 的 inbound 给下一个 ChannelHandlerContext,而 ChannelHandlerContext 的 bind()方法调用时会发送一个 bind 的 outbound 事件给下一个 ChannelHandlerContext。
Inbound 事件传播方法有:

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; 
}

Outbound 事件传播方法有:

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 flush(ChannelHandlerContext ctx) throws Exception; }

大家应该发现了规律:inbound 类似于是事件回调(响应请求的事件),而 outbound 类似于主动触发(发起请求的 事件)。注意,如果我们捕获了一个事件,并且想让这个事件继续传递下去,那么需要调用 Context 对应的传播方法 fireXXX,例如:

public class MyInboundHandler extends ChannelInboundHandlerAdapter { 
	@Override public void channelActive(ChannelHandlerContext ctx) throws Exception { 
		System.out.println("连接成功"); 
		ctx.fireChannelActive(); 
	} 
}
public class MyOutboundHandler extends ChannelOutboundHandlerAdapter { 
	@Override public void close(ChannelHandlerContext ctx, ChannelPromise promise) throws Exception { 
		System.out.println("客户端关闭"); 
		ctx.close(promise); 
	} 
}

如上面的示例代码:MyInboundHandler 收到了一个 channelActive 事件,它在处理后,如果希望将事件继续传播下去,那么需要接着调用 ctx.fireChannelActive()方法

Outbound 事件传播方式

Outbound 事件都是请求事件(request event),即请求某件事情的发生,然后通过 Outbound 事件进行通知。
Outbound 事件的传播方向是 tail -> customContext -> head。
我们接下来以 connect 事件为例,分析一下 Outbound 事件的传播机制。 首先,当用户调用了 Bootstrap 的 connect()方法时,就会触发一个 Connect 请求事件,此调用会触发如下调用链:
在这里插入图片描述
继续跟踪,我们就发现 AbstractChannel 的 connect()其实由调用了 DefaultChannelPipeline 的 connect()方法:

public ChannelFuture connect(SocketAddress remoteAddress, ChannelPromise promise) { 
	return pipeline.connect(remoteAddress, promise); 
}

而 pipeline.connect()方法的实现如下:

public final ChannelFuture connect(SocketAddress remoteAddress, ChannelPromise promise) { 
	return tail.connect(remoteAddress, promise);
 }

可以看到,当 outbound 事件(这里是 connect 事件)传递到 Pipeline 后,它其实是以 tail 为起点开始传播的。 而 tail.connect()其实调用的是 AbstractChannelHandlerContext 的 connect()方法:

public ChannelFuture connect( final SocketAddress remoteAddress, final SocketAddress localAddress, final ChannelPromise promise) { 
	//此处省略 N 句 
	final AbstractChannelHandlerContext next = findContextOutbound(); 
	EventExecutor executor = next.executor(); 
	next.invokeConnect(remoteAddress, localAddress, promise); 
	//此处省略 N 句 
	return promise;
}

findContextOutbound()方法顾名思义,它的作用是以当前 Context 为起点,向 Pipeline 中的 Context 双向链表的前端 寻找第一个 outbound 属性为 true 的 Context(即关联 ChannelOutboundHandler 的 Context),然后返回。 findContextOutbound()方法代码实现如下:

private AbstractChannelHandlerContext findContextOutbound() { 
	AbstractChannelHandlerContext ctx = this; 
	do {ctx = ctx.prev; 
	} while (!ctx.outbound); 
	return ctx; 
}

当我们找到了一个 outbound 的 Context 后,就调用它的 invokeConnect()方法,这个方法中会调用 Context 其关联的 ChannelHandler 的 connect()方法:

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); } }

如果用户没有重写 ChannelHandler 的 connect()方法,那么会调用 ChannelOutboundHandlerAdapter 的 connect() 实现:

public void connect(ChannelHandlerContext ctx, SocketAddress remoteAddress, SocketAddress localAddress, ChannelPromise promise) throws Exception { ctx.connect(remoteAddress, localAddress, promise); }

我们看到,ChannelOutboundHandlerAdapter 的 connect()仅仅调用了 ctx.connect(),而这个调用又回到了: Context.connect -> Connect.findContextOutbound -> next.invokeConnect -> handler.connect -> Context.connect 这样的循环中,直到 connect 事件传递到 DefaultChannelPipeline 的双向链表的头节点,即 head 中。为什么会传递 到 head 中呢?回想一下,head 实现了 ChannelOutboundHandler,因此它的 outbound 属性是 true。 因为 head 本身既是一个 ChannelHandlerContext,又实现了 ChannelOutboundHandler 接口,因此当 connect()消息 传递到 head 后,会将消息转递到对应的 ChannelHandler 中处理,而 head 的 handler()方法返回的就是 head 本身:

public ChannelHandler handler() { 
	return this; 
}

因此最终 connect()事件是在 head 中被处理。head 的 connect()事件处理逻辑如下:

public void connect( ChannelHandlerContext ctx, SocketAddress remoteAddress, SocketAddress localAddress, ChannelPromise promise) throws Exception { 
	unsafe.connect(remoteAddress, localAddress, promise); 
}

到这里, 整个 connect()请求事件就结束了。下图中描述了整个 connect()请求事件的处理过程:
在这里插入图片描述

Inbound 事件传播方式

Inbound 事件和 Outbound 事件的处理过程是类似的,只是传播方向不同。
Inbound 事件是一个通知事件,即某件事已经发生了,然后通过 Inbound 事件进行通知。Inbound 通常发生在 Channel 的状态的改变或 IO 事件就绪。 Inbound 的特点是它传播方向是 head -> customContext -> tail。

上面我们分析了 connect()这个 Outbound 事件,那么接着分析 connect()事件后会发生什么 Inbound 事件,并最终找到 Outbound 和 Inbound 事件之间的联系。当 connect()这个 Outbound 传播到 unsafe 后,其实是在 AbstractNioUnsafe 的 connect()方法中进行处理的:

public final void connect( final SocketAddress remoteAddress,
			 final SocketAddress localAddress, final ChannelPromise promise) {
	 if (doConnect(remoteAddress, localAddress)) { 
	 	fulfillConnectPromise(promise, wasActive); 
	 } else {
	 	 ...
	 } 
}

在 AbstractNioUnsafe 的 connect()方法中,首先调用 doConnect()方法进行实际上的 Socket 连接,当连接上后会调用 fulfillConnectPromise()方法:

private void fulfillConnectPromise(ChannelPromise promise, boolean wasActive) { 
	if (!wasActive && active) {
		 pipeline().fireChannelActive(); 
	} 
}

我们看到,在 fulfillConnectPromise()中,会通过调用 pipeline().fireChannelActive()方法将通道激活的消息(即 Socket 连 接成功)发送出去。而这里,当调用 pipeline.fireXXX 后,就是 Inbound 事件的起点。因此当调用 pipeline().fireChannelActive()后,就产生了一个 ChannelActive Inbound 事件,我们就从这里开始看看这个 Inbound 事件是怎么传播的?

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

果然, 在 fireChannelActive()方法中,调用的是 head.invokeChannelActive(),因此可以证明 Inbound 事件在 Pipeline 中传输的起点是 head。那么,在 head.invokeChannelActive()中又做了什么呢?

static void invokeChannelActive(final AbstractChannelHandlerContext next) { 
	EventExecutor executor = next.executor(); 
	if (executor.inEventLoop()) { 
		next.invokeChannelActive(); 
	} else { 
		executor.execute(new Runnable() { 
			@Override public void run() { next.invokeChannelActive(); } 
		}); 
	} 
}

上面的代码应该很熟悉了。 回想一下在 Outbound 事件(例如 connect()事件)的传输过程中时,我们也有类似的操作:
1、首先调用 findContextInbound(),从 Pipeline 的双向链表中中找到第一个属性 inbound 为 true 的 Context,然后将 其返回。
2、调用 Context 的 invokeChannelActive()方法,invokeChannelActive()方法源码如下:

private void invokeChannelActive() { 
	if (invokeHandler()) { 
		try {
			((ChannelInboundHandler) handler()).channelActive(this); 
		} catch (Throwable t) { 
			notifyHandlerException(t); 
	  }
	} else { 
		fireChannelActive(); 
	} 
}

这个方法和 Outbound 的对应方法(如:invokeConnect()方法)如出一辙。与 Outbound 一样,如果用户没有重写 channelActive() 方法,那就会调用 ChannelInboundHandlerAdapter 的 channelActive()方法:

public void channelActive(ChannelHandlerContext ctx) throws Exception {
 	ctx.fireChannelActive(); 
 }

同样地, 在 ChannelInboundHandlerAdapter 的 channelActive()中,仅仅调用了 ctx.fireChannelActive()方法,因此就 会进入 Context.fireChannelActive() -> Connect.findContextInbound() -> nextContext.invokeChannelActive() -> nextHandler.channelActive() -> nextContext.fireChannelActive()这样的循环中。同理,tail 本身既实现了 ChannelInboundHandler 接口,又实现了 ChannelHandlerContext 接口,因此当 channelActive()消息传递到 tail 后, 会将消息转递到对应的 ChannelHandler 中处理,而 tail 的 handler()返回的就是 tail 本身:

public ChannelHandler handler() { return this; }

因此 channelActive Inbound 事件最终是在 tail 中处理的,我们看一下它的处理方法:

public void channelActive(ChannelHandlerContext ctx) throws Exception { }

TailContext 的 channelActive()方法是空的。如果大家自行查看 TailContext 的 Inbound 处理方法时就会发现,它们的 实现都是空的。可见,如果是 Inbound,当用户没有实现自定义的处理器时,那么默认是不处理的。下图描述了 Inbound 事件的传输过程:
在这里插入图片描述

Pipeline 事件传播小结

Outbound 事件总结:

  • 1、Outbound 事件是请求事件(由 connect()发起一个请求,并最终由 unsafe 处理这个请求)。
  • 2、Outbound 事件的发起者是 Channel。
  • 3、Outbound 事件的处理者是 unsafe。
  • 4、Outbound 事件在 Pipeline 中的传输方向是 tail -> head。
  • 5、在 ChannelHandler 中处理事件时,如果这个 Handler 不是最后一个 Handler,则需要调用 ctx 的方法(如: ctx.connect()方法)将此事件继续传播下去。如果不这样做,那么此事件的传播会提前终止。
  • 6、Outbound 事件流:Context.OUT_EVT() -> Connect.findContextOutbound() -> nextContext.invokeOUT_EVT() -> nextHandler.OUT_EVT() -> nextContext.OUT_EVT()

Inbound 事件总结:

  • 1、Inbound 事件是通知事件,当某件事情已经就绪后,通知上层。
  • 2、Inbound 事件发起者是 unsafe。
  • 3、Inbound 事件的处理者是 Channel,如果用户没有实现自定义的处理方法,那么 Inbound 事件默认的处理者是 TailContext,并且其处理方法是空实现。
  • 4、Inbound 事件在 Pipeline 中传输方向是 head -> tail。
  • 5、在 ChannelHandler 中处理事件时,如果这个 Handler 不是最后一个 Handler,则需要调用 ctx.fireIN_EVT()事 件(如:ctx.fireChannelActive()方法)将此事件继续传播下去。如果不这样做,那么此事件的传播会提前终止。
  • 6、Outbound 事件流:Context.fireIN_EVT() -> Connect.findContextInbound() -> nextContext.invokeIN_EVT() -> nextHandler.IN_EVT() -> nextContext.fireIN_EVT().

Handler 的各种姿势

ChannelHandlerContext

每个 ChannelHandler 被添加到 ChannelPipeline 后,都会创建一个ChannelHandlerContext 并与之创建的 ChannelHandler 关联绑定。ChannelHandlerContext 允许 ChannelHandler 与其他的 ChannelHandler 实现进行交互。 ChannelHandlerContext 不会改变添加到其中的ChannelHandler,因此它是安全的。下图描述了 ChannelHandlerContext、ChannelHandler、ChannelPipeline 的关系:
在这里插入图片描述

Channel 的生命周期

Netty 有一个简单但强大的状态模型,并完美映射到 ChannelInboundHandler 的各个方法。下面是 Channel 生命周期 中四个不同的状态:

状态描述
channelUnregistered()Channel已创建,还未注册到一个EventLoop上
channelRegistered()Channel已经注册到一个EventLoop上
channelActive()Channel是活跃状态(连接到某个远端),可以收发数据
channelInactive()Channel未连接到远端

一个 Channel 正常的生命周期如下图所示。随着状态发生变化相应的事件产生。这些事件被转发到 ChannelPipeline 中的 ChannelHandler 来触发相应的操作。
在这里插入图片描述

ChannelHandler 常用的 API

在这里插入图片描述
Netty定义了良好的类型层次结构来表示不同的处理程序类型,所有的类型的父类是ChannelHandler。ChannelHandler 提供了在其生命周期内添加或从 ChannelPipeline 中删除的方法。

状态描述
handlerAdded()ChannelHandler添加到实际上下文中准备处理事件
handlerRemoved()将ChannelHandler从实际上下文中删除,不再处理事件
exceptionCaught()处理抛出的异常

Netty 还提供了一个实现了 ChannelHandler 的抽象类 ChannelHandlerAdapter。ChannelHandlerAdapter 实现了父 类的所有方法,基本上就是传递事件到 ChannelPipeline 中的下一个 ChannelHandler 直到结束。我们也可以直接继承 于 ChannelHandlerAdapter,然后重写里面的方法。

ChannelInboundHandler

ChannelInboundHandler 提供了一些方法再接收数据或 Channel 状态改变时被调用。下面是 ChannelInboundHandler 的一些方法:

状态描述
channelRegistered()ChannelHandlerContext的Channel被注册到EventLoop
channelUnregistered()ChannelHandlerContext的Channel从EventLoop中注销
channelActive()ChannelHandlerContext的Channel已激活
channelInactiveChannelHanderContxt的Channel结束生命周期
channelRead从当前Channel的对端读取消息
channelReadComplete消息读取完成后执行
userEventTriggered一个用户事件被触发
channelWritabilityChanged改变通道的可写状态,可以使用Channel.isWritable()检查
exceptionCaught重写父类ChannelHandler的方法,处理异常

Netty 提 供 了 一 个 实 现 了 ChannelInboundHandler 接 口 并 继 承 ChannelHandlerAdapter 的 类 : ChannelInboundHandlerAdapter。ChannelInboundHandlerAdapter 实现了 ChannelInboundHandler 的所有方法, 作用就是处理消息并将消息转发到 ChannelPipeline 中的下一个 ChannelHandler。ChannelInboundHandlerAdapter 的 channelRead() 方 法 处 理 完 消 息 后 不 会 自 动 释 放 消 息 , 若 想 自 动 释 放 收 到 的 消 息 , 可 以 使 用 SimpleChannelInboundHandler,看下面的代码:

public class UnreleaseHandler extends ChannelInboundHandlerAdapter { 
	@Override public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception { 
	//手动释放消息 
	ReferenceCountUtil.release(msg); 
	} 
}

SimpleChannelInboundHandler 会自动释放消息:

public class ReleaseHandler extends SimpleChannelInboundHandler<Object> { 
	@Override 
	protected void channelRead0(ChannelHandlerContext ctx, Object msg) throws Exception { 
		//不需要手动释放 
	} 
}

ChannelInitializer 用来初始化 ChannelHandler,将自定义的各种 ChannelHandler 添加到 ChannelPipeline 中。

ChannelHandler注解

  • Sharable 多个ChannelPipeline共用同一个ChannelHandler
  • Skip 被Skip注解的方法不会被调用,直接被忽略
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值