深入netty之BootStrap源码分析

4 篇文章 0 订阅
3 篇文章 0 订阅

本文所涉及到的源码部分均截取自netty:4.1.6

Bootstrap
Bootstrap 是 Netty 提供的一个便利的工厂类, 我们可以通过它来完成 Netty 的客户端或服务器端的 Netty 初始化。 下面我先来看一个例子, 从客户端和服务器端分别分析一下 Netty 的程序是如何启动的。首先,让我们从客户端的代码 片段开始:

EventLoopGroup group = new NioEventLoopGroup();
			try {
				Bootstrap b = new Bootstrap();
				b.group(group)
						.channel(NioSocketChannel.class)
						.option(ChannelOption.TCP_NODELAY, true)
						.handler(new ChannelInitializer<SocketChannel>() {
							@Override
							public void initChannel(SocketChannel ch) throws Exception {
								ChannelPipeline pipeline = ch.pipeline();
								pipeline.addLast("frameDecoder", new LengthFieldBasedFrameDecoder(Integer.MAX_VALUE, 0, 4, 0, 4));
								//对象参数类型编码器
								pipeline.addLast("encoder", new ObjectEncoder());
								//对象参数类型解码器
								pipeline.addLast("decoder", new ObjectDecoder(Integer.MAX_VALUE, ClassResolvers.cacheDisabled(null)));
								pipeline.addLast("handler",consumerHandler);
							}
						});
				//发起同步连接操作
				ChannelFuture future = b.connect("localhost", 8080).sync();
				future.channel().writeAndFlush(msg).sync();
				future.channel().closeFuture().sync();
				} catch(Exception e){
				e.printStackTrace();
			    }finally {//关闭,释放线程资源
				group.shutdownGracefully();
			    }

从上面的客户端代码虽然简单, 但是却展示了 Netty 客户端初始化时所需的所有内容:

1、EventLoopGroup:不论是服务器端还是客户端, 都必须指定 EventLoopGroup。在这个例子中, 指定了 NioEventLoopGroup, 表示一个 NIO 的 EventLoopGroup
2、ChannelType: 指定 Channel 的类型。 因为是客户端,因此使用了 NioSocketChannel。
3、Handler: 设置处理数据的 Handler

下面我们继续深入代码,看一下客户端通过 Bootstrap 启动后,都做了哪些工作?
我们在客户端连接代码的初始化 Bootstrap 中调用了一个 channel()方法,传入的参数是 NioSocketChannel.class
在这个方法中其实就是初始化了一个 ReflectiveChannelFactory 的对象:

   public B channel(Class<? extends C> channelClass) {
        if (channelClass == null) {
            throw new NullPointerException("channelClass");
        } else {
            return this.channelFactory((io.netty.channel.ChannelFactory)(new ReflectiveChannelFactory(channelClass)));
        }
    }

而 ReflectiveChannelFactory 实现了 ChannelFactory 接口, 它提供了唯一的方法, 即 newChannel()方法, ChannelFactory, 顾名思义, 就是创建 Channel 的工厂类。进入到 ReflectiveChannelFactory 的 newChannel()方法中, 代码如下:

 public T newChannel() {
        try {
            return (Channel)this.clazz.newInstance();
        } catch (Throwable var2) {
            throw new ChannelException("Unable to create Channel from class " + this.clazz, var2);
        }
    }

根据上面代码的提示,我们可以得出
1、Bootstrap 中的 ChannelFactory 实现类是 ReflectiveChannelFactory
2、通过 channel()方法创建的 Channel 具体类型是 NioSocketChannel

Channel 的实例化过程其实就是调用 ChannelFactory 的 newChannel()方法,而实例化的 Channel 具体类型又是和初 始化 Bootstrap 时传入的 channel()方法的参数相关。对于客户端的 Bootstrap 而言,创建的 Channel 实例就是 NioSocketChanne

客户端 Channel 的初始化

前面我们已经知道了如何设置一个 Channel 的类型,并且了解到 Channel 是通过 ChannelFactory 的 newChannel()方 法来实例化的, 那么 ChannelFactory 的 newChannel()方法在哪里调用呢?继续跟踪, 其调用链如下

在这里插入图片描述

在 AbstractBootstrap 的 initAndRegister()中调用了 ChannelFactory的 newChannel()来创建一个 NioSocketChannel 的实例,其源码如下:

