通过前面两篇文章的铺垫,终于到了Netty服务端启动的核心流程,但涉及的方法十分多,希望咱们看源码之前,一定要有一个关注点,看源码的过程中就重点留意所关注的东西,其他与核心流程的逻辑关系不大,甚至有很多看不懂的方法,可以先跳过,只关注核心的东西就行。如果对于每一行代码都要执着的理解,这将会是一场灾难!很有可能会因为源码的苦涩而半途而废!所以,看源码的时候,一定要有关注点,看源码的过程中不能迷失...
好了,我们看一下Netty服务端启动的代码
// 绑定端口并启动
ChannelFuture f = b.bind(PORT).sync();
通过这么简单的一行,就能够将Netty服务端运行起来,但其背后却做了很多东西!(再次佩服Netty框架的设计者,使开发者可以优雅简单的运行一个网络应用程序)
好了,废话不多说,看源码!
public ChannelFuture bind(int inetPort) {
// InetSocketAddress类:socket编程基础,封装ip和端口为套接字
return bind(new InetSocketAddress(inetPort));
}
public ChannelFuture bind(SocketAddress localAddress) {
validate();
return doBind(ObjectUtil.checkNotNull(localAddress, "localAddress"));
}
private ChannelFuture doBind(final SocketAddress localAddress) {
// 创建Channel,注册到对应的eventLoop。异步返回Future对象
final ChannelFuture regFuture = initAndRegister();
final Channel channel = regFuture.channel();
if (regFuture.cause() != null) {
return regFuture;
}
// 因为regFuture是异步返回的,如果成功注册到eventLoop,则直接调用doBind0方法绑定端口
if (regFuture.isDone()) {
ChannelPromise promise = channel.newPromise();
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;
}
}
其中,doBind方法是启动ServerBootstrap的核心,咱们先来看一下initAndRegister
方法
// 创建Channel,注册到对应的eventLoop
final ChannelFuture initAndRegister() {
Channel channel = null;
try {
// 创建channel对象
channel = channelFactory.newChannel();
// 初始化channel
init(channel);
} catch (Throwable t) {
// 省略异常处理代码
}
// 将channel注册到eventLoop
ChannelFuture regFuture = config().group().register(channel);
// 省略异常处理代码
return regFuture;
}
去掉异常处理的代码,可以看到主要调用的方法是newChannel()
、init(channel)
、register(channel)
方法,简单来说就是创建Channel-》初始化Channel-》注册Channel
接下来继续重点关注上面三个步骤的源码
创建Channel
【Netty源码系列】服务端启动流程(二)创建并初始化ServerBootstrap对象文章中提到,Channel的创建是被封装到 ReflectiveChannelFactory 类中,所以创建Channel是调用ReflectiveChannelFactory类的newChannel方法
@Override
public T newChannel() {
try {
return constructor.newInstance();
} catch (Throwable t) {
throw new ChannelException("Unable to create Channel from class " + constructor.getDeclaringClass(), t);
}
}
constructor对象是在定义Channel类型的时候定义的,比如下面例子中的channel(NioServerSocketChannel.class),所以constructor.newInstance()实际上就是调用NioServerSocketChannel的无参构造器
ServerBootstrap b = new ServerBootstrap();
b.group(bossGroup, workerGroup) // 绑定线程池组
.channel(NioServerSocketChannel.class) // 服务端channel类型
.option(ChannelOption.SO_BACKLOG, 100) // TCP配置
.handler(new LoggingHandler(LogLevel.INFO)) // 服务端Handler
.childHandler(new ChannelInitializer() { // 客户端Handler
@Override
public void initChannel(SocketChannel ch) throws Exception {
ChannelPipeline p = ch.pipeline();
//p.addLast(new LoggingHandler(LogLevel.INFO));
p.addLast(serverHandler);
}
});
public NioServerSocketChannel() {
this(newSocket(DEFAULT_SELECTOR_PROVIDER));
}
private static ServerSocketChannel newSocket(SelectorProvider provider) {
try {
// 调用JDK原生的方法,创建ServerSocketChannel对象
return provider.openServerSocketChannel();
} catch (IOException e) {
throw new ChannelException("Failed to open a server socket.", e);
}
}
public NioServerSocketChannel(ServerSocketChannel channel) {
// 调用父类的构造器,封装ServerSocketChannel对象为NioServerSocketChannel,并初始化相关的属性,如:unSafe、pipeline等
super(null, channel, SelectionKey.OP_ACCEPT);
// NioServerSocketChannel相关配置
config = new NioServerSocketChannelConfig(this, javaChannel().socket());
}
上面就是创建NioServerSocketChannel的流程,实际上底层调用的是JDK原生代码,通过原生代码创建出ServerSocketChannel对象,然后在Netty内部封装为NioServerSocketChannel,并初始化相关的属性。
ps.请各位注意,NioServerSocketChannel构造器中,调用父级的流程请各位自行动手debug看下,也有不少重点需要注意,比如设置channel为非阻塞,channel的id,unsafe、pipeline是如何初始化等等
初始化channel
/**
* 1. 设置channel option
* 2. 设置channel attribute
* 3. 设置channel pipeline
* 3.1 pipeline 添加 handler
* @param channel
*/
@Override
void init(Channel channel) {
// 1. 设置channel option
setChannelOptions(channel, newOptionsArray(), logger);
// 2. 设置channel attribute
setAttributes(channel, attrs0().entrySet().toArray(EMPTY_ATTRIBUTE_ARRAY));
// 获取channel的pipeline对象
ChannelPipeline p = channel.pipeline();
final EventLoopGroup currentChildGroup = childGroup;
final ChannelHandler currentChildHandler = childHandler;
final Entry, Object>[] currentChildOptions;
// 设置socket channel的option
synchronized (childOptions) {
currentChildOptions = childOptions.entrySet().toArray(EMPTY_OPTION_ARRAY);
}
// 设置socket channel的attribute
final Entry, Object>[] currentChildAttrs = childAttrs.entrySet().toArray(EMPTY_ATTRIBUTE_ARRAY);
// pipeline 添加 handler
p.addLast(new ChannelInitializer() {
@Override
public void initChannel(final Channel ch) {
final ChannelPipeline pipeline = ch.pipeline();
ChannelHandler handler = config.handler();
if (handler != null) {
pipeline.addLast(handler);
}
// 为每一个ServerSocketChannel的pipeline添加ServerBootstrapAcceptor处理器,该处理器专门用于接收客户端请求的连接
ch.eventLoop().execute(new Runnable() {
@Override
public void run() {
pipeline.addLast(new ServerBootstrapAcceptor(
ch, currentChildGroup, currentChildHandler, currentChildOptions, currentChildAttrs));
}
});
}
});
}
初始化channel相关代码我都加上注释,其实都比较简单明了,依次设置了ServerSocketChannel的option、attribute相关属性,除此之外,还设置了pipeline内的处理器。值得注意的是,每个ServerSocketChannel都会添加ServerBootstrapAcceptor
处理器,这个处理器在客户端请求的时候将会发挥很大的作用,简单来说, 该处理器会将SocketChannel注册到WorkerGroup的某一个EventLoop,然后交给WorkerGroup的线程处理客户端的请求。
让我们回过头,当ServerSocketChannel初始化完成后,就会将当前的ServerSocketChannel注册到BossGroup的EventLoop,在 initAndRegister 方法中对应的代码是ChannelFuture regFuture = config().group().register(channel)
。通过debug分析,实际上是调用SingleThreadEventLoop的register方法
注册Channel
/**
* 将channel封装成channelPromise对象
* @param channel
* @return
*/
@Override
public ChannelFuture register(Channel channel) {
return register(new DefaultChannelPromise(channel, this));
}
@Override
public ChannelFuture register(final ChannelPromise promise) {
ObjectUtil.checkNotNull(promise, "promise");
// 将当前channel注册到eventLoop
promise.channel().unsafe().register(this, promise);
return promise;
}
通过上面源码可以看到,promise.channel().unsafe().register(this, promise)
会将channel注册到EventLoop上,实际上是调用AbstractChannel&AbstractUnsafe的register方法,那继续往下看...
@Override
public final void register(EventLoop eventLoop, final ChannelPromise promise) {
// 省略一些判断逻辑
AbstractChannel.this.eventLoop = eventLoop;
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) {
try {
// 省略一些判断逻辑
boolean firstRegistration = neverRegistered;
// channel注册到eventLoop的核心方法!!!
doRegister();
neverRegistered = false;
registered = true;
pipeline.invokeHandlerAddedIfNeeded();
safeSetSuccess(promise);
// 调用下一个channelInboundHandler的channelRegistered()方法
pipeline.fireChannelRegistered();
// 如果Channel处于活动状态
if (isActive()) {
if (firstRegistration) {
// 调用下一个channelInbooundHandler的channelActive()方法
pipeline.fireChannelActive();
} else if (config().isAutoRead()) {
beginRead();
}
}
} catch (Throwable t) {
// 省略异常逻辑
}
}
通过一层层的debug,终于接近ServerSocketChannel注册到eventLoop的底层实现啦!继续往下看doRegister()
方法。由于当前channel是NioServerSocketChannel,所以doRegister方法是在AbstractNioChannel类中实现的
@Override
protected void doRegister() throws Exception {
boolean selected = false;
for (;;) {
try {
selectionKey = javaChannel().register(eventLoop().unwrappedSelector(), 0, this);
return;
} catch (CancelledKeyException e) {
// 省略异常逻辑
}
}
}
我们终于知道在Netty中是如何将服务端ServerSocketChannel注册到BossGroup的eventLoop上。实际上是使用JDK原生的register方法注册ServerSocketChannel到BossGroup的eventLoop的Selector上
目前为止,服务端启动流程就可以启动了吗?不不不,还差最后一步,绑定端口!
绑定端口————doBind0方法
绑定端口涉及到的类较多,我这里就不一一展示出来,只展示最后一步绑定端口的相关方法,其完整的调用逻辑可以参考下面的时序图
// NioServerSocketChannel类
@SuppressJava6Requirement(reason = "Usage guarded by java version check")
@Override
protected void doBind(SocketAddress localAddress) throws Exception {
if (PlatformDependent.javaVersion() >= 7) {
javaChannel().bind(localAddress, config.getBacklog());
} else {
javaChannel().socket().bind(localAddress, config.getBacklog());
}
}
通过调用NioServerSocketChannel类doBind的方法就可以绑定端口,源码中可以发现实际上是调用JDK原生绑定端口方法。以上就是服务端启动的大致流程,希望可以通过上面的流程图和源码剖析过程中的注释和解析可以帮助各位更加了解Netty服务端的启动流程,这对使我们更加熟悉Netty框架,并学习其中一些编程思想,帮助我们日常编码过程中,可以模仿这些优秀框架的设计模式和思想写出更加优质的代码!
如果觉得文章不错的话,麻烦点个赞哈,你的鼓励就是我的动力!对于文章有哪里不清楚或者有误的地方,欢迎在评论区留言~