导读
原创文章,转载请注明出处。
本文源码地址:netty-source-code-analysis
本文所使用的netty版本4.1.6.Final:带注释的netty源码
我们在“BIO vs NIO”这篇文章中我们给出了使用jdk原生nio编写的服务端Hello World。还记得其中的关键步骤吗,咱们再来温习一下。
-
创建一个ServerSocketChannel
-
将ServerSocketChannel设置为非阻塞的
-
将ServerSocketChannel绑定到8000端口
-
将ServerSocketChannel注册到selector上
今天我们就以这几个关键步骤为目标来看一下在netty中是怎么做的,以及在这几个步骤的中间netty又多做了哪些工作。
1 服务端引导代码
以下代码引导启动一个服务端,在以下文章中我们以“引导代码”指代这段程序。这段代码很简单,创建两个EventLoopGroup
分别为bossGroup
和workerGroup
。创建一个ServerBoostrap
并将bossGroup
和workerGroup
传入,配置一个handler
,该handler
为监听端口这条连接所使用的handler
。接着又设置了一个childHandler
即新连接所使用的handler
,本篇文章我们不讲新连接的接入,所以这里的childHandler
里什么也没做。
运行这段这段代码将在控制台打出如下结果。
HandlerAdded
ChannelRegistered
ChannelActive
/**
* 欢迎关注公众号“种代码“,获取博主微信深入交流
*
* @author wangjianxin
*/
public class com.zhongdaima.netty.analysis.bootstrap.ServerBoot {
public static void main(String[] args) throws InterruptedException {
EventLoopGroup bossGroup = new NioEventLoopGroup(1);
EventLoopGroup workerGroup = new NioEventLoopGroup(1);
try {
ServerBootstrap b = new ServerBootstrap();
b.group(bossGroup, workerGroup)
.channel(NioServerSocketChannel.class)
.option(ChannelOption.TCP_NODELAY, true)
.attr(AttributeKey.valueOf("ChannelName"), "ServerChannel")
.handler(new ChannelInboundHandlerAdapter() {
@Override
public void channelRegistered(ChannelHandlerContext ctx) throws Exception {
System.out.println("ChannelRegistered");
}
@Override
public void channelActive(ChannelHandlerContext ctx) throws Exception {
System.out.println("ChannelActive");
}
@Override
public void handlerAdded(ChannelHandlerContext ctx) throws Exception {
System.out.println("HandlerAdded");
}
}).childHandler(new ChannelInboundHandlerAdapter(){
});
ChannelFuture f = b.bind(8000).sync();
f.channel().closeFuture().sync();
} finally {
bossGroup.shutdownGracefully();
workerGroup.shutdownGracefully();
}
}
}
复制代码
2 启动过程
我们从ChannelFuture f = b.bind(8000).sync()
的bind
方法往下跟到AbstractBootStrap
的doBind
方法,这中间的过程很简单,就是将端口号封装为SocketAddress
。
在doBind
内的关键代码有第一行的initAddRegister
方法,还有后面的doBind0
方法。
private ChannelFuture doBind(final SocketAddress localAddress) {
final ChannelFuture regFuture = initAndRegister();
if (regFuture.isDone()) {
doBind0(regFuture, channel, localAddress, promise);
} else {
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) {
} else {
doBind0(regFuture, channel, localAddress, promise);
}
}
});
return promise;
}
}
复制代码
进入到initAndRegister
方法,initAddResgiter
方法中有3个关键步骤,1是channelFactory.newChannel()
,2是init(channel)
,3是config().group().register(channel)
。
final ChannelFuture initAndRegister() {
Channel channel = null;
try {
//创建一个Channel
channel = channelFactory.newChannel();
//初始化Channel
init(channel);
} catch (Throwable t) {
}
//注册Channel
ChannelFuture regFuture = config().group().register(channel);
...
}
复制代码
整个doBind
方法被分成4个关键步骤,分别是:
channelFacotry.newChannel()
init(channel)
config().group().register(channel)
doBind0
接下来咱们分别来看这4个关键步骤。
2.1 新创建一个Channel
channelFacotry
是AbstractBootStrap
的一个属性,这个属性在哪里被赋值呢,其实是在我们在启动时调用b.channel(NioServerSocketChannel)
时赋的值,这个方法在AbstractBootStrap
里,非常简单,我们不再分析。最后的结果是channelFactory
被赋值为ReflectiveChannelFactory
,顾名思义就是用反射的方法创建Channel
,我看们一下其中的newChannel()
方法,很简单,clazz.newInstance
调用无参构造方法创建实例。
public class ReflectiveChannelFactory<T extends Channel> implements ChannelFactory<T> {
private final Class<? extends T> clazz;
@Override
public T newChannel() {
try {
return clazz.newInstance();
} catch (Throwable t) {
throw new ChannelException("Unable to create Channel from class " + clazz, t);
}
}
}
复制代码
接下来咱们就看一下NioServerSocketChannel
的无参构造方法,其中调用newSocket
方法创建了一个jdk的ServerSocketChannel
。好了,咱们已经看到了导读中提到的第1步“创建一个ServerSocketChannel”,紧着把这个channel
传递给了父类的构造方法,还传递一个参数SelectionKey.OP_ACCEPT
,记住这个参数后面会提到。
public NioServerSocketChannel() {
this(newSocket(DEFAULT_SELECTOR_PROVIDER));
}
private static ServerSocketChannel newSocket(SelectorProvider provider) {
try {
return provider.openServerSocketChannel();
} catch (IOException e) {
}
}
public NioServerSocketChannel(ServerSocketChannel channel) {
super(null, channel, SelectionKey.OP_ACCEPT);
config = new NioServerSocketChannelConfig(this, javaChannel().socket());
}
复制代码
咱们接着跟到父类AbstractNioMessageChannel
的构造方法,没什么其他操作,继续调用父类的构造方法。
protected AbstractNioMessageChannel(Channel parent, SelectableChannel ch, int readInterestOp) {
super(parent, ch, readInterestOp);
}
复制代码
接着跟下去,到了AbstractNioChannel
的构造方法,在这里我们看到了ch.configureBlocking(false)
,至此我们看到了导读中提到的第2步“将Channel设置为非阻塞的”。AbstractNioChannel
里又调用了父类的构造方法,接着看下去。
protected AbstractNioChannel(Channel parent, SelectableChannel ch, int readInterestOp) {
super(parent);
this.ch = ch;
this.readInterestOp = readInterestOp;
try {
//将Channel设置为非阻塞的
ch.configureBlocking(false);
} catch (IOException e) {
throw new ChannelException("Failed to enter non-blocking mode.", e);
}
}
复制代码
到了AbstractChannel
的构造方法,这里为Channel
创建了一个id,一个Unsafe
还有一个PipeLine
。Unsafe
和PipeLine
咱们后面再讲。
protected AbstractChannel(Channel parent) {
this.parent = parent;
id = newId();
unsafe = newUnsafe();
pipeline = newChannelPipeline();
}
复制代码
2.2 初始化Channel
我们回到AbstractBootstrap
的initAndRegister
方法,接着往下看init(channel)
,这是个抽象方法,实现在ServerBootstrap
里。
init
方法的主要逻辑是设置Channel
参数、属性,并将我们在引导代码中所配置的Handler
添加进去,最后又添加了一个ServerBootStrapAccptor
,顾名思义这是一个处理新连接接入的Handler
。
这个ServerBootStrapAccptor
在随后的章节中我们会讲,这里先略过。至于为什么调用ch.eventLoop().execute
而不是直接添加,这个我在代码里有简要提示,其实目前的版本,直接添加也是没有问题的。这个我会在出视频教程的时候给大家演示一下,欢迎关注。
void init(Channel channel) throws Exception {
//设置Channel参数,我们在引导代码中通过.option(ChannelOption.TCP_NODELAY, true)所设置的参数
final Map<ChannelOption<?>, Object> options = options0();
synchronized (options) {
channel.config().setOptions(options);
}
//设置Channel属性,我们在引导代码中通过.attr(AttributeKey.valueOf("ChannelName"), "ServerChannel")所设置的属性
final Map<AttributeKey<?>, Object> attrs = attrs0();
synchronized (attrs) {
for (Entry<AttributeKey<?>, Object> e : attrs.entrySet()) {
@SuppressWarnings("unchecked")
AttributeKey<Object> key = (AttributeKey<Object>) e.getKey();
channel.attr(key).set(e.getValue());
}
}
ChannelPipeline p = channel.pipeline();
//p.addLast是同步调用,不管是不是EventLoop线程在执行,这个匿名的ChannelInitializer被立即添加进PipeLine中
//但是这个匿名的ChannelInitializer的initChannel方法是被channelAdded方法调用的,而channelAdded方法只能被EventLoop线程调用
//此时这个Channel还没绑定EventLoop线程,所以这个匿名的ChannelInitializer的channelAdded方法的调用会被封装成异步任务添加到PipeLine的pendingHandlerCallback链表中
//当Channel绑定EventLoop以后会从pendingHandlerCallback链表中取出任务执行。
p.addLast(new ChannelInitializer<Channel>() {
@Override
public void initChannel(Channel ch) throws Exception {
final ChannelPipeline pipeline = ch.pipeline();
ChannelHandler handler = config.handler();
if (handler != null) {
//添加我们在引导代码中所配置的handler
pipeline.addLast(handler);
}
//有些同学对这个有疑问,为什么不直接pipeline.addLast,可以参考下面的issue,其实现在的版本已经可以直接改成pipeline.addLast
//issue链接https://github.com/netty/netty/issues/5566
//为什么现在的版本可以直接改成pipeline.adLast呢,关键在于ChannelInitializer的handlerAdded方法
//大家可以对比4.0.39.Final版本和4.1.6.Final版本的区别
//添加ServerBootStrapAcceptor
ch.eventLoop().execute(new Runnable() {
@Override
public void run() {
pipeline.addLast(new ServerBootstrapAcceptor(
currentChildGroup, currentChildHandler, currentChildOptions, currentChildAttrs));
}
});
}
});
}
复制代码
2.3 绑定EventLoop
并向Selector
注册Channel
我们回到AbstractBootstrap
的initAndRegister
方法,接着往下看到ChannelFuture regFuture = config().group().register(channel);
,这里就是注册Channel的地方了,咱们跟进去看看。
config.group()
的返回是我们在引导代码中所设置的bossGroup
,由于这里只有一个Channel
,所以bossEventLoopGroup
里面只需要1个EventLoop
就够了。
跟到register(channel)
方法里看看,这个register
方法是抽象的,具体实现在MultithreadEventLoopGroup
中,跟进去。
@Override
public ChannelFuture register(Channel channel) {
return next().register(channel);
}
复制代码
next()
方法调用EventExecutorChooser
的next()
方法选择一个EventLoop
。EventExecutorChooser
有两个实现,分别是PowerOfTowEventExecutorChooser
和GenericEventExecutorChooser
,这两个Chooser
用的都是轮询策略,只是轮询算法不一样。如果EventLoopGroup
内的EventLoop
个数是2的幂,则用PowerOfTowEventExecutorChooser
,否则用GenericEventExecutorChooser
。
PowerOfTowEventExecutorChooser
使用位操作。
@Override
public EventExecutor next() {
return executors[idx.getAndIncrement() & executors.length - 1];
}
复制代码
而GenericEventExecutorChooser
使用取余操作。
@Override
public EventExecutor next() {
return executors[Math.abs(idx.getAndIncrement() % executors.length)];
}
复制代码
从EventLoop
的选择算法上我们可以看出,netty为了性能,无所不用其极。
chooser
属性的赋值在MultithreadEventExecutorGroup
的构造方法内通过chooserFactory
创建的。
protected MultithreadEventExecutorGroup(int nThreads, Executor executor,
EventExecutorChooserFactory chooserFactory, Object... args) {
chooser = chooserFactory.newChooser(children);
}
复制代码
而chooserFactory
的赋值在MultithreadEventExecutorGroup
的另一个构造方法内。当我们在引导代码中通过new NioEventLoopGroup(1)
创建EventLoopGroup
时最终会调用到这个构造方法内,默认值为DefaultEventExecutorChooserFactory.INSTANCE
。
protected MultithreadEventExecutorGroup(int nThreads, Executor executor, Object... args) {
this(nThreads, executor, DefaultEventExecutorChooserFactory.INSTANCE, args);
}
复制代码
next()
方法选出的EventLoop
就是个SingleThreadEventLoop
了,我们跟到SingleThreadEventLoop
的register
方法,最终调用的是unsafe
的register
方法。
@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;
}
复制代码
unsafe.register
方法在io.netty.channel.AbstractChannel.AbstractUnsafe
内,我们跟下去看看。在register
方法中最主要的有两件事,一是绑定eventloop
,二是调用register0
方法。此时的调用线程不是EventLoop
线程,会发起一个异步任务。
@Override
public final void register(EventLoop eventLoop, final ChannelPromise promise) {
//绑定eventloop
AbstractChannel.this.eventLoop = eventLoop;
if (eventLoop.inEventLoop()) {
register0(promise);
//此时我们不在EventLoop内,也就是当前线程非EventLoop线程,会走到这个分支
} else {
try {
eventLoop.execute(new Runnable() {
@Override
public void run() {
//调用子类的register0方法
register0(promise);
}
});
} catch (Throwable t) {
}
}
}
复制代码
register0
方法内主要有3步操作。
第1步是doRegister()
,这个咱们稍后说。
第2步是pipeline.invokeHandlerAddedIfNeeded()
这一步是去完成那些在绑定EventLoop
之前触发的添加handler
操作,比如我们添加了一个ChannelInitializer
,在ChannelInitalizer
的initChannel
方法中添加的Handler
,而initChannel
被channelAdded
方法调用,channelAdded
方法的调用必须在EventLoop
内,未绑定EventLoop
之前这个调用会被封装成异步任务。
这些操作被放在pipeline
中的pendingHandlerCallbackHead
中,是个双向链表,具体请参考DefaultChannelPipeLine
的addLast(EventExecutorGroup group, String name, ChannelHandler handler)
方法。
这一步调用了咱们的引导程序中的 System.out.println("HandlerAdded")
,在控制台打出"HandlerAdded"
。
第3步触发ChannelRegistered
事件。这一步调用了咱们的引导程序中的 System.out.println("ChannelRegistered")
,在控制台打出"ChannelRegistered"
。
好了,到这里我们已经知道了,为什么我们的引导程会先打出"HandlerAdded"
和"ChannelRegistered"
。
接着往下isActive()
最终调用是的jdk ServerSocket
类的isBound
方法,咱们不再贴出代码,读者自行查看,很简单,显然这里我们还没有完成端口绑定,所以这个if
分支的代码并不会执行。
private void register0(ChannelPromise promise) {
try {
//向Selector注册Channel
doRegister();
//去完成那些在绑定EventLoop之前触发的添加handler操作,这些操作被放在pipeline中的pendingHandlerCallbackHead中,是个链表,具体请参考`DefaultChannelPipeLine`的`addLast(EventExecutorGroup group, String name, ChannelHandler handler)`方法。
pipeline.invokeHandlerAddedIfNeeded();
//将promise设置为成功的
safeSetSuccess(promise);
//触发ChannelRegistered事件
pipeline.fireChannelRegistered();
//这里并没有Active,因为此时还没完成端口绑定,所以这个if分支的代码都不会执行
if (isActive()) {
if (firstRegistration) {
pipeline.fireChannelActive();
} else if (config().isAutoRead()) {
beginRead();
}
}
} catch (Throwable t) {
}
}
复制代码
接下来咱们跟进去doRegister
方法,这是个抽象方法,本例中方法实现在AbstractNioChannel
中。好了,到这里我们终于看到了导读中提到的第4步“向Selector
注册Channel
”的操作。
@Override
protected void doRegister() throws Exception {
boolean selected = false;
for (;;) {
try {
selectionKey = javaChannel().register(eventLoop().selector, 0, this);
return;
} catch (CancelledKeyException e) {
}
}
}
复制代码
到了这里,我们在导读中说的总共4步操作中,还有第3步没有看到,在哪里呢,接着往下看。
2.4 绑定端口号
前文中我们说过doBind
方法内有两个重要调用initAndRegister
和doBind0
,initAndRegister
我们已经分析完了,接下来看doBind0
。由于initAndRegister
中register
是异步操作,当initAndRegister
返回时,register
操作有可能完成了,也有可能没完成,这里做了判断,如果已经完成则直接调用doBind0
,如果未完成,则将doBind0
放到regFuture
的Listener
中,等register
操作完成后,由EventLoop
线程来回调。
那么什么时候会回调Listener
呢,当调用promise
的setSuccess
或者setFailure
时回调。还记得上文中的AbstractUnsafe.register0
方法吗,其中有一个调用safeSetSuccess(promise)
,对,就是这里了,很简单,我们不再赘述。
private ChannelFuture doBind(final SocketAddress localAddress) {
final ChannelFuture regFuture = initAndRegister();
if (regFuture.isDone()) {
ChannelPromise promise = channel.newPromise();
doBind0(regFuture, channel, localAddress, promise);
return promise;
} else {
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) {
} else {
doBind0(regFuture, channel, localAddress, promise);
}
}
});
return promise;
}
}
复制代码
那么又有读者疑问了,在这个if
判断完成之后到添加Listener
之间的这个时间,promise
有可能已经完成了,Listener
可能不会回调了, 奥秘在DefaultPromise
的addListener(GenericFutureListener<? extends Future<? super V>> listener)
方法里,这里注册完Listener
之后,如果发现promise
已经完成了,那么将直接调用nofityListeners
方法向EventLoop
提交异步任务(此时已经完成绑定EventLoop
),该异步任务即是回调刚刚注册的Listener
。
@Override
public Promise<V> addListener(GenericFutureListener<? extends Future<? super V>> listener) {
synchronized (this) {
addListener0(listener);
}
if (isDone()) {
notifyListeners();
}
return this;
}
复制代码
咱们回归正题,去看doBind0
方法,这里调用了channel.bind
方法,具体实现在AbstractChannel
里。
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());
}
}
});
}
复制代码
AbstractChannel
里的bind
方法调用了pipeline.bind
,还记得一篇“Netty整体架构”文章中的那张图吗,咱们再次放出来。
bind
方法会首先调用Tail的bind
方法,最终传播到Head
的bind
方法,具体怎么传播的,咱们讲PipeLine
的时候再说。
@Override
public ChannelFuture bind(SocketAddress localAddress, ChannelPromise promise) {
return pipeline.bind(localAddress, promise);
}
复制代码
这里咱们直接跟到HeadContext
的bind
方法, 我们看到又调用了unsafe
的bind
方法,前面我们看到Channel
在向Selector
注册时最终也调用到了unsafe
。这里先跟大家说一下unsafe
是netty中最直接跟Channel
接触的类,对Channel
的所有操作最终都会落到unsafe
上,具体详情咱们后面讲unsafe
的时候再说。
@Override
public void bind(
ChannelHandlerContext ctx, SocketAddress localAddress, ChannelPromise promise)
throws Exception {
unsafe.bind(localAddress, promise);
}
复制代码
具体实现在AbstractUnsafe
中,bind方法中两个重要操作,一是调用doBind
方法绑定端口,这个稍后说。二是触发ChannelActive
事件,这一步有一个isActive
判断,到这里我们已经完成了端口绑定,所以是true。这一步调用了咱们引导程序中的System.out.println("ChannelActive")
在控制台打印出"ChannelActive"
。
@Override
public final void bind(final SocketAddress localAddress, final ChannelPromise promise) {
boolean wasActive = isActive();
try {
doBind(localAddress);
} catch (Throwable t) {
}
if (!wasActive && isActive()) {
invokeLater(new Runnable() {
@Override
public void run() {
pipeline.fireChannelActive();
}
});
}
}
复制代码
doBind
方法的实现在NioServerSocketChannel
中,我们一起来看一下,至此导读中提到的第3步操作“绑定端口”,我们已经看到了,服务端启动完成。
protected void doBind(SocketAddress localAddress) throws Exception {
if (PlatformDependent.javaVersion() >= 7) {
javaChannel().bind(localAddress, config.getBacklog());
} else {
javaChannel().socket().bind(localAddress, config.getBacklog());
}
}
复制代码
2.4.1 注册兴趣事件
但是似乎是不是少了点什么,我们在使用jdk api编写的时候,向selector
注册的时候,传递了兴趣事件的,为什么我们没有看到这里有兴趣事件的注册呢。我们继续回到AbstractUnsafe
的bind
方法中,最后调用了pipeline.fireChannelActive()
,下面是PipeLine
的fireChannleActive
方法,调用了AbstractChannelHandlerContext.invokeChannelActive(head)
,而这个head
就是我们的“netty整体架构图”中的HeadContext
。
@Override
public final ChannelPipeline fireChannelActive() {
AbstractChannelHandlerContext.invokeChannelActive(head);
return this;
}
static void invokeChannelActive(final AbstractChannelHandlerContext next) {
EventExecutor executor = next.executor();
if (executor.inEventLoop()) {
next.invokeChannelActive();
} else {
executor.execute(new Runnable() {
@Override
public void run() {
next.invokeChannelActive();
}
});
}
}
复制代码
HeadContext
中的channelActive
方法如下,奥秘在readIfIsAutoRead
里,readIfIsAutoRead
,最终调用了channel.read
。
@Override
public void channelActive(ChannelHandlerContext ctx) throws Exception {
ctx.fireChannelActive();
readIfIsAutoRead();
}
private void readIfIsAutoRead() {
if (channel.config().isAutoRead()) {
channel.read();
}
}
复制代码
channel.read
方法的实现在AbstractChannel
中,调用到了pipeline.read
。
@Override
public Channel read() {
pipeline.read();
return this;
}
复制代码
PipeLine
中的read
方法如下,调用了tail
的read
方法,最终这个调用会传播到head
的read
方法,具体的传播过程,等咱们讲PipeLine
的时候再说。咱们直接去看HeadContext
的read
方法。
@Override
public final ChannelPipeline read() {
tail.read();
return this;
}
复制代码
HeadContext
的read
方法又调用到unsafe.beginRead()
。
@Override
public void read(ChannelHandlerContext ctx) {
unsafe.beginRead();
}
复制代码
beginRead
方法的实现在AbstractUnsafe
中,这里调用了doBeginRead
。doBeginRead
方法的实现在AbstractNioChannel
中。
@Override
public final void beginRead() {
try {
doBeginRead();
} catch (final Exception e) {
}
}
复制代码
doBeginRead
方法的实现在AbstractNioChannel
中,这里修改了selectionKey
的兴趣事件,把已有的兴趣事件interestOps
和readInterestOp
合并在一起重新设置。
interestOps
是现有的兴趣事件,在上文中向Selector
注册时的代码里javaChannel().register(eventLoop().selector, 0, this)
,所以interestOps
就是0。
readInterestOp
在哪里设置的呢,还记得本篇文章中新创建一个Channel
那一小节中吗?
@Override
protected void doBeginRead() throws Exception {
// Channel.read() or ChannelHandlerContext.read() was called
final SelectionKey selectionKey = this.selectionKey;
if (!selectionKey.isValid()) {
return;
}
readPending = true;
final int interestOps = selectionKey.interestOps();
if ((interestOps & readInterestOp) == 0) {
selectionKey.interestOps(interestOps | readInterestOp);
}
}
复制代码
在NioServerSocketChannel
调用父类的构造方法时传递了一个兴趣事件参数,值为SelectionKey.OP_ACCEPT
,至此,真相大白。
public NioServerSocketChannel(ServerSocketChannel channel) {
super(null, channel, SelectionKey.OP_ACCEPT);
config = new NioServerSocketChannelConfig(this, javaChannel().socket());
}
复制代码
九曲连环,我们终于找到了这么小小的一个点,为什么流程这么长呢,似乎很难理解,不要紧,继续关注我的文章,咱们讲PipeLine
的时候会把这里讲明白。
3 总结
netty服务端启动流程:
-
创建一个
Channel
实例,这个过程中将Channel
设置为非阻塞的,为Channel
创建了PipeLine
和Unsafe
。 -
初始化
Channel
,为Channel
设置参数和属性,并添加ServerBootstrapAcceptor
这个特殊的Handler
。 -
注册
Channel
,为Channel
绑定一个EventLoop
并向Selector
注册Channel
。 -
绑定端口。
关于作者
王建新,转转架构部资深Java工程师,主要负责服务治理、RPC框架、分布式调用跟踪、监控系统等。爱技术、爱学习,欢迎联系交流。
原创文章,码字不易,别忘了点赞。