final ChannelFuture initAndRegister() {
       // 去掉非关键代码 
        Channel channel = channelFactory.newChannel(); init(channel);
        this.init(channel);
        ChannelFuture regFuture = this.config().group().register(channel);
        return regFuture;
    }

在ChannelFactory的 newChannel()方法中,利用反射机制调用类对象的 newInstance()方法来创建一个新的 Channel 实例,相当于调用 NioSocketChannel 的默认构造器。NioSocketChannel 的默认构造器代码如下:

 public NioSocketChannel(SelectorProvider provider) {
        this(newSocket(provider));
    }

其中newSocket比较关键,在这里会调用 newSocket()来打开一个新的 Java NIO 的 SocketChannel:

  private static SocketChannel newSocket(SelectorProvider provider) {
        try {
            return provider.openSocketChannel();
        } catch (IOException e) {
            throw new ChannelException("Failed to open a socket.", e);
        }
    }

接着会调用父类, 即 AbstractNioByteChannel 的构造器:
AbstractNioByteChannel(Channel parent, SelectableChannel ch)
并传入参数 parent 为 null, ch 为刚才调用 newSocket()创建的 Java NIO 的 SocketChannel 对象, 因此新创建的 NioSocketChannel 对象中 parent 暂时是 null

protected AbstractNioByteChannel(Channel parent, SelectableChannel ch) { 
super(parent, ch, SelectionKey.OP_READ);
}

接着会继续调用父类 AbstractNioChannel 的构造器,并传入实际参数 readInterestOp = SelectionKey.OP_READ:

protected AbstractNioChannel(Channel parent, SelectableChannel ch, int readInterestOp) {
        super(parent);
        this.ch = ch;
        this.readInterestOp = readInterestOp;
        ch.configureBlocking(false);
        }
    }

在这里设置 Java NIO SocketChannel 为非阻塞的
然后继续调用父类 AbstractChannel 的构造器:

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

至此, NioSocketChannel 就初始化就完成了, 我们总结一下 NioSocketChannel 初始化所做的工作内容:

1、调用 NioSocketChannel.newSocket(DEFAULT_SELECTOR_PROVIDER)打开一个新的 Java NIOSocketChannel
2、AbstractChannel(Channel parent)中需要初始化的属性:

  • id:每个 Channel 都拥有一个唯一的 id。
  • parent:属性置为 null。
  • unsafe:通过 newUnsafe()实例化一个 unsafe 对象,它的类型是 AbstractNioByteChannel.NioByteUnsafe 内部类
  • pipeline:是 new DefaultChannelPipeline(this)新创建的实例。
    3、AbstractNioChannel 中需要初始化的属性:
  • ch:赋值为 Java SocketChannel,即 NioSocketChannel 的 newSocket()方法返回的Java NIO SocketChannel。
  • readInterestOp:赋值为 SelectionKey.OP_READ
  • ch:被配置为非阻塞,即调用 ch.configureBlocking(false)
    4、NioSocketChannel 中需要初始化的属性:
  • config = new NioSocketChannelConfig(this, socket.socket())

Unsafe 的初始化
看完了channel的初始化过程,有没有注意到一个熟悉的类,我们知道NIO可以通过操作堆外内存创建对象,而Unsafe 类就是java留的后门,但是实际上这里的Unsafe并不是java中的那个,这里的Unsafe是Netty框架中的类,它封装了对 Java 底层 Socket 的操作,是沟通 Netty 上层和 Java 底层的重要的桥梁

Unsafe 接口提供了很多相关的 Java 底层的 Socket 的操作

interface Unsafe { 
RecvByteBufAllocator.Handle recvBufAllocHandle(); 
SocketAddress localAddress(); 
SocketAddress remoteAddress(); 
void register(EventLoop eventLoop, ChannelPromise promise); 
void bind(SocketAddress localAddress, ChannelPromise promise);
...

继续回到 AbstractChannel 的构造方法中,在这里调用了 newUnsafe()获取一个新的 unsafe 对象,而 newUnsafe() 方法在 NioSocketChannel 中被重写了。来看代码:

 @Override
    protected AbstractNioUnsafe newUnsafe() {
        return new NioSocketChannelUnsafe();
    }

从这里我们就可以确定了, 在实例化的 NioSocketChannel 中的 unsafe 字段,其实是一个 NioSocketChannelUnsafe 的实例

Pipeline 的初始化

上面我们分析了 NioSocketChannel 的大体初始化过程, 但是伴随着channel的还有一个关键类,即 ChannelPipeline 。
我们知道,在实例化一个 Channel 时,必然都要实例化一个 ChannelPipeline。而我们确实在 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,这个 channel 就是我们实例化的 NioSocketChannel, DefaultChannelPipeline 会将这个 NioSocketChannel 对象保存在 channel 字段中。DefaultChannelPipeline 中还有两个特殊的字段,即 head 和 tail,这两个字段是双向链表的头和尾。用于维护一个以 AbstractChannelHandlerContext 为节点元素的双向链表,这个链表是 Netty 实现 Pipeline 机制的关键

先看看 HeadContext:

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

HeadContext调用了父类 AbstractChannelHandlerContext 的构造器,并传入参数 inbound = false,outbound = true
而 TailContext 的构造器与 HeadContext 的相反,它调用了父类 AbstractChannelHandlerContext 的构造器,并传入参数 inbound = true,outbound = false

即 header 是一个 OutBoundHandler,而 tail 是一个 InBoundHandler

EventLoop 的初始化

回到最开始的用户代码中,我们在一开始就实例化了一个 NioEventLoopGroup 对象,接下来我们就从它的构造器中追踪一下 EventLoop 的初始化过程。首先来看一下 NioEventLoopGroup 的类继承层次:
在这里插入图片描述
NioEventLoop 有几个重载的构造器,不过内容都没有太大的区别,最终都是调用的父类 MultithreadEventLoopGroup 的构造器:

   protected MultithreadEventLoopGroup(int nThreads, Executor executor, Object... args) {
        super(nThreads == 0 ? DEFAULT_EVENT_LOOP_THREADS : nThreads, executor, args);
    }

默认线程数
如果我们传入的线程数 nThreads 是 0,那么 Netty 会为我们设置默认的线程数 ,这个默认的线程数是怎么确定的呢? 其实很简单,在静态代码块中,Netty 首先会从系统属性中获取"io.netty.eventLoopThreads"的值,如果我们没有设置的话,那么就返回默认值:即处理 器核心数 * 2

 static {
        DEFAULT_EVENT_LOOP_THREADS = Math.max(1, SystemPropertyUtil.getInt(
                "io.netty.eventLoopThreads", Runtime.getRuntime().availableProcessors() * 2));

        if (logger.isDebugEnabled()) {
            logger.debug("-Dio.netty.eventLoopThreads: {}", DEFAULT_EVENT_LOOP_THREADS);
        }
    }

回到 MultithreadEventLoopGroup 构造器中,MultithreadEventLoopGroup 会继续调用父类 MultithreadEventExecutorGroup 的构造器:

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

我们继续跟踪到 newChooser 方法里面看看其实现逻辑,具体代码如下:

public EventExecutorChooser newChooser(EventExecutor[] executors) {
        if (isPowerOfTwo(executors.length)) {
            return new PowerOfTowEventExecutorChooser(executors);
        } else {
            return new GenericEventExecutorChooser(executors);
        }
    }

在这里会判断线程数量,如果 nThreads 是 2 的幂,则使用 PowerOfTwoEventExecutorChooser,否则 使用 GenericEventExecutorChooser。这两个 Chooser 都重写 next()方法。next()方法的主要功能就是将数组索引循环位移
在这里插入图片描述
当索引移动最后一个位置时,再调用 next()方法就会将索引位置重新指向 0

在这里插入图片描述
这里是netty性能压榨的一个典型例子,就连一个非常简单的数组索引运算,Netty 都帮我们做了优化。就是因为在计算机底层,&与比%的运算效率更高

MultithreadEventExecutorGroup 中的处理逻辑总结:
1、创建一个大小为 nThreads 的 EventExecutor 数组
2、根据 nThreads 的大小,创建不同的 Chooser,即如果 nThreads 是 2 的幂,则使用 PowerOfTwoEventExecutorChooser,反之使用 GenericEventExecutorChooser。不论使用哪个 Chooser,它们的功能都是一样的,即从 children 数组中选出一个合适的 EventExecutor 实例
3、调用 newChild()方法初始化 children 数组

根据上面的代码,我们知道了MultithreadEventExecutorGroup在内部维护了一个 EventExecutor 数组,而 Netty 的 EventLoopGroup的实现机制其实就建立在MultithreadEventExecutorGroup之上。每当Netty需要一个EventLoop 时, 会调用 next()方法获取一个可用的 EventLoop

上面代码还调用了 newChild()方法,这个是一个抽象方法,它的任务是实例化 EventLoop 对象。这个方法在 NioEventLoopGroup 类中有实现,其内容很简单:

