本文所涉及到的源码部分均截取自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 的注册过程