前言
这是本系列的第二篇文章,我想整体把Netty的架构介绍一下,在本篇文章中我会提到一些Netty中的一些组件,但是不会深入去源码分析,在后面的文章我会对Netty中每个重要的组件进行源码分析。
一 Netty的简介
引用 《Netty IN ACTION》 中对Netty的定义:Netty是 是一款异步的事件驱动的网络应用程序框架,支持快速地开发可维护的高性能的面向协议的服务器和客户端。
1.1 Netty的优点
在上一篇文章中我介绍了NIO的相关知识,这里我提到高性能那就引出一个问题:对比Java标准NIO类库,Netty是如何实现更高性能的吗?
从性能角度,Netty在基础的NIO等类库之上进行了很多改进,例如:
- 更加优雅的Reactor模式实现、灵活的线程模型、利用EventLoop等创新性的机制,可以非常高效地管理成百上千的Channel。
- 拥有比Java的核心API更高的吞吐量以及更低的延迟得益于池化和复用,充分利用了Java的Zero-Copy机制,并且从多种角度,“斤斤计较”般的降低内存分配和回收的开销。例如,使用池化的Direct Bufer等技术,在提高IO性能的同时,减少了对象的创建和销毁;利用反射等技术直接操纵SelectionKey,使用数组而不是Java容器等。
- 使用更多本地代码。例如,直接利用JNI调用OpenSSL等方式,获得比Java内建SSL引擎更好的性能。
- 在通信协议、序列化等其他角度的优化。
总的来说,Netty并没有Java核心类库那些强烈的通用性、跨平台等各种负担,针对性能等特定目标以及Linux等特定环境(例如上篇我讲到的EpollServerSocketChannel充分依赖于Linux的epoll函数),采取了一些极致的优化手段。
从设计角度,Netty里面用到了很多优秀的设计模式,而且使用起来也比较简单,具体的一些几个方面体现:
- 统一的API,支持多种传输类型,阻塞的和非阻塞的
- 简单而强大的线程模型
- 真正的无连接数据报套接字支持
- 链接逻辑组件以支持复用
- 详实的javadoc和大量的示例集
当然还有从健壮性来说有:不会因为慢速、快速或者超载的连接而导致OutOfMemoryError;消除在高速网络中NIO应用程序常见的不公平读/写比率;从安全性角度:有完整的SSL/TLS以及StartTLS支持;可用于受限环境下,如Applet和OSGI。最后Netty的社区活跃,发布快速而且频繁,有助于学习和快速定位相关的问题。
1.2 Netty中重要概念
这里还是先把上面的例子程序贴出来
public final class Server {
public static void main(String[] args) throws Exception {
EventLoopGroup bossGroup = new NioEventLoopGroup(1);
EventLoopGroup workerGroup = new NioEventLoopGroup();
try {
ServerBootstrap b = new ServerBootstrap();
b.group(bossGroup, workerGroup)
.channel(NioServerSocketChannel.class)
.childOption(ChannelOption.TCP_NODELAY, true)
.childAttr(AttributeKey.newInstance("childAttr"), "childAttrValue")
.handler(new ServerHandler())
.childHandler(new ChannelInitializer<SocketChannel>() {
@Override
public void initChannel(SocketChannel ch) {
ch.pipeline().addLast(new AuthHandler());
//..
}
});
ChannelFuture f = b.bind(8888).sync();
f.channel().closeFuture().sync();
} finally {
bossGroup.shutdownGracefully();
workerGroup.shutdownGracefully();
}
}
}
-
ServerBootstrap,服务器端程序的入口,这是Netty为简化网络程序配置和关闭等生命周期管理,所引入的Bootstrapping机制。我们通常要做的创建Channel、绑定端口、注册Handler等,都可以通过这个统一的入口,以Fluent API等形式完成,相对简化了API使用。与之相对应, Bootstrap则是Client端的通常入口。
-
channel它代表一个到实体(如一个硬件设备、一个文件、一个网络套接字或者一个能够执行一个或者多个不同的I/O操作的程序组件)的开放连接,如读操作和写操作。目前,可以把Channel 看作是传入(入站)或者传出(出站)数据的载体。因此,它可以被打开或者被关闭,连接或者断开连接。其实Channel就对应着传统网络编程中的Socket连接。
-
EventLoop,这是Netty处理事件的核心机制。上面我使用了EventLoopGroup。我们在NIO中通常要做的几件事情,如注册感兴趣的事件、调度相应的Handler等,都是EventLoop负责。Channel、EventLoop、Thread以及EventLoopGroup 之间的关系可以用如下图表示:
-
ChannelHandler: 这是应用开发者放置业务逻辑的主要地方,Handler主要的操作为Channel缓存读、数据解码、业务处理、写Channel缓存,然后由Channel(代表client)发送到最终的连接终端。
-
ChannelPipeline: 它是ChannelHandler链条的容器,每个Channel在创建后,自动被分配一个ChannelPipeline。在上面的示例中,我们通过ServerBootstrap注册了ChannelInitializer,并且实现了initChannel方法,而在该方法中则承担了向ChannelPipleline安装其他Handler的任务。它在处理ChannelHandler时采用链式调用,初略的流程如下图:
- ChannelFuture: 这是Netty实现异步IO的基础之一,保证了同一个Channel操作的调用顺序。Netty扩展了Java标准的Future,提供了针对自己场景的特有Future定义。可以将 ChannelFuture看作是将来要执行的操作的结果的占位符。它究竟什么时候被执行则可能取决于若干的因素,因此不可能准确地预测,但是可以肯定的是它将会被执行。
上面我把Netty中几个重要的组件,下面我用一幅图来展示他们之间的关系,如下:
二 Netty服务端启动
上一小节我们简略的了解了Netty的各个组件,而且也了解了每个组件各自的任务,这一节我们就通过Netty对Netty服务端启动的分析,更深入的理解Netty的工作机制。这小节开始前先给自己定两个问题:服务端的Socket在哪里初始化的?在哪里accept连接? 这里我把服务端的启动分为四个步骤-创建服务端Channel,初始化服务端Channel、注册Selector和端口绑定,这里我先用一幅图来表示其启动的过程,具体的分析我们后面的小节通过源码来解析。图如下:
2.1 创建服务端Channel
上面小节我贴了服务端启动的代码,具体的每一行代码的含义我在第一篇文章有详细的介绍,这里我就不做介绍了。服务端Channel创建的入口函数是"ChannelFuture f = b.bind(8888).sync()"这行代码中的bind方法,我这里先对这个方法的整体流程做个介绍,首先调用bind方法里面会调用initAndRegister() 方法初始化并注册,在这个方法里面会调用newChannel() 方法创建服务端Channel,具体的我们看看代码。
/**
* AbstractBootstrap.java (251行起)
*/
public abstract class AbstractBootstrap<B extends AbstractBootstrap<B, C>, C extends Channel> implements Cloneable{
.....
/**
* Create a new {@link Channel} and bind it.
*/
public ChannelFuture bind(int inetPort) {
return bind(new InetSocketAddress(inetPort));
}
/**
* Create a new {@link Channel} and bind it.
*/
public ChannelFuture bind(String inetHost, int inetPort) {
return bind(new InetSocketAddress(inetHost, inetPort));
}
/**
* Create a new {@link Channel} and bind it.
*/
public ChannelFuture bind(InetAddress inetHost, int inetPort) {
return bind(new InetSocketAddress(inetHost, inetPort));
}
/**
* Create a new {@link Channel} and bind it.
*/
public ChannelFuture bind(SocketAddress localAddress) {
validate();
if (localAddress == null) {
throw new NullPointerException("localAddress");
}
return doBind(localAddress);
}
.....
}
可见不管传怎样的参数指给bind方法,在最后都会调用doBind方法。然后我们看看doBind方法中做了些啥呢?接着看代码如下:
/**
* AbstractBootstrap.java (316行起)
*/
public abstract class AbstractBootstrap<B extends AbstractBootstrap<B, C>, C extends Channel> implements Cloneable{
......
private ChannelFuture doBind(final SocketAddress localAddress) {
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;
}
}
......
}
可以看到doBind方法首先是通过调用**initAndRegister()**方法来创建的Channel,其实服务端的Channel就是在这个方法中创建的,具体的我们来看看他的源码,如下:
/**
* AbstractBootstrap.java (316行起)
*/
public abstract class AbstractBootstrap<B extends AbstractBootstrap<B, C>, C extends Channel> implements Cloneable{
......
final ChannelFuture initAndRegister() {
Channel channel = null;
try {
channel = channelFactory.newChannel();
init(channel);
} 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);
}
ChannelFuture regFuture = config().group().register(channel);
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;
}
......
}
有上代码可以看出Channel是通过channelFactory.newChannel()创造出来的,具体的细节我们还是得进入源码看看,具体的源码如下:
*/
public class ReflectiveChannelFactory<T extends Channel> implements ChannelFactory<T> {
private final Class<? extends T> clazz;
public ReflectiveChannelFactory(Class<? extends T> clazz) {
if (clazz == null) {
throw new NullPointerException("clazz");
}
this.clazz = clazz;
}
@Override
public T newChannel() {
try {
return clazz.newInstance();
} catch (Throwable t) {
throw new ChannelException("Unable to create Channel from class " + clazz, t);
}
}
......
}
通过上面可以看出这里的newChannel方法创建Channel是通过反射创造的,那么这个Class又是什么时候赋值的呢?那么我们看看这个channelFactory是什么时候初始化的?其实ServerBootstrap 在调用channel的时候我们传入了NioServerSocketChannel.class这个class就是我们初始化的class。
反射创建服务端Channel
既然知道了Channel是哪里创建的,那我们就深入NioServerSocketChannel的源码,看看到里面做了些什么事情,这里我还是把整个流程先梳理一下,首先在构造方法中调用newSocket()方法,这个方法通过jdk底层创建jdk channel,然后调用父类构造方法AbstractNioChannel(),在这个方法中,还会去调用父类构造方法AbstractChannel()创建id、unsafe和pipeline,紧接着调用configureBlocking(false),这个方法是在使用java NIO必须要执行的方法,即设置服务端Channel为非阻塞模式,,最后调用NioServerSocketChannelConfig来创建tcp参数配置类,具体的源码如下:
/**
* NioServerSocketChannel.java (70行起)
*/
public class NioServerSocketChannel extends AbstractNioMessageChannel
implements io.netty.channel.socket.ServerSocketChannel {
......
/**
* Create a new instance
*/
public NioServerSocketChannel() {
this(newSocket(DEFAULT_SELECTOR_PROVIDER));
}
/**
* Create a new instance using the given {@link SelectorProvider}.
*/
public NioServerSocketChannel(SelectorProvider provider) {
this(newSocket(provider));
}
/**
* Create a new instance using the given {@link ServerSocketChannel}.
*/
public NioServerSocketChannel(ServerSocketChannel channel) {
super(null, channel, SelectionKey.OP_ACCEPT);
config = new NioServerSocketChannelConfig(this, javaChannel().socket());
}
......
}
前面我们分析了创建Channel最后是通过反射来实现NioServerSocketChannel的构造,首先会调用NioServerSocketChannel的无参构造函数,在里面就会调用newSocket,这个方法通过jdk底层创建jdk channel,具体的可以看下面的源码:
private static ServerSocketChannel newSocket(SelectorProvider provider) {
try {
/**
* Use the {@link SelectorProvider} to open {@link SocketChannel} and so remove condition in
* {@link SelectorProvider#provider()} which is called by each ServerSocketChannel.open() otherwise.
*
* See <a href="https://github.com/netty/netty/issues/2308">#2308</a>.
*/
return provider.openServerSocketChannel();
} catch (IOException e) {
throw new ChannelException(
"Failed to open a server socket.", e);
}
}
在NioServerSocketChannel中最后都会调用下面的构造函数:
/**
* Create a new instance using the given {@link ServerSocketChannel}.
*/
public NioServerSocketChannel(ServerSocketChannel channel) {
super(null, channel, SelectionKey.OP_ACCEPT);
config = new NioServerSocketChannelConfig(this, javaChannel().socket());
}
我们看看他的父类构造函数,做了些什么事情:
/**
*AbstractNioChannel.java 83行起
*/
public abstract class AbstractNioChannel extends AbstractChannel {
......
/**
* Create a new instance
*
* @param parent the parent {@link Channel} by which this instance was created. May be {@code null}
* @param ch the underlying {@link SelectableChannel} on which it operates
* @param readInterestOp the ops to set to receive data from the {@link SelectableChannel}
*/
protected AbstractNioChannel(Channel parent, SelectableChannel ch, int readInterestOp) {
super(parent);
this.ch = ch;
this.readInterestOp = readInterestOp;
try {
ch.configureBlocking(false);
} catch (IOException e) {
try {
ch.close();
} catch (IOException e2) {
if (logger.isWarnEnabled()) {
logger.warn(
"Failed to close a partially initialized socket.", e2);
}
}
throw new ChannelException("Failed to enter non-blocking mode.", e);
}
}
......
}
在上面的代码中我们已经看到了ch.configureBlocking(false) 这段调用,熟悉java NIO的人都知道这个在不使用java原生网络编程的时候是必须要调用的方法,为Channel设置阻塞模式。我们还得看看这个函数的父类构造器做了啥,具体源码如下:
public abstract class AbstractChannel extends DefaultAttributeMap implements Channel {
......
/**
* Creates a new instance.
*
* @param parent
* the parent of this channel. {@code null} if there's no parent.
*/
protected AbstractChannel(Channel parent) {
this.parent = parent;
id = newId();
unsafe = newUnsafe();
pipeline = newChannelPipeline();
}
......
}
调用父类的构造函数最后会调用到AbstractChannel的构造函数,这个函数是所有Channel的抽象,不管是客户端还是服务端都是继承至这个类,可以看到这个类主要是创建Id(channel的唯一标识),unsafe(Channel底层TCP读写的一个类)和pipeline这三个属性。至此NioServerSocketChannel调用父类的构造方法就分析完了。
/**
* Create a new instance using the given {@link ServerSocketChannel}.
*/
public NioServerSocketChannel(ServerSocketChannel channel) {
super(null, channel, SelectionKey.OP_ACCEPT);
config = new NioServerSocketChannelConfig(this, javaChannel().socket());
}
在构造方法中最后为config赋值了创建了一个NioServerSocketChannelConfig类,这个类的主要作用就是关于tcp参数配置的一个类。到这里Channel的创建我们就分析完了,下一节我们讲讲Channel的初始化。
2.2 初始化Channel
上面小节我们分析了Channel的创建,分析的主要代码是initAndRegister方法中的channel = channelFactory.newChannel() 这一行代码,那么Channel的初始化就是紧跟着的 init(channel) 这行代码。
final ChannelFuture initAndRegister() {
Channel channel = null;
try {
channel = channelFactory.newChannel();
init(channel);
} 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);
}
ChannelFuture regFuture = config().group().register(channel);
if (regFuture.cause() != null) {
if (channel.isRegistered()) {
channel.close();
} else {
channel.unsafe().closeForcibly();
}
}
return regFuture;
}
这节我们的重点是分析init(channel),这里我先对这个方法做个总结吧。保存用户自定义的一些属性,然后通过这些属性创建一个连接接入器,每次接受新的连接是都会配置这些属性,接下来我们具体看看里面的代码吧,源码如下:
/**
*
*/
public class ServerBootstrap extends AbstractBootstrap<ServerBootstrap, ServerChannel> {
......
@Override
void init(Channel channel) throws Exception {
final Map<ChannelOption<?>, Object> options = options0();
synchronized (options) {
channel.config().setOptions(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());
}
}
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(Channel ch) throws Exception {
final ChannelPipeline pipeline = ch.pipeline();
ChannelHandler handler = config.handler();
if (handler != null) {
pipeline.addLast(handler);
}
// We add this handler via the EventLoop as the user may have used a ChannelInitializer as handler.
// In this case the initChannel(...) method will only be called after this method returns. Because
// of this we need to ensure we add our handler in a delayed fashion so all the users handler are
// placed in front of the ServerBootstrapAcceptor.
ch.eventLoop().execute(new Runnable() {
@Override
public void run() {
pipeline.addLast(new ServerBootstrapAcceptor(
currentChildGroup, currentChildHandler, currentChildOptions, currentChildAttrs));
}
});
}
});
}
......
}
这上面的代码有点多,这里我分成几部分来讲解,首先我们先看看下面几行代码:
final Map<ChannelOption<?>, Object> options = options0();
synchronized (options) {
channel.config().setOptions(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());
}
}
上面这几行代码就是把用户自定义的ChannelOption和ChannelAttrs保存起来。紧接着我们看看下面几行代码。
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()));
}
首先拿到在Channel初始化时创建的pipeline,然后拿到我们在创建服务端程序时传入的EventLoopGroup(例子程序中的workerGroup)和childHandler(例子程序中的ChannelInitializer),最后保存用户配置的childOptions和childAttrs。下面我们继续分析下面的源码,如下:
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) {
pipeline.addLast(handler);
}
// We add this handler via the EventLoop as the user may have used a ChannelInitializer as handler.
// In this case the initChannel(...) method will only be called after this method returns. Because
// of this we need to ensure we add our handler in a delayed fashion so all the users handler are
// placed in front of the ServerBootstrapAcceptor.
ch.eventLoop().execute(new Runnable() {
@Override
public void run() {
pipeline.addLast(new ServerBootstrapAcceptor(
currentChildGroup, currentChildHandler, currentChildOptions, currentChildAttrs));
}
});
}
});
可以看出上面的代码就直接是配置服务端的pipeline,首先在pipeline中添加的是用户自定义的Handler,在例子程序中就是ServerHandler,最后实在Pipline中添加ServerBootstrapAcceptor这个连接处理器,这个连接处理器会把用户在childHandler中添加的所有Handler添加进来,具体的这个连接处理器我会在后面的文章中详细介绍这里就不过多介绍,到这里我就把Channel的初始化分析完了,下面小节我会详细介绍Selector的注册。
2.3 注册Selector
这一小节开始我们还是看看initAndRegister这个方法,如下:
final ChannelFuture initAndRegister() {
Channel channel = null;
try {
channel = channelFactory.newChannel();
init(channel);
} 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);
}
ChannelFuture regFuture = config().group().register(channel);
if (regFuture.cause() != null) {
if (channel.isRegistered()) {
channel.close();
} else {
channel.unsafe().closeForcibly();
}
}
return regFuture;
}
我们分析了这个方法的前两步,下面我们就分析这个方法的下面一步,主要体现在ChannelFuture regFuture = config().group().register(channel)这行代码中,这个register方法最终会调用到AbstractChannel.java中的register方法,具体的源码如下:
/**
* AbstractChannel.java 455行起
*/
@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);
} 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);
}
}
}
我们还是对这个方法进行分步拆解,首先是绑定线程具体的代码体现是AbstractChannel.this.eventLoop = eventLoop这行代码,这里的EventLoop我下一篇文章会详细介绍,紧接着是调用**register0(promise)**这个方法,具体的我们看看这个方法里面做了什么,看源码如下:
/**
*AbstractChannel.java 492行起
*/
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;
// 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();
safeSetSuccess(promise);
pipeline.fireChannelRegistered();
// 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);
}
}
其实上面的代码开起来复杂但是我们可以分为两个步骤,第一个步骤就是调用d**oRegister()**方法,具体我们看看这个方法,源码如下:
/**
* AbstractNioChannel.java 383行起
*/
@Override
protected void doRegister() throws Exception {
boolean selected = false;
for (;;) {
try {
selectionKey = javaChannel().register(eventLoop().selector, 0, this);
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;
}
}
}
}
这里可以看到其实这里还是调用的是jdk底层Channel的register方法,javaChannel就是我们前面介绍的保存的服务端Channel,这里还有的细节,到后面的文章我会详细介绍,那下面我们看看第二步,具体的代码如下:
// 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();
safeSetSuccess(promise);
pipeline.fireChannelRegistered();
// 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.
可以看出具体的就是调用pipeline的invokeHandlerAddedIfNeeded方法和和fireChannelRegistered方法,这在例子程序中是体现在哪里的呢,我们看看例子程序中的ServerHandler,具体的代码如下:
/**
* ServerHandler.java
*/
public class ServerHandler extends ChannelInboundHandlerAdapter {
@Override
public void channelActive(ChannelHandlerContext ctx) {
System.out.println("channelActive");
}
@Override
public void channelRegistered(ChannelHandlerContext ctx) {
System.out.println("channelRegistered");
}
@Override
public void handlerAdded(ChannelHandlerContext ctx) {
System.out.println("handlerAdded");
}
......
}
其实依次体现的是handlerAdded方法和channelRegistered方法,在这里我就把Channel的注册介绍完了,那么还剩下最后一步的端口绑定了,具体的看下一小节介绍。
2.4 端口绑定
代码分析到这里,其实我们前面三个步骤都是在分析**initAndRegister()这个方法,这里我们还是回到最初的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()) {
// 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;
}
}
这里的端口绑定主要是通过调用**doBind0()**这个方法来实现的,具体的我们看看源码,如下:
/**
* AbstractBootstrap.java 335行起
*/
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());
}
}
});
}
这个doBind() 方法最后会调用到 AbstractChannel中的bind方法,这个方法具体的源码如下:
/**
* AbstractChannel.java 532行起
*/
public abstract class AbstractChannel extends DefaultAttributeMap implements Channel {
......
@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.isRoot()) {
// 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();
}
});
}
safeSetSuccess(promise);
}
......
}
这个bind方法其实可以分成两步,首先是做一个jdk底层的绑定,然后调用一个fireChannelActive去触发一个ChannelActive事件。首先我们先看看这里的doBind方法吧,具体的源码如下:
/**
* NioServerSocketChannel.java 125行起
*/
public class NioServerSocketChannel extends AbstractNioMessageChannel
implements io.netty.channel.socket.ServerSocketChannel {
......
@Override
protected void doBind(SocketAddress localAddress) throws Exception {
if (PlatformDependent.javaVersion() >= 7) {
javaChannel().bind(localAddress, config.getBacklog());
} else {
javaChannel().socket().bind(localAddress, config.getBacklog());
}
}
......
}
可以看到这里是直接调用javaChannel() 的 bind()方法,这个Channel就是我们前面三步创建的Channel,这个bind方法就是jdk底层的bind方法。下面我们看看第二步调用一个fireChannelActive去触发一个ChannelActive事件,具体的是下面几行代码,如下:
if (!wasActive && isActive()) {
invokeLater(new Runnable() {
@Override
public void run() {
pipeline.fireChannelActive();
}
});
}
首先这个if的条件表示,端口帮点前不是active,端口绑定后就是active,然后进入这段逻辑后,就会从pipeline开始传播一个fireChannelActive事件了,这个事件会从一个HeadContext开始触发,然后会调用一个readIfIsAutoRead() 方法,这个方法又会触发一个**read()**方法,到最后会调用到服务端Channel的一个 doBeginRead() 方法,这个房做的事情就是向Selector注册一个accept事件。
三 总结
这里我们回到第二节开始的时候提到的两个问题,首先是第一个:服务端的Socket在哪里初始化的? 经过上面对源码的分析我们很容易知道这个是在第一步创建服务端Channel的时候通过反射实现的Socket的初始化。然后我们看看第二个问题:在哪里accept连接? 这个其实是在最后一步端口绑定的时候触发的doBeginRead方法,在里面调用了jdk的源码进行accept连接的。其实服务端的启动就是第二节的四个标题,分表由newChannel()、init()、register() 和 doBind() 这四个方法总结。在这里服务端的启动流程就先分析到这里,下一篇文章我将分析 NioEventLoop的工作流程。
参考资料
《Netty实战》 《Netty权威指南》
注:这里使用的是Netty4.1.6的源码进行分析
原文地址(我的个人博客):Netty学习系列(二)-Netty简介及启动