netty的启动

181 阅读7分钟

客户端

基本流程:

  • 创建BootStrap对象;
  • 通过group方法设置线程池;
  • 通过channel方法设置IO通道类型;
  • 通过handler方法设置、添加handler。
  • connect连接

源码的分析也从以上几个步骤为切入点。

BootStrap

顾名思义,一个启动类,用于客户端建立连接、启动通道。

继承了AbstractBootStrap抽象类,AbstractBootStrap有两个实现,BootStrap和ServerBootStrap,分别用于客户端和服务端,包含一些build方法,用于设置一系列属性。同时AbstractBootStrap提供了bind相关的方法,用于服务端绑定socket,客户端可用于建立无连接的udp传输。

BootStrap,提供了connect等连接相关的方法,用于客户端建立连接。

NioEventLoopGroup

使用启动类的group建造者方法设置为AbstractBootstrap中的属性。

实现了ScheduledExecutorService等定时执行器相关的接口;同时实现了Iterable接口。可以看作是一个线程池,创建时如果不设置线程数,默认使用系统属性设置/CPU核数*2;

同时,在创建NioEventLoop时,初始化并优化创建了Selector,用于后面注册事件使用。

// NioEventLoopGroup
DEFAULT_EVENT_LOOP_THREADS = Math.max(1, SystemPropertyUtil.getInt(
                "io.netty.eventLoopThreads", NettyRuntime.availableProcessors() * 2));
super(nThreads == 0 ? DEFAULT_EVENT_LOOP_THREADS : nThreads, executor, args);
// MultithreadEventExecutorGroup
children = new EventExecutor[nThreads];
children[i] = newChild(executor, args);
// NioEventLoopGroup
return new NioEventLoop(this, executor, (SelectorProvider) args[0],
                        ((SelectStrategyFactory) args[1]).newSelectStrategy(), (RejectedExecutionHandler) args[2], queueFactory);
// NioEventLoop::openSelector()
unwrappedSelector = provider.openSelector();
// ...优化封装了java的SelectorImpl
selectedKeysField.set(unwrappedSelector, selectedKeySet);
publicSelectedKeysField.set(unwrappedSelector, selectedKeySet);

提供了next()方法按照调用顺序轮询选择一个EventLoop/EventExecutor返回。

NioSocketChannel

Bootstrap对象通过channel方法传入Class<? extends Channel>对象,netty提供了许多Channel实现类,如客户端/服务端、TCP/UDP/SCTP、阻塞/非阻塞等不同适用的实现类。

AbstractBootStrap拿到Class对象后,默认创建一个根据类型反射创建Channel的工厂,赋值给channelFactory属性。

return channelFactory(new ReflectiveChannelFactory<C>(ObjectUtil.checkNotNull(channelClass, "channelClass")));

ChannelHandler

通道处理器,用于处理Socket的读写数据、业务逻辑等。

Bootstrap对象通过handler方法传入通道处理器设置AbstractBootstrap的handler属性。

启动连接

connect方法经过参数校验、Socket地址创建等流程都会使用确定的remoteAddress,调用doResolveAndConnect方法,其核心的两句代码:

final ChannelFuture regFuture = initAndRegister();
return doResolveAndConnect0(channel, remoteAddress, localAddress, channel.newPromise());

initAndRegister方法的核心如下,先利用上面提到的channelFactory创建了通道,在利用上面提到的group调用了注册通道的方法。

channel = channelFactory.newChannel();
init(channel);
ChannelFuture regFuture = config().group().register(channel);

创建通道即利用反射调用了NioSocketChannel的空构造器,主要做了以下事情:

SelectorProvider到SocketChannel

// NioSocketChannel
private static final SelectorProvider DEFAULT_SELECTOR_PROVIDER = SelectorProvider.provider(); //  根据不同系统不同
public NioSocketChannel() {
  this(DEFAULT_SELECTOR_PROVIDER);
}
// 创建java NIO中的 SocketChannel
return provider.openSocketChannel();
// AbstractNioChannel
protected AbstractNioChannel(Channel parent, SelectableChannel ch, int readInterestOp) {
  super(parent);
  this.ch = ch;
  this.readInterestOp = readInterestOp;
  ch.configureBlocking(false);
}
// AbstractChannel
this.parent = parent;
id = newId();
unsafe = newUnsafe();
pipeline = newChannelPipeline();

