在分析netty 服務端啓動流程之前,我們先看下 java nio socket服務端的代碼如何編寫,因爲netty nio 框架底層最終還是要用java nio那一套方式啓動服務。
//創建channel
ServerSocketChannel channel = ServerSocketChannel.open();
//綁定端口
channel.socket().bind(new InetSocketAddress("localhost", 8080));
//設置成非阻塞
channel.configureBlocking(false);
Selector selector = Selector.open();
//註冊到selector 對accept connection 感興趣
channel.register(selector, SelectionKey.OP_ACCEPT);
//監聽selector事件,對不同類型的事件做相應的處理
.....
下面來看下netty 啓動socket 服務端的代碼(Netty 版本4.1.10-Final)
//接收連接請求的事件循環
EventLoopGroup bossGroup =new NioEventLoopGroup();
//接收IO讀寫的事件循環
EventLoopGroup workerGroup =new NioEventLoopGroup();
try {
ServerBootstrap serverBootstrap = new ServerBootstrap();
serverBootstrap.group(bossGroup,workerGroup).
//指定Channel(netty中的)
channel(NioServerSocketChannel.class)
//sockect channel的處理器鏈
.childHandler(new ServerInitializerXXX());
//綁定端口
ChannelFuture sync = serverBootstrap.bind(9000).
sync();
sync.channel().closeFuture().sync();
} finally {
bossGroup.shutdownGracefully();
workerGroup.shutdownGracefully();
}
代碼中bind方法要傳端口號,說明這是啓動服務端的入口,接下來分析一下bind方法。代碼一路跟蹤下去,走到doBind方法。(分析的代碼中砍掉了一些異常處理流程,和不重要的邏輯)
private ChannelFuture doBind(final SocketAddress localAddress) {
final ChannelFuture regFuture = initAndRegister();
final Channel channel = regFuture.channel();
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 裏面的代碼砍掉了
}
這裏我們可以猜到netty做了2件事情
1.initAndRegister 方法:創建了Channel,並且註冊到selector
2.doBind0 方法:傳入了SocketAddress,說明進行了端口綁定操作
接下來我們看下netty是如何創建channel,註冊到selector的。進入initAndRegister 方法
final ChannelFuture initAndRegister() {
Channel channel = null;
try {
//創建Channel對象
channel = channelFactory.newChannel();
//初始化channel
init(channel);
} catch (Throwable t) {
//相關代碼砍掉了
}
//將Channel對象註冊到selector
ChannelFuture regFuture = config().group().register(channel);
//判斷註冊到selector 是否有異常,如果有異常關閉channel
if (regFuture.cause() != null) {
if (channel.isRegistered()) {
channel.close();
} else {
channel.unsafe().closeForcibly();
}
}
return regFuture;
}
initAndRegister 做了三件事情
一.通過 channelFactory 創建了channel對象。此處的channelFactory是在創建了ServerBootstrap對象後調用channel方法時創建.
public B channel(Class<? extends C> channelClass) {
if (channelClass == null) {
throw new NullPointerException("channelClass");
}
//ServerBootstrap中的channelFactory引用ReflectiveChannelFactory對象。
//ReflectiveChannelFactory 對象中引用了channel方法中傳入的Channel.class,ReflectiveChannelFactory 通過反射的形式創建Channel對象
return channelFactory(new ReflectiveChannelFactory<C>(channelClass));
}
// 小插曲
//這裏創建channel對象使用SelectorProvider.openServerSocketChannel() 的方式創建,使用原生的ServerSocketChannel.open()創建channel,會加鎖有性能損失.(https://github.com/netty/netty/issues/2308)
二.通過init方法初始化channel
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(childOptions.size()));
}
synchronized (childAttrs) {
currentChildAttrs = childAttrs.entrySet().toArray(newAttrArray(childAttrs.size()));
}
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));
}
});
}
});
}
1.設置attribute
2.設置ChannelOption
3.註冊了一個ChannelHandler,在channel pipleline中出入了一個 ServerBootstrapAcceptor對象。ServerBootstrapAcceptor主要用來將新的請求轉發到child eventLoopGroup.
這塊邏輯不是很重要
三.channel 註冊
ChannelFuture regFuture = config().group().register(channel)
一路跟蹤到AbstractChannel 的register方法
//砍掉了細枝末節的邏輯
public final void register(EventLoop eventLoop, final ChannelPromise promise) {
AbstractChannel.this.eventLoop = eventLoop;
//判斷是不是當前線程在做register操作,保證一個線程負責一個channel的整個生命週期,避免線程安全問題
if (eventLoop.inEventLoop()) {
register0(promise);
} else {
try {
eventLoop.execute(new Runnable() {
@Override
public void run() {
register0(promise);
}
});
} catch (Throwable t) {
}
}
}
register0方法
private void register0(ChannelPromise promise) {
try {
boolean firstRegistration = neverRegistered;
doRegister();
neverRegistered = false;
registered = true;
pipeline.invokeHandlerAddedIfNeeded();
safeSetSuccess(promise);
pipeline.fireChannelRegistered();
if (isActive()) {
if (firstRegistration) {
pipeline.fireChannelActive();
} else if (config().isAutoRead()) {
beginRead();
}
}
} catch (Throwable t) {
}
}
1.doRegister :將channel 註冊到selector
2.invokeHandlerAddedIfNeeded :調用pipeline中channel的handlerAdded方法
3.fireChannelRegistered:調用pipeline中channelRegister方法
4.判斷channel是否是active狀態,如果channel 綁定了端口認爲是active。此時還沒有走到bind端口,不會觸發調用ChannelActive方法。
Channel創建完成,註冊到selector後開始綁定端口。我們回到doBind0方法,代碼跟蹤到AbstractChannelHandlerContext的bind 方法
public ChannelFuture bind(final SocketAddress localAddress, final ChannelPromise promise) {
final AbstractChannelHandlerContext next = findContextOutbound();
EventExecutor executor = next.executor();
//在一個線程中執行channel的端口綁定
if (executor.inEventLoop()) {
next.invokeBind(localAddress, promise);
} else {
safeExecute(executor, new Runnable() {
@Override
public void run() {
next.invokeBind(localAddress, promise);
}
}, promise, null);
}
return promise;
}
再一路跟蹤到AbstractChannel的bind 方法
//判斷是否綁定端口
boolean wasActive = isActive();
try {
// 執行ServerSocketChannel的bind方法,綁定端口
doBind(localAddress);
} catch (Throwable t) {
}
if (!wasActive && isActive()) {
//異步觸發調用pipeline中channelHandler的ChannelActive方法
invokeLater(new Runnable() {
@Override
public void run() {
pipeline.fireChannelActive();
}
});
}
總結
netty 服務端啓動流程
1.創建channel對象,設置option,attribute,添加ServerBootstrapAcceptor 接收器。
2.channel 註冊到selector
3.觸發channelAdd,channelRegistered
4.bind 端口
5.觸發ChannelActive