Netty的源码包里面,io.netty.example下有很多示例代码可供参考,是学习使用Netty的利器;另外要掌握netty,需要对它的关键源码有一定程度的了解。从这一章开始,我们开始跟着源码学习Netty的各核心模块,源码使用当前最新稳定版4.1.45.Final。
另外要能读懂Netty源码,有几个前提条件:
- 掌握java的多线程原理,有一定深度,尤其对java.util.concurrent.Executor框架要熟悉;
- 对TCP协议有一定了解,会Socket编程,掌握java NIO相关接口;
- 使用过Netty。
本系列文章剖析源码的策略:
- Netty是一个比较复杂的框架,尤其是一些核心类,功能非常多,牵涉的概念非常多,完全以类为单位来分析是徒劳的,我们经常是以某个功能为线索进行追踪;
- 为了节省篇幅,本文对源码做了裁剪处理,只贴关键部分,基本剔除了错误和异常处理(实际这是Netty最有价值的部分之一);
- 章节安排有严格顺序,在分析每个功能的实现细节过程中,不断洞察到Netty的设计思路,构成后续章节的基础。
看源码是一个非常细致的活,跟着别人的思路往往无法深入,所以读者必须实操一番才会正真掌握;本系列文章期望能够帮助大家理解关键部分,进而提高学习效率。
ServerBootStrap
ServerBootStrap是Netty服务端服务端的引导类,以io.netty.example.echo工程为例,一个典型的Netty服务端启动过程如下:
//创建bossGroup,指定线程数1
EventLoopGroup bossGroup = new NioEventLoopGroup(1);
//创建workerGroup,不指定线程数
EventLoopGroup workerGroup = new NioEventLoopGroup();
try {
ServerBootstrap b = new ServerBootstrap();
//指定bossGroup,和workerGroup
b.group(bossGroup, workerGroup)
//指定监听Channel的类型
.channel(NioServerSocketChannel.class)
//设定监听Channel的TCP参数
.option(ChannelOption.SO_BACKLOG, 100)
//设定监听Channel的handler
.handler(new LoggingHandler(LogLevel.INFO))
//设定读写Channel的handler
.childHandler(new ChannelInitializer<SocketChannel>() {
@Override
public void initChannel(SocketChannel ch) throws Exception {
ch.pipeline().addLast(new EchoServerHandler());
}
});
//绑定端口,开始监听连接请求
ChannelFuture f = b.bind(PORT).sync();
//主线程阻塞,等待监听Channel关闭
f.channel().closeFuture().sync();
} finally {
//关闭两个EventLoopGroup,和关闭java.concurent.Executor的原理类似
bossGroup.shutdownGracefully();
workerGroup.shutdownGracefully();
}
这一节我们围绕ServerBootstrap的启动过程,从源码角度剖析一下每个步骤背后的工作;按照我们的既定策略,这一步只深入一层,点到即止。
除非明确指出,下面的讨论假设Netty工作以Java NIO为底层接口,相关类型是NioEventLoopGroup,NioServerSocketChannel。
NioEventLoopGroup
无论是Netty客户端和服务端都要依赖EventLoopGroup来处理事件,前面在概念模型介绍中说过,EventLoopGroup是一个EventLoop组,每个EventLoop都是一个线程。我们以NioEventLoopGroup这个具体实现类作为切入点来探索一番。
当我们调用new NioEventLoopGroup()
时,后者调用了它的基类MultithreadEventLoopGroup构造方法:
class MultithreadEventLoopGroup {
private static final int DEFAULT_EVENT_LOOP_THREADS;
static {
DEFAULT_EVENT_LOOP_THREADS = Math.max(1, SystemPropertyUtil.getInt(
"io.netty.eventLoopThreads", NettyRuntime.availableProcessors() * 2));
}
protected MultithreadEventLoopGroup(int nThreads, Executor executor, Object... args) {
super(nThreads == 0 ? DEFAULT_EVENT_LOOP_THREADS : nThreads, executor, args);
}
}
参数nThreads代表EventLoopGrouop线程数,NioEventLoopGroup的默认构造函数,nThreads实参是0,将会被改写为DEFAULT_EVENT_LOOP_THREADS;而后者有两个来源,一是java系统属性:io.netty.eventLoopThreads,二是CPU核数x2,前者优先。也就是说,如果我们没有定义这个系统属性,NioEventLoopGroup线程数要么由参数指定,要么等于CPU核数x2。
继续跟进,来到MultithreadEventLoopGroup的基类MultithreadEventExecutorGroup构造函数:
class MultithreadEventExecutorGroup {
private final EventExecutor[] children;
private final Set<EventExecutor> readonlyChildren;
private final AtomicInteger terminatedChildren = new AtomicInteger();
private final Promise<?> terminationFuture = new DefaultPromise(GlobalEventExecutor.INSTANCE);
private final EventExecutorChooserFactory.EventExecutorChooser chooser;
protected MultithreadEventExecutorGroup(int nThreads, Executor executor,
EventExecutorChooserFactory chooserFactory, Object... args) {
//这个executor充当了线程工厂的角色
if (executor == null) {
executor = new ThreadPerTaskExecutor(newDefaultThreadFactory());
}
//创建一个EventExecutor数组,长度就是nThreads参数
//这个数组存放的就是EventLoop对象
children = new EventExecutor[nThreads];
for (int i = 0; i < nThreads; i ++) {
//创建EventLoop的地方
children[i] = newChild(executor, args);
}
...
}
protected abstract EventExecutor newChild(Executor executor, Object... args) throws Exception;
}
上面代码展示了创建EventLoop的过程,具体创建逻辑还要看newChild方法的实现,于是又回到NioEventLoopGroup:
class NioEventLoopGroup {
...
protected abstract EventExecutor newChild(Executor executor, Object... args) throws Exception {
EventLoopTaskQueueFactory queueFactory = args.length == 4 ? (EventLoopTaskQueueFactory) args[3] : null;
//创建NioEventLoop实例
return new NioEventLoop(this, executor, (SelectorProvider) args[0],
((SelectStrategyFactory) args[1]).newSelectStrategy(), (RejectedExecutionHandler) args[2], queueFactory);
}
}
可见NioEventLoopGroup最终按照指定的线程数,创建了对应数量的NioEventLoop。
关键点总结:
- NioEventLoopGroup本质上是一个固定线程数的线程池;
- NioEventLoopGroup的线程数量可以通过参数指定,也可以默认,默认值是CPU核数x2或java系统属性:io.netty.eventLoopThreads;
- NioEventLoopGroup的内部线程实例类型是NioEventLoop;
- EventExecutorChooser替Channel在NioEventLoopGroup选择一个EventLoop,它的默认实现就是轮询,这块不再深挖;
监听Channel的具体类型
SeverBootStrap的channel()方法,定义在基类AbstractBootstrap中:
class AbstractBootstrap {
//以及ChannelClass创建一个工厂类对象:ReflectiveChannelFactory
public B channel(Class<? extends C> channelClass) {
return channelFactory(new ReflectiveChannelFactory<C>(
ObjectUtil.checkNotNull(channelClass, "channelClass")
));
}
//指定一个channel工厂
public B channelFactory(ChannelFactory<? extends C> channelFactory) {
ObjectUtil.checkNotNull(channelFactory, "channelFactory");
this.channelFactory = channelFactory;
return self();
}
}
所以SeverBootStrap使用简单工厂模式来创建监听Socket Channel。
EventLoopGroup和channel的具体类型需要对应上,NioEventLoopGroup需要配合NioServerSocketChannel来使用。
bind
SeverBootStrap的handler(),option(),childHadle()只是将相关参数保存起来备用,它们的具体作用后续再分析。
我们来看看bind方法是如何工作的,SeverBootStrap.bind方法最终调用了AbstractBootstrap.doBind(略去调用转发和参数变换的中间过程):
class AbstractBootstrap {
//初始化监听channel,绑定网络端口
//bind是一个异步操作,所以返回ChannelFuture
//当ChannelFuture返回成功时,netty服务端启动完成
private ChannelFuture doBind(final SocketAddress localAddress) {
//注册监听channel
final ChannelFuture regFuture = initAndRegister();
final Channel channel = regFuture.channel();
//如果注册失败,提前结束
if (regFuture.cause() != null) {
return regFuture;
}
//注册已经成功
if (regFuture.isDone()) {
ChannelPromise promise = channel.newPromise();
//执行doBind0,并返回bind操作的Future
doBind0(regFuture, channel, localAddress, promise);
return promise;
}
else //绝大多数情况下注册会立即成功,否则需要监听regFuture,等注册成功后继续执行doBind0
{
final PendingRegistrationPromise promise = new PendingRegistrationPromise(channel);
regFuture.addListener(new ChannelFutureListener() {
@Override
public void operationComplete(ChannelFuture future) throws Exception {
Throwable cause = future.cause();
if (cause != null) {
promise.setFailure(cause);
} else {
promise.registered();
doBind0(regFuture, channel, localAddress, promise);
}
}
});
return promise;
}
}
//初始化监听Channel,并注册到bossGroup
final ChannelFuture initAndRegister() {
Channel channel = null;
try {
//创建channel
channel = channelFactory.newChannel();
init(channel);
} catch (Throwable t) {
//异常处理代码已省略
}
//注册channel到config().group(),实际就是bossGroup
ChannelFuture regFuture = config().group().register(channel);
if (regFuture.cause() != null) {
//异常处理代码已省略
}
return regFuture;
}
//Channel的具体初始化工作由子类来实现
abstract void init(Channel channel) throws Exception;
//Channel注册bossGroup成功后,调用channel.bind
private static void doBind0(final ChannelFuture regFuture, final Channel channel,
final SocketAddress localAddress, final ChannelPromise promise) {
channel.eventLoop().execute(new Runnable() {
@Override
public void run() {
if (regFuture.isSuccess()) {
channel.bind(localAddress, promise).addListener(ChannelFutureListener.CLOSE_ON_FAILURE);
} else {
promise.setFailure(regFuture.cause());
}
}
});
}
}
AbstractBootstrap.bind的逻辑可分成以下步骤:
- 调用channelFactory创建channel;
- 初始化channel,由子类来实现,这里是SeverBootStrap;
- 将channel注册到bossGroup,细节待探索;
- 调用channel.bind绑定端口,细节待探索。
上面代码中,注册channel到bossGroup是一个异步操作,通过ChannelFuture(ChannelPromise是ChannelFuture子类)来同步状态。
再看看SeverBootStrap.init如何初始化监听Channel:
Class SeverBootStrap {
@Override
void init(Channel channel) {
//这两步将之前保存的channel option,以及一些atrribute copy到channel对象内;
setChannelOptions(channel, options0().entrySet().toArray(EMPTY_OPTION_ARRAY), logger);
setAttributes(channel, attrs0().entrySet().toArray(EMPTY_ATTRIBUTE_ARRAY));
ChannelPipeline p = channel.pipeline();
final EventLoopGroup currentChildGroup = childGroup;
final ChannelHandler currentChildHandler = childHandler;
final Entry<ChannelOption<?>, Object>[] currentChildOptions = childOptions.entrySet().toArray(EMPTY_OPTION_ARRAY);
final Entry<AttributeKey<?>, Object>[] currentChildAttrs = childAttrs.entrySet().toArray(EMPTY_ATTRIBUTE_ARRAY);
//添加一个ChannelInitializer
p.addLast(new ChannelInitializer<Channel>() {
@Override
public void initChannel(final Channel ch) {
final ChannelPipeline pipeline = ch.pipeline();
ChannelHandler handler = config.handler();
//这是客户代码调用SeverBootStrap.handler配置的handler
if (handler != null) {
pipeline.addLast(handler);
}
//向channel所在的eventLoop提交了一个task,添加一个叫做ServerBootstrapAcceptor的handler到pipeline
ch.eventLoop().execute(new Runnable() {
@Override
public void run() {
pipeline.addLast(new ServerBootstrapAcceptor(
ch, currentChildGroup, currentChildHandler, currentChildOptions, currentChildAttrs));
}
});
}
});
}
}
SeverBootStrap.init方法最关键的逻辑就是添加了一个ServerBootstrapAcceptor到pipeline,回忆Reactor模型,相信你已经猜到这个它的角色。
ServerBootStrap要点总结
ServerBootStrap的启动过程大体如下:
- 创建两个EventLoopGroup(bossGroup和workerGroup),可指定线程数量(或默认CPU核数x2),EventLoopGroup内部创建对应数量、对应类型的EventLoop;
- NioEventLoopGroup内部创建NioEventLoop;
- NioEventLoopGroup继承自MultithreadEventExecutorGroup,是一个多线程Group,每个NioEventLoop相当于一个线程;
- 调用ServerBootStrap.channel配置监听Channel类型;
- 调用ServerBootStrap.option设置监听channel参数,调用childOption设置通信channel的参数;
- 调用ServerBootStrap.handler设置监听channel的handler,.childHandler设置通信channel的handler;
- 调用ServerBootStrap.bind初始化Channel,并完成注册、绑定端口的操作;
- 内部通过反射创建指定类型的Channel实例,并将相关参数Copy过去;
- 初始化channel的pipline,添加了一个神秘的ServerBootstrapAcceptor;
- 给channel分配一个EventLoop,并调用EventLoop.register注册channel,细节待探索;
- 调用channel.bind方法绑定端口,细节待探索。
这一节基本搞清楚了ServerBootStrap的启动过程,有以下几个遗留问题:
- 向EventLoop注册channel是如何实现的?
- NioServerSocketChannel的bind方法到底做了什么?
ChannelInitializer
我们抽空聊一下这个ChannelInitializer类,我们在配置ServerBootstrap的childHandler用到它,而上面的源码看到,SeverBootStrap在内部初始化监听channel时也用到了它。
首先它是一个ChannelHandler,但它的存在不是为了处理网络事件,而是为了注册其他ChannelHandler。
@Sharable
public abstract class ChannelInitializer<C extends Channel> extends ChannelInboundHandlerAdapter
{
private final Set<ChannelHandlerContext> initMap = Collections.newSetFromMap(
new ConcurrentHashMap<ChannelHandlerContext, Boolean>());
//初始化Channel接口,由具体类来实现
//个人认为,该方法叫iniChannelPipeline更好
protected abstract void initChannel(C ch) throws Exception;
//如果channel注册时,ChannelInitializer已经进入pipeline,那么由该方法来执行initChannel
@Override
@SuppressWarnings("unchecked")
public final void channelRegistered(ChannelHandlerContext ctx) throws Exception {
//当channel注册到EventLoopGroup之后的回调
if (initChannel(ctx)) {
//因为channel的pipeline被重新初始化了,所以从头fire register事件,让刚加入handler也能收到该事件;
ctx.pipeline().fireChannelRegistered();
//初始化完成,就从initMap移除ctx
removeState(ctx);
} else {
//如果initChannel已经执行过了,转发事件即可
ctx.fireChannelRegistered();
}
}
//如果channel注册时,ChannelInitializer尚未进入pipeline,那么由handlerAdded回调来执行initChannel
@Override
public void handlerAdded(ChannelHandlerContext ctx) throws Exception {
if (ctx.channel().isRegistered()) {
if (initChannel(ctx)) {
removeState(ctx);
}
}
}
//执行initChannel,调用子类的initChannel实现,并将自己移除
private boolean initChannel(ChannelHandlerContext ctx) throws Exception {
将ctx加入到initMap,防止重入导致多次初始化
if (initMap.add(ctx)) {
try {
initChannel((C) ctx.channel());
} catch (Throwable cause) {
exceptionCaught(ctx, cause);
} finally {
//initChannel成功后,ChannelInitializer完成了使命,将自己从pipeline移除
ChannelPipeline pipeline = ctx.pipeline();
if (pipeline.context(this) != null) {
pipeline.remove(this);
}
}
return true;
}
return false;
}
//将此ChannelHandlerContext从initMap移除
//按道理,上面initChannel方法已经将channel从pipeline移除了,至于为什么要考虑移除尚未完成的情况,暂不清楚
private void removeState(final ChannelHandlerContext ctx) {
if (ctx.isRemoved()) {
initMap.remove(ctx);
} else {
ctx.executor().execute(new Runnable() {
@Override
public void run() {
initMap.remove(ctx);
}
});
}
}
}
上面代码有几个值得注意的点:
- @Sharable:说明该handler实例可以为多个channel pipeline共享,所以它需要用一个集合来暂存正在初始化的ChannelHandlerContext;
- 一旦初始化完成,ChannelInitializer就从pipeline删除自己;
- 无论在channel注册到EvenLoop之前还是之后,都可以使用ChannelInitializer。
ChannelInitializer的存在是很有必要的,因为Channel是动态建立的,我们必须预先配置好pipeline的初始化逻辑,让Netty在创建新Channel之后来执行。不过将ChannelInitializer作为ChannelHandler的实现类,而不是单独作为一个抽象,确实造成了一点混淆。
NioServerSocketChannel的启动
现在我们回过头,解决ServerBootStrap启动过程中留下的两个问题,NioServerSocketChannel是如何注册 和 绑定端口的。
NioServerSocketChannel创建
我们要看一个前面忽略了的问题,关于NioServerSocketChannel创建的细节。ServerBootStrap通过反射创建NioServerSocketChannel实例,它的相关构造代码如下:
public class NioServerSocketChannel extends AbstractNioMessageChannel
implements io.netty.channel.socket.ServerSocketChannel {
private static final ChannelMetadata METADATA = new ChannelMetadata(false, 16);
private static final SelectorProvider DEFAULT_SELECTOR_PROVIDER = SelectorProvider.provider();
//静态方法,通过SelectorProvider创建一个java NIO ServerSocketChannel
private static ServerSocketChannel newSocket(SelectorProvider provider) {
try {
return provider.openServerSocketChannel();
} catch (IOException e) {
throw new ChannelException("Failed to open a server socket.", e);
}
}
private final ServerSocketChannelConfig config;
//默认构造方法,通过DEFAULT_SELECTOR_PROVIDER创建ServerSocketChannel
public NioServerSocketChannel() {
this(newSocket(DEFAULT_SELECTOR_PROVIDER));
}
//将ServerSocketChannel传递了给父类构造方法
public NioServerSocketChannel(ServerSocketChannel channel) {
super(null, channel, SelectionKey.OP_ACCEPT);
config = new NioServerSocketChannelConfig(this, javaChannel().socket());
}
}
- NioServerSocketChannel默认构造方法确实创建了一个java nio ServerSocketChannel;
- SelectorProvider是JDK提供的创建channel&selector等相关对象的抽象工厂,位于java.nio.channels.spi.SelectorProvider;
- ServerSocketChannelConfig是channel对外发布配置信息的抽象,是一个面向对象设计技巧,应该没有特别的用意。
NioServerSocketChannel注册
在AbstractBootstrap.initAndRegister的方法中,有一句代码:config().group().register(channel)
,将NioServerSocketChannel注册到bossGroup,所以我们追踪NioEventLoopGroup.register方法。
发现实现在基类MultithreadEventLoopGroup里。
class MultithreadEventLoopGroup {
@Override
public ChannelFuture register(Channel channel) {
return next().register(channel);
}
//super.next的实现就是用EventExecutorChooser选择一个EventLoop
@Override
public EventLoop next() {
return (EventLoop) super.next();
}
}
如上所示,register又被转发给了NioEventLoop;继续追踪发现,NioEventLoop.register实现在基类SingleThreadEventLoop里。
class SingleThreadEventLoop {
@Override
public ChannelFuture register(Channel channel) {
return register(new DefaultChannelPromise(channel, this));
}
@Override
public ChannelFuture register(final ChannelPromise promise) {
ObjectUtil.checkNotNull(promise, "promise");
promise.channel().unsafe().register(this, promise);
return promise;
}
}
如上所示,最终又绕回去了,回到了NioServerSocketChannel里面,调用了内部一个unsafe对象的register方法。
经过追踪,这个unSafe对象来自AbstractChannel的内部类——AbstractUnsafe:
class AbstractChannel.AbstractUnsafe implements Unsafe {
@Override
public final void register(EventLoop eventLoop, final ChannelPromise promise) {
//这里省略了一些检查代码
...
//要求注册必须在EventLoop线程执行,这也是为什么注册可能是异步的
//真正的逻辑在register0方法
if (eventLoop.inEventLoop()) {
register0(promise);
} else {
try {
eventLoop.execute(new Runnable() {
@Override
public void run() {
register0(promise);
}
});
} catch (Throwable t) {
//这里省略了异常处理代码
}
}
}
private void register0(ChannelPromise promise) {
//由于注册是异步的,这里检查channel是否已经关闭了
if (!promise.setUncancellable() || !ensureOpen(promise)) {
return;
}
//这个方法又回到Channel里去了
doRegister();
//如果channel第一次注册,Pipeline触发HandlerAdd事件,即Pipeline所有handler收到handlerAdded回调
pipeline.invokeHandlerAddedIfNeeded();
safeSetSuccess(promise);
//Pipeline触发ChannelRegister事件
pipeline.fireChannelRegistered();
//如果channel现在是活跃的
if (isActive()) {
//如果是第一次注册,触发ChannelActive事件
if (firstRegistration) {
pipeline.fireChannelActive();
}
//开始读数据,与https://github.com/netty/netty/issues/4805有关
else if (config().isAutoRead()) {
beginRead();
}
}
}
}
这个Unsafe类族有必要说明一下,它和JDK里的那个unsafe没有关系,它是Netty对底层IO逻辑的抽象;起名为Unsafe是为了警示用户,不要直接使用它。
继续追踪doRegister方法,这里的实现在AbstractNioChannel里面:
class AbstractNioChannel {
//ava nio channel
private final SelectableChannel ch;
//java nio SelectionKey
volatile SelectionKey selectionKey;
@Override
protected void doRegister() throws Exception {
boolean selected = false;
for (;;) {
try {
//javaChannel()返回的就是上面的nio channel
//eventLoop().unwrappedSelector()返回的就是nio selector对象;
selectionKey = javaChannel().register(eventLoop().unwrappedSelector(), 0, this);
return;
} catch (CancelledKeyException e) {
if (!selected) {
eventLoop().selectNow();
selected = true;
} else {
throw e;
}
}
}
}
}
上面代码复杂的异常处理咱们暂且不谈,doRegister方法执行的就是:将NioServerSocketChannel内部封装的nio channel注册到NioEventLoop封装的nio Selector。
NioServerSocketChannel绑定端口
前面已经看到AbstractBootstrap.doBind调用了NioServerSocketChannel.bind,它的实现在基类AbstractChannel里面:
class AbstractChannel {
@Override
public ChannelFuture bind(SocketAddress localAddress) {
return pipeline.bind(localAddress);
}
}
令人惊奇的是,居然将bind转发给了pipeline,我们知道pipeline只会将事件转发给handler,不会处理任何操作。
不过问题是,前面的分析可知,NioServerSocketChannel的pipline目前只有一个ServerBootstrapAcceptor,但是阅读源码发现ServerBootstrapAcceptor并没有实现bind。。。。
经过调试发现,pipeline的默认实现DefaultChannelPipeline,将pineline实现为双向链表,该链表有一个头结点、一个尾部节点,这两个节点可不仅仅是哨兵元素,而是包含逻辑的的,其中头结点HeadContext实现了bind方法:
class DefaultChannelPipeline.HeadContext extends AbstractChannelHandlerContext
implements ChannelOutboundHandler, ChannelInboundHandler {
private final Unsafe unsafe;
HeadContext(DefaultChannelPipeline pipeline) {
super(pipeline, null, HEAD_NAME, HeadContext.class);
unsafe = pipeline.channel().unsafe();
setAddComplete();
}
//原来是调用了Channel.unsafe.bind
@Override
public void bind(
ChannelHandlerContext ctx, SocketAddress localAddress, ChannelPromise promise) {
unsafe.bind(localAddress, promise);
}
}
于是我们又回到了AbstractUnsafe类:
class AbstractChannel.AbstractUnsafe {
@Override
public final void bind(final SocketAddress localAddress, final ChannelPromise promise) {
//这里忽略了部分检查逻辑
...
boolean wasActive = isActive();
try {
//doBind由AbstractChannel的子类实现
doBind(localAddress);
} catch (Throwable t) {
safeSetFailure(promise, t);
closeIfClosed();
return;
}
//如果channel绑定后,已经处于Active状态,触发pipeline ChannelActive事件
if (!wasActive && isActive()) {
invokeLater(new Runnable() {
@Override
public void run() {
pipeline.fireChannelActive();
}
});
}
safeSetSuccess(promise);
}
}
doBind方法又回到NioServerSocketChannel类:
class NioServerSocketChannel {
//其实就是调用了nio ServerSocketChannel的bind方法
@Override
protected void doBind(SocketAddress localAddress) throws Exception {
if (PlatformDependent.javaVersion() >= 7) {
javaChannel().bind(localAddress, config.getBacklog());
} else {
javaChannel().socket().bind(localAddress, config.getBacklog());
}
}
}
至此,bind的逻辑终于理顺了,出人意料地绕了很大一个圈子,细细体会有几点发现:
- 感觉netty的设计理念是,一旦channel注册完成,所有的操作都应该在EventLoop里面来做,避免多线程并发;
- bind方法实际是声明在接口ChannelOutboundHandler里,它还声明了connect,close等方法,这些操作从传播方向上看,都是outbound方向,作为ChannelOutboundHandler方法倒也合情合理;
- PipeLine的头节点HeadContext,实现了connect&close,基本都是转发给Channel的Unsafe成员,其他ChannelOutboundHandler实现就不用操心connect&close;
- 最终还是调用了Java NIO ServerSocketChannel的bind方法。
NioServerSocketChannel要点总结
NioServerSocketChannel启动的过程,涉及以下几个步骤:
- 创建NioServerSocketChannel,它内部封装Java NIO ServerSocketChannel;
- 将NioServerSocketChannel注册到NioEventLoopGroup;
- NioEventLoopGroup给channel分配一个NioEventLoop,channel实际注册到NioEventLoop;
- 在底层,将ServerSocketChannel(封装在NioServerSocketChannel内)注册到内Selector(封装在NioEventLoop内);
- NioEventLoop做好了将事件转发到Channel Pipeline的准备;
- NioServerSocketChannel绑定到端口;
- 这个操作通过Channel Pipeline来执行的,接受者是PipeLine的头结点HeadContext;
- HeadContext最终调用了Channel.unsafe.bind,印证了Channel的底层IO操作都封装在Unsafe抽象内;
Netty Server启动过程总结
通过读源码,我们基本印证了之前了解到的Netty工作模型,核心理念就是:bossGroup相当于Reactor模型中的主Reactor,它本质上是一个线程池(一般配置为单线程模式),用来监听客户端的链接。更重要的是,通过源码我们洞察到以下关键信息:
- MultithreadEventLoopGroup(所有多线程EventLoopGroup的基类)它的默认线程数是CPU核数x2;
- NioServerSocketChannel内部确实封装了一个java nio ServerSocketChannel,通过SelectorProvider创建的;
- ServerBootStrap.bind方法实现了NioServerSocketChannel的初始化、注册、绑定端口三个步骤;
- ServerBootStrap在初始化NioServerSocketChannel时,将一个类型为ServerBootstrapAcceptor的handler加入了它的pipeline中,从类名基本可以确定,该handler就是Reactor模型中的acceptor角色;
- NioEvengLoop内有一个Selector,在注册NioServerSocketChannel到EvengLoop时,内部将ServerSocketChannel注册到了Selector;
- Netty的Channel内部有一个抽象叫Unsafe,它封装了所有底层IO接口调用,channel相关的底层操作最终会经过该抽象,每个具体Channel类型都会实现它,所以以后要调试底层功能的话,我们在这守株待兔即可;
- 从线程角度来看,Channel的IO操作(包括bind,connect,close)是在EventLoop内执行的,换句话说,是通过pipleline来执行;为此Netty在pipeline内放置了头尾两个节点,实现了相关IO操作(其实就是转发给Channel的Unsafe);
我们虽然仅从NioServerSocketChannel入手分析了Netty Server端的启动过程,其他类型的Channel相信也是大同小异的。