Netty核心组件之Channel和ChannelFuture

本文详细分析了Netty框架中的核心组件Channel,包括其生命周期、网络I/O相关的方法、选择器工厂与轮询算法以及注册过程。Channel作为连接通道,负责网络I/O读写的抽象接口,其生命周期包括多个状态,注册过程涉及到EventLoop、Selector。ChannelFuture作为异步IO操作的结果,体现了Netty的异步非阻塞特性。通过对ChannelFuture的使用介绍,阐述了其在异步操作中的重要角色。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

2021SC@SDUSC

1. 核心组件

  •          EventLoop 和 EventLoopGroup(已分析)
  •          ChannelPipeline(ChannelPipeline中已分析)
  •          ChannelHandler(ChannelPipeline中已分析)
  •          ChannelHandlerContext(已分析)
  •          Channel(本节分析)
  •          Channel(本节分析)

2. ChannelHandler责任链模式的过滤链 (已分析)

3. 通信协议和私有协议栈开发


目录

一.Channel

1.  认识Channel

2. Channel网络I/O相关的方法定义

         3. Channel生命周期

         4.Channel选择器工厂与轮询算法及Channel注册过程

二ChannelFuture的用法


这次主要分析的内容是进行剩余的重要组件Channel,Selector的分析,并且结合前面分析的组件对Netty的一个整体结构进行梳理,以求对整体结构有更好的把握。

一.Channel

1.  认识Channel

Channel是客户端和服务端建立的一个连接通道,是Netty抽象出来的网络I/O读写相关的接口;客户端有一个Channel(SocketChannel),服务端也有一个Channel(NioSocketChannel),当客户端和服务端建立连接后,客户端的Channel会跟服务端的Channel进行联通;

2. Channel网络I/O相关的方法定义

我们来看一下对于每个方法的功能:

       id():获取该channel的标识

  eventloop():获取该channel注册的线程池

  parent():获取该channel的父channel,NIO没有父channel,一般为null

  config():获取该channel的配置

  isOpen():该channel是否打开状态

  isRegistered():该channel是否注册到线程池中

  isActive():该channel是否可用

  metadata():该channel的元数据

  localAddress():该channel的本地绑定地址端口

  remoteAddress():该channel连接的对端的地址端口

  closeFuture():该channel关闭时触发的future

  isWritable():该channel当前是否可写,只有IO线程会处理可写状态

  bytesBeforeUnwritable():该channel还能写多少字节

       unsafe():获取该channel的unsafe操作对象。对于channel的读写,一般不直接操作channel,而是转交给unsafe对象处理,channel本身通常只做查询状态,获取相关字段内容的操作。

       alloc():获取分配的缓冲区

  read():进行read操作

  write():进行write操作

 上面的方法我们看见了一个不熟悉的unsafe对象,这个也是一个比较重要的概念,理解这个类在整个结构所处的位置作用,对于我们理解框架有较大的帮助。

Unsafe被直接定义在Channel接口内部,意味着该接口是与Channel绑定的,上述方法的时候也解释过该类作用。channel本身不直接做相应的工作,交给unsafe方法调用。下图是unsafe的接口定义:

我们来看一下对于每个方法的功能:

  recvBufAllocHandle():获取处理读取channel数据之后处理的handler

  localAddress():本地地址端口

  remoteAddress():远程地址端口

  register():将channel注册到线程池中

  bind():服务端绑定本地端口

  connect():客户端连接远程端口

  disconnect():断开连接

  close():关闭channel

  closeForcibly():强制关闭

  deregister():移除线程池的注册

  beginRead():准备读取数据

  write():写入数据

  flush():强制刷新

  voidPromise():特殊的promise

  outboundBuffer():获取输出数据的buffer操作类

 从上面的一系列接口我们可以了解到,实际操作channel进行I/O读写操作的是unsafe类。channel本身不直接做相应的工作。

3. Channel生命周期

Channel生命周期如下(此生命周期即每次进行网络连接都要经过的流程):

当这些状态发生改变时,将会生成对应的事件。

这些事件将会被转发给 ChannelPipeline 中的 ChannelHandler,其可以随后对它们做出响应。

4.Channel选择器工厂与轮询算法及Channel注册过程

在分析EventLoop的时候,我们就知道在Netty 中, 每个 Channel 都有且仅有一个 EventLoop 与之关联。它们关联的过程如下所示:

我们观察到注册的入口,即在MultithreadEventLoopGroup.register(Channel channel)方法:

  public ChannelFuture register(Channel channel) {
        return next().register(channel);
    }

注册channel第一步调用了next()方法,next()是MultithreadEventLoopGroup里边的,获取一个可用的EventLoop。

    public EventLoop next() {
        return (EventLoop) super.next();
    }

可以看出来这里只是调用了父类的next()方法。

我们看一下next()方法在父类MultithreadEventExecutorGroup中的实现:

    public EventExecutor next() {
        return chooser.next();
    }

这里出现了一个chooser,我们来看一下它的结构:

private final EventExecutorChooserFactory.EventExecutorChooser chooser;

这里的EventExecutorChooserFactory是一个工厂,生产各种Executor。

我们先来看一下DefaultEventExecutorChooserFactory:

/*

 * 默认使用round-robin算法选择下一个实例的EventExecutor实现
 * round-robin:主要用在负载均衡方向,比如有5台机器,
   第一次分请求到了第一台机器,第二次到了第二台机器,第三次请求到了第三台请求,
   以此类推一直到第五台机器,然后第六次又到了第一台机器。
   这样一个轮流的调用,处理负载。这里的Executor数组也是使用这种方式,
   保证数组里边的EventExecutor被均衡调用。

 */

public final class DefaultEventExecutorChooserFactory implements EventExecutorChooserFactory {
    public EventExecutorChooser newChooser(EventExecutor[] executors) {
        if (isPowerOfTwo(executors.length)) {
            return new PowerOfTwoEventExecutorChooser(executors);
        } else {
            return new GenericEventExecutorChooser(executors);
        }
    }

PowerOfTwoEventExecutorChooser和GenericEventExecutorChooser都是DefaultEventExecutorChooserFactory 的静态内部类,都有next()方法返回一个EventExecutor。

当有2的指数个executor的时候,使用PowerOfTwoEventExecutorChooser性能会比非指数个的GenericEventExecutorChooser性能高一点。所以这里进行了这样的处理。

以上是对chooser的创建的一个分析, 现在回到MultithreadEventExecutorGroup看一下对chooser的赋值:


public abstract class MultithreadEventExecutorGroup extends AbstractEventExecutorGroup {
    private final EventExecutor[] children;
    protected MultithreadEventExecutorGroup(int nThreads, Executor executor,
          EventExecutorChooserFactory chooserFactory, Object... args) {

         children = new EventExecutor[nThreads];

         for (int i = 0; i < nThreads; i ++) {
            children[i] = newChild(executor, args);
         }   

        chooser = chooserFactory.newChooser(children);  
    }
}

children的来源是newChild()方法,这个方法我们也在EventLoop中进行过分析。

    /*
     创建一个EventExecutor ,稍后可以调用next()方法,这个next()方法被每个线程调用,这些线程是服务MultithreadEventExecutorGroup的
     */
    protected abstract EventExecutor newChild(Executor executor, Object... args) throws Exception;

接下来我们看一下register方法。

/**
 * Abstract base class for {@link EventLoop}s that execute all its submitted tasks in a single thread.
 * EventLoop的基础抽象类,所有提交的任务都会在一个线程里边执行。
 */

public abstract class SingleThreadEventLoop extends SingleThreadEventExecutor implements EventLoop {

    //关键代码
    public ChannelFuture register(Channel channel) {
        return register(new DefaultChannelPromise(channel, this));
    }
    
}

这里的DefaultChannelPromise是ChannelFuture的具体实现, 其持有Channel 和当前的EventLoop。

    public DefaultChannelPromise(Channel channel, EventExecutor executor) {
        super(executor);
        this.channel = channel;
    }

父类的实现方法如下图:

public class DefaultPromise<V> extends AbstractFuture<V> implements Promise<V> {
    public DefaultPromise(EventExecutor executor) {
        this.executor = checkNotNull(executor, "executor");
    }
}

最后我们来到register方法:

public abstract class SingleThreadEventLoop extends SingleThreadEventExecutor implements EventLoop
    public ChannelFuture register(final ChannelPromise promise) {
        ObjectUtil.checkNotNull(promise, "promise");
        promise.channel().unsafe().register(this, promise);
        return promise;
    }
}

这里的ChannelPromise 结构,其实是一个异步返回结果的封装,它持有channel和当前的SingleThreadEventLoop 。这个方法是注册逻辑的真正的入口了,出现了unsafe对象。前面我们说过是unsafe是能够真正的操作Channel。我们来看一下这个方法:

 protected abstract class AbstractUnsafe implements Unsafe {

        private volatile ChannelOutboundBuffer outboundBuffer = new ChannelOutboundBuffer(AbstractChannel.this);
        private RecvByteBufAllocator.Handle recvHandle;
        private boolean inFlush0;
        /** true if the channel has never been registered, false otherwise */
        private boolean neverRegistered = true;
  public final void register(EventLoop eventLoop, final ChannelPromise promise) {
            if (eventLoop == null) {
                throw new NullPointerException("eventLoop");
            }
            if (isRegistered()) {
                promise.setFailure(new IllegalStateException("registered to an event loop already"));
                return;
            }
            if (!isCompatible(eventLoop)) {
                promise.setFailure(
                        new IllegalStateException("incompatible event loop type: " + eventLoop.getClass().getName()));
                return;
            }
             //上边的逻辑主要是一些非空判断之类的东西

            AbstractChannel.this.eventLoop = eventLoop;

            //主要的注册逻辑分支,if和else分支可以看到最后调用的都是register0,但是else里边加了一个外壳---线程
            if (eventLoop.inEventLoop()) {
                register0(promise);
            } else {
                try {
                    eventLoop.execute(new Runnable() {
                        @Override
                        public void run() {
                            register0(promise);
                        }
                    });
                } catch (Throwable t) {
                    logger.warn(
                            "Force-closing a channel whose registration task was not accepted by an event loop: {}",
                            AbstractChannel.this, t);
                    closeForcibly();
                    closeFuture.setClosed();
                    safeSetFailure(promise, t);
                }
            }
        }
}