关于SelectorProvider.provider()的获取,首先尝试从系统属性设置的实现类、ServiceLoader加载获取(系统类加载器能够加载的类/spi扩展),如果获取不到则使用DefaultSelectorProvider进行创建,Linux下与Mac下创建的SelectorProvider实例并不一致:

// DefaultSelectorProvider,Linux下
public static SelectorProvider create() {
    String var0 = (String)AccessController.doPrivileged(new GetPropertyAction("os.name"));
    if (var0.equals("SunOS")) {
        return createProvider("sun.nio.ch.DevPollSelectorProvider");
    } else {
        return (SelectorProvider)(var0.equals("Linux") ? createProvider("sun.nio.ch.EPollSelectorProvider") : new PollSelectorProvider());
    }
}
// Mac下
public static SelectorProvider create() {
  return new KQueueSelectorProvider();
}

SelectorProvider提供了创建Selector的方法,对应不同平台创建EPollSelectorImpl、PollSelectorImpl、KQueueSelectorImpl等。抽象类SelectorImpl抽象出select、selectedKeys等方法方便应用调用。

TODO,如何调用底层的等分析,使用hotspot源码未找到makePipe等调用

通过SelectorProvider创建SocketChannel后,继续实例化NioSocketChannel,可以看到主要将SocketChannel赋值给ch属性,创建了Unsafe对象,创建了通道的pipeline。

ChannelPipeline的创建

protected DefaultChannelPipeline(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;
}

双向链表,其中头节点、尾节点已创建完成,TailContext实现了ChannelInboundHandler,未处理的数据会打一条debug日志;HeadContext实现了ChannelInboundHandler、ChannelOutboundHandler,使用上面提到的channel的unsafe进行bind、connect、write、read等操作。

同时在Channel实例化完成后,会调用init(Channel)方法,Bootstrap拿到channel后执行handler的添加操作和channel的属性设置;handler会被包装成一个AbstractChannelHandlerContext后加入到pipeline双向链表的tail的前一个节点,其中AbstractChannelHandlerContext主要用来做一些回调事件的触发操作。

channel.pipeline().addLast(config.handler());

Channel的注册

回到上面连接,initAndRegister的init部分主要创建并初始化了channel,register主要是用使用EventLoopGroup使用channel做为参数调用register方法。

// MultithreadEventLoopGroup        
return next().register(channel);
// SingleThreadEventLoop        
return register(new DefaultChannelPromise(channel, this));
promise.channel().unsafe().register(this, promise);
// Unsafe 异步 + 一些回调事件
eventLoop.execute(()->register0(promise));
// AbstractNioChannel doRegister
selectionKey = javaChannel().register(eventLoop().unwrappedSelector(), 0, this); // todo 0 是什么意思

register将从EventLoopGroup中选择一个EventLoop调用register方法,将channel参数传入,EventLoop将自己做为Executor构建ChannelPromise,最终调用Unsafe的register方法。

Unsafe内部类对象处理使用eventLoop中的线程执行注册操作(此处实现了调用线程异步,通过ChannelPromise通信),然后负责做参数校验并处理pipeline的一系列钩子事件,在其中调用AbstractChannel的doREgister方法。doRegister方法在AbstractNioChannel中,可以看到使用java Nio的方式将该channel对应的java SocketChannel注册到了EventLoop的selector上。

猜想上述将register方法教给EventLoop线程执行的原因是,NioEventLoop在execute方法中开启了线程任务的执行,这里注册并开启了线程的轮询。

private void execute(Runnable task, boolean immediate) {
  addTask(task);
  startThread();
}

Handler的添加

Unsafe执行注册channel时,在调用完成doRegister后,会调用

pipeline.invokeHandlerAddedIfNeeded();   
// 最终会调用pipeline链表中的每个context的每个handlerAdded方法
handler().handlerAdded(this);