 protected EventLoop newChild(Executor executor, Object... args) throws Exception {
        return new NioEventLoop(this, executor, (SelectorProvider) args[0],
            ((SelectStrategyFactory) args[1]).newSelectStrategy(), (RejectedExecutionHandler) args[2]);
    }

其逻辑很简单:就是实例化一个 NioEventLoop 对象, 然后返回 NioEventLoop 对象。

EventLoopGroup 的初始化过程总结:

1、EventLoopGroup(其实是 MultithreadEventExecutorGroup)内部维护一个类型为 EventExecutor children 数组, 其大小是 nThreads,这样就构成了一个线程池
2、如果在实例化 NioEventLoopGroup 时指定线程池大小,则 nThreads 就是指定的值,反之是处理 器核心数 * 2
3、MultithreadEventExecutorGroup 中会调用 newChild()抽象方法来初始化 children 数组(NioEventLoop 数组)
4、创建children 数组中对象的抽象方法 newChild()是在 NioEventLoopGroup 中实现的,它返回一个 NioEventLoop 实例
5、NioEventLoop 属性赋值:

  • provider:在 NioEventLoopGroup 构造器中通过 SelectorProvider.provider()获取
  • selector:在 NioEventLoop 构造器中通过调用通过 provider.openSelector()方法获取
Channel 注册到 Selector

在上面的分析中,我们知道了 Channel 会在 AbstractBootstrap 的 initAndRegister()中进行初始化,除此之外这个方法还负责将 Channe 注册到 NioEventLoop 的 selector 中

final ChannelFuture initAndRegister() { 
// 删除了非关键代码 
Channelchannel = channelFactory.newChannel(); 
init(channel); 
ChannelFuture regFuture = config().group().register(channel); 
return regFuture; 
}

接下来我们来分析一下 Channel 注册的过程:
当 Channel 初始化后,紧接着会调用 group().register()方法来向 selector 注册 Channel。其调用链如下:
在这里插入图片描述
通过跟踪调用链, 最终我们发现是调用到了 unsafe 的 register 方法:

public final void register(EventLoop eventLoop, final ChannelPromise promise) { 
// 省略了条件判断和错误处理的代码 
AbstractChannel.this.eventLoop = eventLoop; 
register0(promise); }

首先,将Channel 将传进来的 eventLoop 赋值给 eventLoop 属性,而我们知道这个 eventLoop 对象其实是通过MultithreadEventLoopGroup 的 next()方法获得的NioEventLoop 实例
register()方法接着调用了 register0()方法:

protected void doRegister() throws Exception { 
// 省略了错误处理的代码 
selectionKey = javaChannel().register(eventLoop().selector, 0, this); }

javaChannel()方法我们知道它返回的是一个 Java NIO 的 SocketChannel 对象,到这里为止 SocketChannel终于注册到了selector 上了

Channel 注册过程总结:
1、首先在 AbstractBootstrap 的 initAndRegister()方法中, 通过 group().register(channel),调用 MultithreadEventLoopGroup 的 register()方法
2、在 MultithreadEventLoopGroup 的 register()中,调用 next()方法获取一个可用的 SingleThreadEventLoop, 然 后调用它的 register()方法
3、在 SingleThreadEventLoop 的 register()方法中,调用 channel.unsafe().register(this, promise)方法来获取channel 的 unsafe()底层操作对象,然后调用 unsafe 的 register()
4、在 AbstractUnsafe 的 register()方法中, 调用 register0()方法注册 Channel 对象
5、在 register0()方法中,调用 AbstractNioChannel 的 doRegister()方法
6、AbstractNioChannel 的 doRegister()方法将对应的 Java NIO 的 SocketChannel对象 注册到一个 eventLoop 的 selector 中,并且将当前 Channel 作为 attachment 与 SocketChannel 关联

总的来说:
Channel 注册过程所做的工作就是将 Channel 与 EventLoop 关联,在 Netty 中,每个 Channel 都会关联一个 EventLoop,并且这个 Channel 中的所有 IO 操作都是在这个 EventLoop 中执行的
当关联好 Channel 和 EventLoop 后,会继续调用底层 Java NIO 的 SocketChannel 对象的 register()方法,将底层 Java NIO 的 SocketChannel 注册到指定的EventLoop 中的 selector 中。通过这两步,就完成了 Channel 的注册过程

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值