Netty源码学习(四)Netty服务器是如何启动的?
本文会分析Netty服务器的启动过程,采用的范例代码是Netty编写的Echo Server。
0. 声明acceptor与worker
由于Netty采用的reactor模型,所以需要声明两组线程,一组作为boss/acceptor,另一组作为worker
boss/acceptor负责监听绑定的端口,accept新接入的连接,然后将这些连接转交给worker,worker会处理这些连接上的读写事件。
也就是下面的代码:
EventLoopGroup bossGroup = new NioEventLoopGroup(1);//boss/acceptor线程组
EventLoopGroup workerGroup = new NioEventLoopGroup();//worker线程组
1. 声明ServerBootstrap,并调用bind方法开始监听端口
ServerBootstrap.bind()方法最终会调用到AbstractBootstrap.doBind()方法,其源码如下:
private ChannelFuture doBind(final SocketAddress localAddress) {
final ChannelFuture regFuture = initAndRegister();
final Channel channel = regFuture.channel();
if (regFuture.cause() != null) {
return regFuture;
}
if (regFuture.isDone()) {//如果Channel已经register成功,则直接调用doBind0方法
// At this point we know that the registration was complete and successful.
ChannelPromise promise = channel.newPromise();
doBind0(regFuture, channel, localAddress, promise);//doBind0方法后文再做分析
return promise;
} else {//否则添加一个回调函数,在Channel register成功后再调用doBind0方法
// 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;
}
}
final ChannelFuture initAndRegister() {
Channel channel = null;
try {
channel = channelFactory.newChannel();//创建一个新的Channel,这里的channelFactory对象是在初始化ServerBootstrap时调用channel()方法的时候被设置的,在Echo Server范例中,此处会构造一个NioServerSocketChannel
init(channel);//见下文分析,初始化Channel,设置属性与选项,以及为这个NioServerSocketChannel添加一个ChannelInitializer,其目的是在这个Channel被register到boss EventLoopGroup的时候,自动添加一个SerrverBootstrapAcceptor
} catch (Throwable t) {
if (channel != null) {
// channel can be null if newChannel crashed (eg SocketException("too many open files"))
channel.unsafe().closeForcibly();
}
// as the Channel is not registered yet we need to force the usage of the GlobalEventExecutor
return new DefaultChannelPromise(channel, GlobalEventExecutor.INSTANCE).setFailure(t);//如果初始化Channel的时候发生异常,则将其包装一下然后返回
}
ChannelFuture regFuture = config().group().register(channel);//group()方法得到的是boss EventLoopGroup,register方法很重要,后文再做分析
if (regFuture.cause() != null) {
if (channel.isRegistered()) {
channel.close();
} else {
channel.unsafe().closeForcibly();
}
}
// If we are here and the promise is not failed, it's one of the following cases:
// 1) If we attempted registration from the event loop, the registration has been completed at this point.
// i.e. It's safe to attempt bind() or connect() now because the channel has been registered.
// 2) If we attempted registration from the other thread, the registration request has been successfully
// added to the event loop's task queue for later execution.
// i.e. It's safe to attempt bind() or connect() now:
// because bind() or connect() will be executed *after* the scheduled registration task is executed
// because register(), bind(), and connect() are all bound to the same thread.
return regFuture;
}
@Override
void init(Channel channel) throws Exception {
final Map<ChannelOption<?>, Object> options = options0();
synchronized (options) {
setChannelOptions(channel, options, logger);//设置的channel的options
}
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());//设置Channel的attribute
}
}
ChannelPipeline p = channel.pipeline();//获取Channel的pipeline,此时为DefaultChannelPipeline,且只有head与tail两个节点
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>() {//向pipeline中添加一个ChannelInitializer对象
@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(//为当前Channel添加一个ServerBootstrapAcceptor,其目的后文会做分析
ch, currentChildGroup, currentChildHandler, currentChildOptions, currentChildAttrs));
}
});
}
});
}
主要干的事情:
a. 声明了一个Channel对象(实际上是NioServerSocketChannel对象),这个Channel会被register到之前声明的boss NioEventLoopGroup里,然后再调用doBind0方法绑定端口,后文会分析这两个方法的调用链
b. 向这个Channel的pipeline里添加一个ChannelInitializer对象,这个对象继承于ChannelInboundHandler接口,后续初始化的时候会向pipeline里再加一个ServerBootstrapAcceptor对象,它也继承于ChannelInboundHandler接口,这个对象的作用我们会在文末介绍。
2. register方法
register方法的实际调用者为前文声明的boss NioEventLoopGroup,其register实现位于MultithreadEventLoopGroup中,调用链如下:
MultithreadEventLoopGroup.register()
@Override
public ChannelFuture register(Channel channel) {
return next().register(channel);//next方法会从boss NioEventLoopGroup管理的NioEventLoop中挑一个并返回,所以调用的是NioEventLoop.register()方法。而其实际实现位于SingleThreadEventLoop中
}
SingleThreadEventLoop.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);//调用channel的unsafe()方法,通过单步可以知道,这里会获得一个AbstractNioMessageChannel.NioMessageUnsafe对象,这个对象是在NioServerSocketChannel初始化时创建的。但是register()方法的实际实现位于AbstractUnsafe中
return promise;
}
AbstractUnsafe.register()
@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 (eventLoop.inEventLoop()) {
register0(promise);//如果当前线程是EventLoop线程,则直接调用register0方法
} else {
try {
eventLoop.execute(new Runnable() {//否则给EventLoop提交一个task,最终还是调用register0方法
@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);
}
}
}
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();//将channel注册到Selector,具体实现位于AbstractNioChannel中
neverRegistered = false;
registered = true;
// Ensure we call handlerAdded(...) before we actually notify the promise. This is needed as the
// user may already fire events through the pipeline in the ChannelFutureListener.
pipeline.invokeHandlerAddedIfNeeded();//这里会将ServerBootstrapAcceptor添加到当前Channel中
safeSetSuccess(promise);
pipeline.fireChannelRegistered();//触发pipeline的channelRegistered事件
// Only fire a channelActive if the channel has never been registered. This prevents firing
// multiple channel actives if the channel is deregistered and re-registered.
if (isActive()) {
if (firstRegistration) {
pipeline.fireChannelActive();
} else if (config().isAutoRead()) {
// This channel was registered before and autoRead() is set. This means we need to begin read
// again so that we process inbound data.
//
// See https://github.com/netty/netty/issues/4805
beginRead();
}
}
} catch (Throwable t) {
// Close the channel directly to avoid FD leak.
closeForcibly();
closeFuture.setClosed();
safeSetFailure(promise, t);
}
}
AbstractNioChannel.doRegister()
@Override
protected void doRegister() throws Exception {
boolean selected = false;
for (;;) {
try {
selectionKey = javaChannel().register(eventLoop().unwrappedSelector(), 0, this);//终于,调用JDK NIO的方法,将传入的channel绑定到event loop关联的Selector上,这个Selector是在NioEventLoop初始化时构造的。需要注意的是这里设置的interest ops是0,此时没有任何作用,后续会在doBind0方法里做具体设置
return;
} catch (CancelledKeyException e) {
if (!selected) {
// Force the Selector to select now as the "canceled" SelectionKey may still be
// cached and not removed because no Select.select(..) operation was called yet.
eventLoop().selectNow();
selected = true;
} else {
// We forced a select operation on the selector before but the SelectionKey is still cached
// for whatever reason. JDK bug ?
throw e;
}
}
}
}
调用链很长,干的事情倒是比较简单:将传入的Channel注册到某个EventLoop关联的Selector上,然后触发一些相关的回调函数。
3. doBind0方法
调用链如下:
AbstractBootstrap.doBind0()
private static void doBind0(
final ChannelFuture regFuture, final Channel channel,
final SocketAddress localAddress, final ChannelPromise promise) {
// This method is invoked before channelRegistered() is triggered. Give user handlers a chance to set up
// the pipeline in its channelRegistered() implementation.
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()
@Override
public ChannelFuture bind(SocketAddress localAddress, ChannelPromise promise) {
return pipeline.bind(localAddress, promise);
}
DefaultChannelPipeline.bind()
@Override
public final ChannelFuture bind(SocketAddress localAddress, ChannelPromise promise) {
return tail.bind(localAddress, 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();
EventExecutor executor = next.executor();
if (executor.inEventLoop()) {//根据当前线程是否为event loop线程而采取策略
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);
}
}
DefaultChannelPipeline.bind()
@Override
public void bind(
ChannelHandlerContext ctx, SocketAddress localAddress, ChannelPromise promise)
throws Exception {
unsafe.bind(localAddress, promise);//这里的unsafe是AbstractNioMessageChannel.NioMessageUnsafe
}
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);
} catch (Throwable t) {
safeSetFailure(promise, t);
closeIfClosed();
return;
}
if (!wasActive && isActive()) {
invokeLater(new Runnable() {
@Override
public void run() {
pipeline.fireChannelActive();//这里会设置对ACCEPT事件的监听,后文再做分析
}
});
}
safeSetSuccess(promise);
}
NioServerSocketChannel.bind()
@Override
protected void doBind(SocketAddress localAddress) throws Exception {
if (PlatformDependent.javaVersion() >= 7) {
javaChannel().bind(localAddress, config.getBacklog());//调用JDK的NIO提供的方法,将传入的channel与指定的端口绑定,并设置backlog大小
} else {
javaChannel().socket().bind(localAddress, config.getBacklog());
}
}
很长很长的调用链,最终做的事情是调用JDK NIO提供的bind函数,将channel与指定的端口绑定。
在AbstractChannel.bind()方法中,提交了一个异步任务,里面只有一行代码:pipeline.fireChannelActive(),这行代码经过一系列调用之后,会执行AbstractChannel.AbstractUnsafe.doBeginRead()方法
然后又会执行到AbstractNioChannel.doBeginRead()方法,其代码如下:
@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);//设置interestOps
}
}
虽然此时selectionKey.interestOps()还没有被设置,但是readInterestOp是一个全局变量,在NioServerSocketChannel初始化的时候就会被设置为SelectionKey.OP_ACCEPT。
于是在doBeginRead方法中,Channel所关注的IO事件就会被设置为ACCEPT事件了。
这样如果有客户端连接进来,就会触发关联的EventLoop里的相关代码,并作出处理了。
4. ServerBootstrapAcceptor
ServerBootstrapAcceptor的关键代码是channelRead方法
@Override
@SuppressWarnings("unchecked")
public void channelRead(ChannelHandlerContext ctx, Object msg) {
final Channel child = (Channel) msg; child.pipeline().addLast(childHandler);//给child channel的pipeline添加用户自定义的handler setChannelOptions(child, childOptions, logger);//设置child channel的options for (Entry<AttributeKey<?>, Object> e: childAttrs) {
child.attr((AttributeKey<Object>) e.getKey()).set(e.getValue());//设置child channel的attribute
} try {//将child channel注册到worker event loop group里,Netty会根据round-robin算法选择一个worker线程来做绑定
childGroup.register(child).addListener(new ChannelFutureListener() {
@Override
public void operationComplete(ChannelFuture future) throws Exception {
if (!future.isSuccess()) {
forceClose(child, future.cause());
}
}
});
} catch (Throwable t) {
forceClose(child, t);
}
}
在服务器收到新链接的时候,这个函数会被触发,然后设置Channel的各种属性与关联的pipeline
这个Channel接着会被交付给worker eventloop group里的一个worker,然后这个Channel上发生的任何读写事件都是由这个worker来处理了
Netty源码学习(四)Netty服务器是如何启动的?的更多相关文章
- 【Netty源码学习】ServerBootStrap
上一篇博客[Netty源码学习]BootStrap中我们介绍了客户端使用的启动服务,接下来我们介绍一下服务端使用的启动服务. 总体来说ServerBootStrap有两个主要功能: (1)调用父类Ab ...
- Netty 源码学习——客户端流程分析
Netty 源码学习--客户端流程分析 友情提醒: 需要观看者具备一些 NIO 的知识,否则看起来有的地方可能会不明白. 使用版本依赖 <dependency> <groupId&g ...
- Netty源码学习系列之4-ServerBootstrap的bind方法
前言 今天研究ServerBootstrap的bind方法,该方法可以说是netty的重中之重.核心中的核心.前两节的NioEventLoopGroup和ServerBootstrap的初始化就是为b ...
- 【Netty源码学习】DefaultChannelPipeline(三)
上一篇博客中[Netty源码学习]ChannelPipeline(二)我们介绍了接口ChannelPipeline的提供的方法,接下来我们分析一下其实现类DefaultChannelPipeline具 ...
- 【Netty源码学习】ChannelPipeline(一)
ChannelPipeline类似于一个管道,管道中存放的是一系列对读取数据进行业务操作的ChannelHandler. 1.ChannelPipeline的结构图: 在之前的博客[Netty源码学习 ...
- Netty 源码学习——EventLoop
Netty 源码学习--EventLoop 在前面 Netty 源码学习--客户端流程分析中我们已经知道了一个 EventLoop 大概的流程,这一章我们来详细的看一看. NioEventLoopGr ...
- 【Netty源码学习】EventLoopGroup
在上一篇博客[Netty源码解析]入门示例中我们介绍了一个Netty入门的示例代码,接下来的博客我们会分析一下整个demo工程运行过程的运行机制. 无论在Netty应用的客户端还是服务端都首先会初始化 ...
- Netty源码分析 (三)----- 服务端启动源码分析
本文接着前两篇文章来讲,主要讲服务端类剩下的部分,我们还是来先看看服务端的代码 /** * Created by chenhao on 2019/9/4. */ public final class ...
- (一)Netty源码学习笔记之概念解读
尊重原创,转载注明出处,原文地址:http://www.cnblogs.com/cishengchongyan/p/6121065.html 博主最近在做网络相关的项目,因此有契机学习netty,先 ...
- Netty源码学习系列之5-NioEventLoop的run方法
前言 NioEventLoop的run方法,是netty中最核心的方法,没有之一.在该方法中,完成了对已注册的channel上来自底层操作系统的socket事件的处理(在服务端时事件包括客户端 ...
随机推荐
- 网易OpenStack部署运维实战
OpenStack自2010年项目成立以来,已经有超过200个公司加入了 OpenStack 项目,目前参与 OpenStack 项目的开发人员有 17,000+,而且这些数字还在增加,作为一个开源的 ...
- hash算法和常见的hash函数 [转]
Hash,就是把任意长度的输入,通过散列算法,变换成固定长度的输出,该输出就是散列值. 这种转换是一种压缩映射,也就是,散列值的空间通常远小于输入的空间,不同的输入可能 会散列成相同的输出,而不 ...
- Ubuntu设置root密码[repost]
From: http://hi.baidu.com/busybox/item/283e7d31433db7179cc65ef3 安装完Ubuntu后在终端使用命令:su -然后输入密码,总是不正确.原 ...
- 抓取网站访问者的QQ号码
开源,是一种精神.但不开源,并不是没有精神,而可能是代码写得惨不忍睹,我属于后者.(首先申明:对代码提出意见可接受,虚心接受,但不能人身攻击啊!) 最近闲的蛋疼,喜欢到处看看做得好的站点, 莫 ...
- java开发环境的安装
1.Java是一门面向对象的编程语言,由sun公司开发的,目前公司已经被oracle公司收购.那么作为一门编程语言,它有自己的编程环境.并不是你编写了java代码后,就能在任何平台上运行,它的运行有自 ...
- restorator 运行后其他所有EXE文件都无法运行的解决方案
昨天要反编译一个EXE,用RESTORATOR来查看资源罗列情况,倒霉的事情发生了,所有EXE文件点右键后‘打开’都没有了,刚开始以为中度了,进安全模式看,发现文件都没有异常,并且在安全模式下问题照样 ...
- lombok 去除麻烦的实体类get和set,toString书写
首先在pom.xml中添加 <dependency> <groupId>org.projectlombok</groupId> <artifactId> ...
- perf 的事件
perf的事件包括: 硬件事件:branch-instrctions / branch-miss / bus-cycles / cache-miss / cache-reference / cycle ...
- 【bzoj4325】NOIP2015 斗地主(&“加强”版) 搜索
题目描述 牛牛最近迷上了一种叫斗地主的扑克游戏.斗地主是一种使用黑桃.红心.梅花.方片的A到K加上大小王的共54张牌来进行的扑克牌游戏.在斗地主中,牌的大小关系根据牌的数码表示如下:3<4< ...
- Hibernate中多对多的annotation的写法(中间表可以有多个字段)
2011-07-04 6:52 一般情况下,多对多的关联关系是需要中间表的: 情况一:如果中间表仅仅是做关联用的,它里面仅有2个外键做联合主键,则使用ManyToMany(不用写中间表的Model,只 ...