经常用到的ChannelInitializer便在调用handlerAdded时执行了initChannel(ctx)方法,执行了我们在外面写的一些列pipeline.addLast方法,然后将自己移除。

initChannel((C) ctx.channel());
pipeline.remove(this);

执行连接

initAndRegister后,doResolveAndConnect0方法中调用了doConnect方法

// Bootstrap
doConnect(remoteAddress, localAddress, promise);
// 同样交给通道的eventLoop执行连接,异步操作
channel.eventLoop().execute();
// channel -> pipeline -> handler
channel.connect(remoteAddress, localAddress, connectPromise);
return pipeline.connect(remoteAddress, localAddress, promise);
return tail.connect(remoteAddress, localAddress, promise);
// AbstractChannelHandlerContext,从尾部找到能够找到处理connect的第一个ChannelOutboundHandler执行connect
final AbstractChannelHandlerContext next = findContextOutbound(MASK_CONNECT);
next.invokeConnect(remoteAddress, localAddress, promise);
// 最终找到pipeline双向链表的头节点HeadContext,在其中使用unsafe执行
unsafe.connect(remoteAddress, localAddress, promise);
// unsafe最终调用了主类 NioSocketChannel 的doConnect方法,开始调用java的SocketChannel的方法进行绑定和连接
SocketUtils.bind(javaChannel(), localAddress);
boolean connected = SocketUtils.connect(javaChannel(), remoteAddress);

服务端

服务端启动的流程与客户端相似:

  • 对应启动类为ServerBootstrap,同样继承了AbstractBoostrap;
  • group需要使用两个,创建两个NioEventLoopGroup分别给bossGroup、workerGroup
  • 使用的Channel类型为NioServerSocketChannel,可以类比客户端,其创建、初始化、注册过程结合了java中的ServerSocketChannel,对应pipeline
  • 每个客户端连接时都会产生一个SocketChannel,对应另一个pipeline,区别ServerSocketChannel,使用ServerBootstrap的childHander方法添加handler

SocketChannel的产生、注册

服务端启动的过程与客户端不一样的一个地方,客户端NioSocketChannel创建时直接用SelectorProvider.openSocketChannel,而服务端对应NioServerSocketChannel创建时使用的是provider.openServerSocketChannel();得到ServerSocketChannel,只能在建立连接时才会accept产生SocketChannel。

// NioServerSocketChannel.newSocket
return provider.openServerSocketChannel();

NioServerSocketChannel相关事件最终会使用其内部类-Unsafe的子类NioMessageUnsafe进行read操作,最终调用的是NioServerSocketChannel的doReadMessages方法

// NioServerSocketChannel::doReadMessages
SocketChannel ch = SocketUtils.accept(javaChannel());
buf.add(new NioSocketChannel(this, ch));

可以看到,这里仍然是java NIO的方式产生了SocketChannel,并创建为NioSocketChannel添加到了事件对象列表中,NioMessageUnsafe在读取完成后,会触发读取完成的事件

pipeline.fireChannelReadComplete();

即将数据交给了NioServerSocketChannel的handler。

childHandler添加

初始化channel和pipeline时,ServerBootstrap实现了AbstractBoostrap的一个抽象方法init(Channel channel),在其中向NioServerSocketChannel的pipeline添加了一个handler ServerBootstrapAcceptor

pipeline.addLast(new ServerBootstrapAcceptor(
         ch, currentChildGroup, currentChildHandler, currentChildOptions, currentChildAttrs));

ServerBootstrapAcceptor是ServerBootstrap的一个内部类,是一个ChannelInboundHandlerAdapter,在NioServerSocketChannel有相关读数据事件时会出发相关回调,NioSocketChannel将会被unsafe读取完成传递到这里。

public void channelRead(ChannelHandlerContext ctx, Object msg) {
  final Channel child = (Channel) msg;

  child.pipeline().addLast(childHandler);

  setChannelOptions(child, childOptions, logger);
  setAttributes(child, childAttrs);
    
 	childGroup.register(child);
}

这便是熟悉的操作,添加childHandler,注册到childGroup的EventLoop上。