10 netty服务端启动流程
前置知识
1 服务端启动步骤概述
netty的启动关键逻辑可以用以下代码表示
package CodeAnalyze;
import io.netty.channel.socket.nio.NioServerSocketChannel;
import java.io.IOException;
import java.net.InetSocketAddress;
import java.nio.channels.SelectionKey;
import java.nio.channels.Selector;
import java.nio.channels.ServerSocketChannel;
public class Example {
public static void main(String[] args) {
try {
// 下面三行都是原生的NIO的API
ServerSocketChannel serverSocketChannel = ServerSocketChannel.open(); // 服务器监听socket
serverSocketChannel.configureBlocking(false);
Selector selector = Selector.open();
NioServerSocketChannel attachment = new NioServerSocketChannel(); // netty提供
SelectionKey sscKey = serverSocketChannel.register(selector,0,attachment);
// 绑定端口
serverSocketChannel.bind(new InetSocketAddress(8080));
// 关联事件
sscKey.interestOps(SelectionKey.OP_ACCEPT);
} catch (IOException e) {
e.printStackTrace();
}
}
}
上述代码中服务端启动可以划分为以下3个步骤:
- step1:创建ServerSocketChannel对象
ServerSocketChannel serverSocketChannel = ServerSocketChannel.open(); // 服务器监听socket
serverSocketChannel.configureBlocking(false);
- step2:将ServerSocketChannel注册到selector选择器,从而方便后续监控channel的事件
NioServerSocketChannel attachment = new NioServerSocketChannel(); // netty提供
Selector selector = Selector.open();
SelectionKey sscKey = serverSocketChannel.register(selector,0,attachment);
- step3:绑定端口并关联accept事件
// 绑定端口
serverSocketChannel.bind(new InetSocketAddress(8080));
// 关联事件
sscKey.interestOps(SelectionKey.OP_ACCEPT);
2 netty服务端启动代码
package CodeAnalyze;
import io.netty.bootstrap.ServerBootstrap;
import io.netty.channel.ChannelInitializer;
import io.netty.channel.nio.NioEventLoopGroup;
import io.netty.channel.socket.nio.NioServerSocketChannel;
import io.netty.channel.socket.nio.NioSocketChannel;
import io.netty.handler.logging.LoggingHandler;
public class TestSourceServer {
public static void main(String[] args) {
new ServerBootstrap()
.group(new NioEventLoopGroup())
.channel(NioServerSocketChannel.class)
.childHandler(new ChannelInitializer<NioSocketChannel>() {
@Override
protected void initChannel(NioSocketChannel ch) throws Exception {
ch.pipeline().addLast(new LoggingHandler());
}
}).bind(8080); // 入口1
}
}
bind源码追踪
io.netty.bootstrap.AbstractBootstrap.bind(int inetPort) =>
io.netty.bootstrap.AbstractBootstrap.doBind(final SocketAddress localAddress
public ChannelFuture bind(int inetPort) { // 入口1源码
return bind(new InetSocketAddress(inetPort)); // 入口2
}
---------------------------------------------------------------------------------------
public ChannelFuture bind(SocketAddress localAddress) { //入口2源码
validate(); // 检测group和channel的工厂类是否为空,为空则抛出异常
return doBind(ObjectUtil.checkNotNull(localAddress, "localAddress")); // 入口3
}
---------------------------------------------------------------------------------------
private ChannelFuture doBind(final SocketAddress localAddress) { // 入口3源码
final ChannelFuture regFuture = initAndRegister();
final Channel channel = regFuture.channel();
if (regFuture.cause() != null) {
return regFuture;
}
if (regFuture.isDone()) {
// At this point we know that the registration was complete and successful.
ChannelPromise promise = channel.newPromise();
doBind0(regFuture, channel, localAddress, promise);
return promise;
} else {
// Registration future is almost always fulfilled already, but just in case it's not.
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) {
// Registration on the EventLoop failed so fail the ChannelPromise directly to not cause an
// IllegalStateException once we try to access the EventLoop of the Channel.
promise.setFailure(cause);
} else {
// Registration was successful, so set the correct executor to use.
// See https://github.com/netty/netty/issues/2586
promise.registered();
doBind0(regFuture, channel, localAddress, promise);
}
}
});
return promise;
}
}
总结:基于bind的源码追踪可以定位到doBind方法,该方法实现了服务端的3个启动步骤
2-1 dobind()源码分析
2-1-1 概述
private ChannelFuture doBind(final SocketAddress localAddress) {
/*
创建并初始化NioServerSocketChannel,并且将该channel注册到一个EventLoop中,需要注意的是
这里注册即绑定selector的操作是通过EventLoop提交线程任务完成,对于这种类型的异步操作,这里
initAndRegister()会返回一个Future对象用于感知注册的结果
*/
final ChannelFuture regFuture = initAndRegister();
final Channel channel = regFuture.channel();
if (regFuture.cause() != null) {
return regFuture;
}
if (regFuture.isDone()) { // channel注册已经完成(绝大部分情况),直接进行绑定
ChannelPromise promise = channel.newPromise();
doBind0(regFuture, channel, localAddress, promise);
return promise;
} else { // channel注册还未完成
final PendingRegistrationPromise promise = new PendingRegistrationPromise(channel);
// 当前注册还么完成通过在future中添加回调函数确保
// EventLoop中线程执行完任务后调用该回调函数完成绑定操作
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;
}
}
2-1-2 initAndRegister()
initAndRegister()源码
final ChannelFuture initAndRegister() {
Channel channel = null;
try {
channel = channelFactory.newChannel(); //实例化NioServerSocketChannel
init(channel); // 初始化channel(在pipeline中添加handler)
} catch (Throwable t) {
if (channel != null) {
channel.unsafe().closeForcibly();
return new DefaultChannelPromise(channel, GlobalEventExecutor.INSTANCE).setFailure(t);
}
return new DefaultChannelPromise(new FailedChannel(), GlobalEventExecutor.INSTANCE).setFailure(t);
}
// 将创建的channel实例对象与EventLoopGroup中的一个EventLoop进行绑定
ChannelFuture regFuture = config().group().register(channel);
if (regFuture.cause() != null) {
if (channel.isRegistered()) {
channel.close();
} else {
channel.unsafe().closeForcibly();
}
}
return regFuture;
}
initAndRegister方法包含以下三个核心功能:
// a)创建NioServerSocketChannel实例
channel = channelFactory.newChannel();
// b) 为channel的pipeline添加初始化handler,注意该handler只被调用一次,并且当前为调用
init(channel);
// c) 将NioServerSocketChanenl实例注册NioEventLoopGroup的selector中
ChannelFuture regFuture = config().group().register(channel);
a) 创建channel对象
通过channelFactory创建NioServerSocketChannel的实例
上面截图表明:方法initAndRegister()在执行过程中通过反射的方式利用NioServerSocketChannel的构造器实例化NioServerSocketChannel对象
进一步分析netty的NioServerSocketChannel的构造源码:
public NioServerSocketChannel() {
this(newSocket(DEFAULT_SELECTOR_PROVIDER));
}
---------------------------------------------------------------------------
private static ServerSocketChannel newSocket(SelectorProvider provider) {
try {
return provider.openServerSocketChannel(); //
} catch (IOException e) {
throw new ChannelException(
"Failed to open a server socket.", e);
}
}
对比JDK中ServerSocketChannel的open方法源码
public static ServerSocketChannel open() throws IOException {
return SelectorProvider.provider().openServerSocketChannel();
}
总结:对比上述两个代码段可以判定在实例化netty的NioServerSocketChannel的过程中也实例化了JDK自带的ServerSocketChannel
b) 初始化channel对象
在NioServerSocketChannel的pipeline添加初始化handler
init()方法源码
@Override
void init(Channel channel) throws Exception {
final Map<ChannelOption<?>, Object> options = options0();
synchronized (options) {
setChannelOptions(channel, options, logger);
}
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();
final EventLoopGroup currentChildGroup = childGroup;
final ChannelHandler currentChildHandler = childHandler;
final Entry<ChannelOption<?>, Object>[] currentChildOptions;
final Entry<AttributeKey<?>, Object>[] currentChildAttrs;
synchronized (childOptions) {
currentChildOptions = childOptions.entrySet().toArray(newOptionArray(0));
}
synchronized (childAttrs) {
currentChildAttrs = childAttrs.entrySet().toArray(newAttrArray(0));
}
// 为NioServerSocketChannel添加handler,该handler后续只会被调用一次
// 用于初始化NioServerSocketChannel!!!!!!
p.addLast(new ChannelInitializer<Channel>() {
@Override
public void initChannel(final Channel ch) throws Exception {
final ChannelPipeline pipeline = ch.pipeline();
ChannelHandler handler = config.handler();
if (handler != null) {
pipeline.addLast(handler);
}
ch.eventLoop().execute(new Runnable() {
@Override
public void run() {
pipeline.addLast(new ServerBootstrapAcceptor(
ch, currentChildGroup, currentChildHandler, currentChildOptions, currentChildAttrs));
}
});
}
});
}
c) 完成channel对象的注册
ChannelFuture regFuture = config().group().register(channel)的调用链如下所示:
io.netty.channel.MultithreadEventLoopGroup.register =>
io.netty.channel.SingleThreadEventLoop.register(Channel channel) =>
io.netty.channel.SingleThreadEventLoop.register(final ChannelPromise promise)=>
io.netty.channel.AbstractChannel.register(EventLoop eventLoop, final ChannelPromise promise)=>
io.netty.channel.AbstractChannel.register0=>
io.netty.channel.nio.doRegister()
==============================================================================
分析调用链可发现:注册的基本逻辑是先从NioEventLoopGroup中选择一个NioEventLoop,然后通过选择的NioEventLoop提交任务完成channel的注册(绑定当前eventLoop的selector)
==============================================================================
@Override
public ChannelFuture register(Channel channel) {
return next().register(channel);
}
------------------------------------------------------------------------
@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;
}
-------------------------------------------------------------------------------
@Override
public final void register(EventLoop eventLoop, final ChannelPromise promise) {
if (eventLoop == null) {
throw new NullPointerException("eventLoop");
}
if (isRegistered()) {
promise.setFailure(new IllegalStateException("registered to an event loop already"));
return;
}
if (!isCompatible(eventLoop)) {
promise.setFailure(
new IllegalStateException("incompatible event loop type: " + eventLoop.getClass().getName()));
return;
}
AbstractChannel.this.eventLoop = eventLoop;
// 下面的if-else通过检查当前线程是否时NIO线程,确保register0方法在NIO线程中执行!!
if (eventLoop.inEventLoop()) {
register0(promise);
} else {
try {
eventLoop.execute(new Runnable() {
@Override
public void run() {
register0(promise);
}
});
} catch (Throwable t) {
logger.warn(
"Force-closing a channel whose registration task was not accepted by an event loop: {}",
AbstractChannel.this, t);
closeForcibly();
closeFuture.setClosed();
safeSetFailure(promise, t);
}
}
}
=====================================register0=================================================
private void register0(ChannelPromise promise) {
try {
// check if the channel is still open as it could be closed in the mean time when the register
// call was outside of the eventLoop
if (!promise.setUncancellable() || !ensureOpen(promise)) {
return;
}
boolean firstRegistration = neverRegistered;
doRegister();
neverRegistered = false;
registered = true;
// 调用之前添加的初始化handler
pipeline.invokeHandlerAddedIfNeeded();
// 设置promise容器中为success,之后会调用promise容器关联的回调函数进行bind
safeSetSuccess(promise);
// 触发channelRegister方法调用
pipeline.fireChannelRegistered();
if (isActive()) {
if (firstRegistration) {
pipeline.fireChannelActive();
} else if (config().isAutoRead()) {
beginRead();
}
}
} catch (Throwable t) {
closeForcibly();
closeFuture.setClosed();
safeSetFailure(promise, t);
}
}
----------------------------doregister----------------------------------------
@Override
protected void doRegister() throws Exception {
boolean selected = false;
for (;;) {
try {
selectionKey = javaChannel().register(eventLoop().unwrappedSelector(), 0, this);
return;
} catch (CancelledKeyException e) {
if (!selected) {
eventLoop().selectNow();
selected = true;
} else {
throw e;
}
}
}
}
总结:register过程中首先从EventLoopGroup中选择1个EventLoop,由EventLoop中的线程池提交任务去执行doRegister()方法完成注册,所谓注册就是让EventLoop中的selector关注ServerSocketChannel的事件。
// 主线程和NIO线程配合的关键代码
if (eventLoop.inEventLoop())
// 源码中eventLoop通过判断当前线程是否是evenLoop中的线程对象来确定是否需要提交任务。
// 不是,则执行eventLoop.execute(()->{ register0(promise);}),该代码让选择eventLoop提交任务。
register0的工作:
- doRegister()方法将ServerSocketChannel注册到当前eventLoop中
selectionKey = javaChannel().register(eventLoop().unwrappedSelector(), 0, this);
// javaChannel()就是JDK原生的ServerSocketChannel,eventLoop()
// unwrappedSelector()是当前EventLoop的selector
// this指的就是已经创建好的NioServerSocketChannel(Netty提供)
// doRegister()方法中的这行代码与(1 服务端启动步骤概述 step2)本质是相同的。
- 执行handler初始化Channel
pipeline.invokeHandlerAddedIfNeeded(); // 此处代码执行之前添加在pipeline中的初始化handler
问题:为什么在“初始化channel对象阶段b”添加的handler到“channel对象注册阶段c“才进行执行?
原因:在阶段b,channel对象还没有绑定eventLoop,但是所添加的handler中需要EventLoop提交任务(见下面代码片段),因此必须等到阶段c中完成EventLoop的绑定之后再调用handler中的代码。
ch.eventLoop().execute(new Runnable() {
@Override
public void run() {
pipeline.addLast(new ServerBootstrapAcceptor(
ch, currentChildGroup, currentChildHandler, currentChildOptions, currentChildAttrs));
}
/*
ServerBootstrapAcceptor是1个handler用于ServerSocketChannel中accept事件
发生时进行连接建立的处理。
*/
initAndRegister方法总结
该方法总体的执行需要主线程和EventLoop中的线程配合完成:
* 主线程创建并初始化channel对象并从EventLoop中选出channel绑定的EventLoop
* 实际注册工作是EventLoop中线程池提交任务完成的
注册的结果通过future容器(regFuture)作为该方法的返回值返回
注册成功设置位置如下:Nio线程执行register0()时设置promise容器中为success
safeSetSuccess(promise);
在dobind()源码中:regFuture就是上述代码的promise
/*
通过regFuture(promise容器)感知NIO线程中register是否完成,dobind方法中如果发现注册还没有完成,此 时会为future添加回调函数ChannelFutureListener()来确保执行doBind0()中完成端口绑定。
*/
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);
}
}
});
2-1-3 doBind0()
调用链
io.netty.bootstrapAbstractBootstrap.doBind0()
=>
io.netty.channel.AbstractChannel.bind(SocketAddress localAddress, ChannelPromise promise) =>
io.netty.channel.DefaultChannelPipeline.bind(SocketAddress localAddress, ChannelPromise promise)
=>
io.netty.channel.AbstractChannelHandlerContext.bind(SocketAddress localAddress)
=>
io.netty.channel.AbstractChannelHandlerContext.invokeBind(SocketAddress localAddress, ChannelPromise promise)
=>
io.netty.channel.DefaultChannelPipeline.bind(localAddress, promise);
=>
io.netty.channel.AbstractChannel.bind(final SocketAddress localAddress, final ChannelPromise promise)
=>
AbstractChannelHandlerContext中bind相关源码:
@Override
public ChannelFuture bind(final SocketAddress localAddress, final ChannelPromise promise) {
if (localAddress == null) {
throw new NullPointerException("localAddress");
}
if (isNotValidPromise(promise, false)) {
// cancelled
return promise;
}
final AbstractChannelHandlerContext next = findContextOutbound(MASK_BIND);
EventExecutor executor = next.executor();
/*
下面的if-else确保后续的invokeBind()操作是由nio线程完成
if 检测当前线程是否是EventLoop的线程
invokeBind
else
线程池提交任务及逆行invokeBind
返回promise
*/
if (executor.inEventLoop()) {
next.invokeBind(localAddress, promise);
} else {
safeExecute(executor, new Runnable() {
@Override
public void run() {
next.invokeBind(localAddress, promise);
}
}, promise, null);
}
return promise;
}
private void invokeBind(SocketAddress localAddress, ChannelPromise promise) {
if (invokeHandler()) {
try {
((ChannelOutboundHandler) handler()).bind(this, localAddress, promise);
} catch (Throwable t) {
notifyOutboundHandlerException(t, promise);
}
} else {
bind(localAddress, promise);
}
}
}
注意:上述源码中涉及线程的切换,代码中保证invokeBind()方法调用是由EventLoop的线程执行的。
AbstractChannel.bind源码如下:
@Override
public final void bind(final SocketAddress localAddress, final ChannelPromise promise) {
assertEventLoop();
if (!promise.setUncancellable() || !ensureOpen(promise)) {
return;
}
// See: https://github.com/netty/netty/issues/576
if (Boolean.TRUE.equals(config().getOption(ChannelOption.SO_BROADCAST)) &&
localAddress instanceof InetSocketAddress &&
!((InetSocketAddress) localAddress).getAddress().isAnyLocalAddress() &&
!PlatformDependent.isWindows() && !PlatformDependent.maybeSuperUser()) {
// Warn a user about the fact that a non-root user can't receive a
// broadcast packet on *nix if the socket is bound on non-wildcard address.
logger.warn(
"A non-root user can't receive a broadcast packet if the socket " +
"is not bound to a wildcard address; binding to a non-wildcard " +
"address (" + localAddress + ") anyway as requested.");
}
boolean wasActive = isActive();
try {
doBind(localAddress); // 1)完成底层ServerSocketChannel和端口的绑定
} catch (Throwable t) {
safeSetFailure(promise, t);
closeIfClosed();
return;
}
// 2)完成端口绑定后触发pipeline中handler的active事件
if (!wasActive && isActive()) {
invokeLater(new Runnable() {
@Override
public void run() {
pipeline.fireChannelActive();
// fire channel active(调用所有handler的channelActive方法
}
});
}
safeSetSuccess(promise);
}
上述源码中的注意点:
1) doBind方法完成底层ServerSocketChannel和端口的绑定
protected void doBind(SocketAddress localAddress) throws Exception {
if (PlatformDependent.javaVersion() >= 7) {
javaChannel().bind(localAddress, config.getBacklog());
} else {
javaChannel().socket().bind(localAddress, config.getBacklog());
}
}
2) 完成所有工作后,会触发当前NioServerSocketChannel的pipeline中所有handler的active事件
动机:当前的NioServerSocketChannel的pipeline结构是 head <= accept handler <= tail,
其中head和tail是pipeline默认的handler。三个handler中,head handler的channelActive让key关注accept事件
sscKey.interestOps(SelectionKey.OP_ACCEPT); // 默认的head handler的channelActive会完成该工作
3 总结
3-1 设计思想
ServerSocketChannel serverSocketChannel = ServerSocketChannel.open();
serverSocketChannel.configureBlocking(false);
Selector selector = Selector.open();
SelectionKey sscKey = serverSocketChannel.register(selector,0,null);
serverSocketChannel.bind(new InetSocketAddress(8080));
sscKey.interestOps(SelectionKey.OP_ACCEPT);
上述原始的服务端启动流程在netty中显得异常复杂度,主要原因在于:
代码结构:
- netty采用EventLoop对原始的JDK的selector进行封装
- 动机:充分利用多线程优势
- 采用NioServerSocketChannel封装了ServerSocketChannel
- 动机:netty基于责任链模式设计的channel让数据的处理更加方便直观
执行逻辑:执行过程中,无论是注册还是绑定端口都是通过channel绑定的EventLoop取提交任务实现,线程间的
协同使得代码不是特别直观。但框架的复杂性保证了使用者的简便性。
3-2 线程间的同步
Netty中线程间的写法:
promise = func1(参数,...,promise)
// 情况1:通过promise判断func1的逻辑是否执行完整,如果已经执行完整,直接执行后续逻辑
if(promise.isSuccess){
func2()
}else{
// 情况2:通过promise判断func1的逻辑是否执行完整,如果未执行完整,添加回调函数,当执行完成后通过回调函数调用确保后续逻辑执行
promise.addListener(()->{func1})
}
说明:上述伪代码逻辑在netty中经常出现,首先是异步方法func1执行(涉及其他线程),我们可以通过方法参数中是否有promise类型对象来判断当前方法是否涉及异步调用。然后通过返回的promise内容的判断其他线程是否执行完成(其他线程会将结果放到promise中),确保func2的逻辑一定实在func1后面执行。
-
上述逻辑在Netty启动源码中体现:端口的绑定必须在channel注册后才能执行,这种严格顺序关系就是通过上述伪代码的思路实现。本质上还是多线程思想中的保护性暂停模式。这里的promise是充当了线程间同步的协调者。
-
上述逻辑func2()由于线程执行的不确定性,可能是线程1执行,也可能是线程2执行,如果我们希望确保某个目标线程执行某些操作,那么可以判断当前线程是否是目标线程。netty源码中多次出现inEventLoop()方法调用就是确保某些操作必须由EventLoop中的线程完成。
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· DeepSeek 开源周回顾「GitHub 热点速览」
· 物流快递公司核心技术能力-地址解析分单基础技术分享
· .NET 10首个预览版发布:重大改进与新特性概览!
· AI与.NET技术实操系列(二):开始使用ML.NET
· 单线程的Redis速度为什么快?