这里通过下面的语句对channel和eventLoop进行了绑定:

AbstractChannel.this.eventLoop = eventLoop;

这里的逻辑是判断当前线程是不是SingleThreadEventExecutor里边维护的线程。如果SingleThreadEventExecutor里边的线程不是当前线程,就新建一个Thread去执行register0,下边看一下register0的逻辑:

        private void register0(ChannelPromise promise) {
            try {
              
                if (!promise.setUncancellable() || !ensureOpen(promise)) {
                    return;
                }
                boolean firstRegistration = neverRegistered;
                doRegister();//核心注册方法
                neverRegistered = false;
                registered = true;

                pipeline.invokeHandlerAddedIfNeeded();

                safeSetSuccess(promise);
                pipeline.fireChannelRegistered();
                
                if (isActive()) {
                    if (firstRegistration) {
                        pipeline.fireChannelActive();
                    } else if (config().isAutoRead()) {
                         
                        beginRead();
                    }
                }
            } catch (Throwable t) {
               
                closeForcibly();
                closeFuture.setClosed();
                safeSetFailure(promise, t);
            }
        }

这里我们重点关注doRegister():

public abstract class AbstractNioChannel extends AbstractChannel {

//关键代码
    protected void doRegister() throws Exception {
        boolean selected = false;
        for (;;) {
            try {
                selectionKey = javaChannel().register(eventLoop().unwrappedSelector(), 0, this);
                
                return;
            } catch (CancelledKeyException e) {
                if (!selected) {                
                    eventLoop().selectNow();
                    selected = true;
                } else {                  
                    throw e;
                }
            }
        }
    }    
}

 javaChannel() 返回的是SelectableChannel。javaChannel().register是将channel注册到Selector上去,所以eventLoop().unwrappedSelector()返回的是Selector。

到这里我们看到了最终netty的JavaNio的注册实现。

小结:

结合之前对EventLoop的分析和通过上面对整个注册流程的代码分析,有下面这几点,我们要十分注意:

  1. 所有由EventLoop所处理的各种I/O事件都将在它所关联的那个Thread上进行处理。
  2. 一个Channel在它的整个生命周期中只会注册在一个EventLoop上。
  3. 一个EventLoop在运行过程中,会被分配给一个或多个Channel。

二. ChannelFuture的用法

我们下面简单的介绍一下ChannelFuture的用法。

ChannelFuture是Channel异步IO操作的结果。

什么是异步操作呢?

这个在我们上学期学习完操作系统后应该很好理解。异步操作比如说我执行一个IO调用,异步操作是立即返回的,同步操作是等待IO执行完毕后带着结果返回的,可能需要等待很长时间,让程序阻塞在这里。

Netty中的所有IO操作都是异步的。这意味着任何IO调用都将立即返回,而不能保证所请求的IO操作在调用结束时完成。这里将返回一个带有ChannelFuture的实例,该实例将提供有关IO操作的结果或状态的信息。

ChannelFuture要么是未完成状态,要么是已完成状态。IO操作刚开始时,将创建一个新的Future对象。新的Future对象最初处于未完成的状态。如果IO操作因为执行成功、执行失败或者执行取消导致操作完成,则将被标记为已完成的状态,并带有更多特定信息,例如失败原因。

到这里,我们可以大致猜测出netty的思想,既然调用还没有成功就返回了,那应该会有一个机制,在调用成功后来进行提醒。

ChannelFuture提供了各种方法,可以检查IO操作是否已完成,等待完成以及获取IO操作的结果。

在实现中,DefaultChannelFuture里面有一个listener的集合,addListener会添加listener到集合里面,然后再真正的io操作完成之后来触发相应的listener。

addListener(GenericFutureListener):

这个方法是非阻塞的。它只是将指定的ChannelFutureListener添加到ChannelFuture,并且与将来关联的IO操作完成时,IO线程将通知监听器。ChannelFutureListener完全不阻塞,因此可产生最佳的性能和资源利用率。


小结

截止到现在,对于Netty核心组件这些基础内容已经分析完毕。前面花费的时间也比较多,分析的也比较认真,是从最基础的源码一点一点去找的,为了打下一个比较好的基础。现在目前已经对netty的整体运作有了一个比较清楚的认识。还有一部分关于netty的线程模型等,我也进行了学习,如果后面还有时间,会再进行补充。

下面要更多地学习在基础上,netty是如何实现其他功能的。我主要负责Netty通信协议和私有协议栈开发这一部分。又是一个新的起点,希望可以顺利分析下去。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值