又见netty更新,顺便看了一下example,发觉改动非常大,于是感觉应该看看源码,记录之

首先从example中的telnet例子开始:

public void run() throws Exception {
EventLoopGroup bossGroup = new NioEventLoopGroup();
EventLoopGroup workerGroup = new NioEventLoopGroup();
try {
ServerBootstrap b = new ServerBootstrap();
b.group(bossGroup, workerGroup)
.channel(NioServerSocketChannel.class)
.childHandler(new TelnetServerInitializer());
b.bind(port).sync().channel().closeFuture().sync();
} finally {
bossGroup.shutdownGracefully();
workerGroup.shutdownGracefully();
}
}

以上是example中的telnet的TelnetServer启动端的代码实现。

bossGroup与workerGroup与前代netty一样,netty都是基于Reactor模型的一个socketio框架实现,Reactor模型可以专门开一个专题论述,这里就不详细说明了。而bossGroup,workerGroup正是Reactor中的部分体现。

*注意:每一代netty框架发布,一般都应该关注其线程池的实现,例代都有比较明显的改进。这里的EventLoopGroup类就是新一代netty框架的线程池实现了。相对于netty3.0的线程池实现,netty4.0的线程的变化相当大。关于netty4.0的线程池,会在后面开辟一个专门的栏目对其进行讨论。这边只要注意,EventLoopGroup是一个netty4.0的线程池实现就足够了。

ServerBootstrap顾名思忆,就知道是一个服务端启动项类。由它出发,我们来看一下netty4.0是一个怎么样的启动流程,以此引出netty4.0的内部实现:

首先由上面的代码片段我们大致可以看到,ServerBootstrap的对象实例产生后,首先设置group,这是netty的Reactor模型中的线程池实现。设置完了线程池实例,我们就给服务器设置一个服务器管道实例(NioServerSocketChannel看名字就知道是什么),这个实例对象是netty服务端套接字实现的关键,将在后续开辟一个专门的栏目论述它的具体实现。最后是我们熟悉的设置handler,只不过,这里为什么是childHandler,工作原理又是怎么样的,都会在后续文章出得到答案,这里暂时不详述。

好,所有主要的设置项全部就绪后,就是我们的bind操作,一系列的bind操作完成后,我们的服务器随即启动,一切顺利的话,就可以进行访问了。

接下来,我们对bind的流程进行一下论述,以深入netty代码,在论述之前,先要交代一下ServerBootstrap类的细节:

netty4.0源码分析_nio

图1-1

图1-1是ServerBootStrap的一个简略的类继承关系图。中间列出的相关方法与Telnet服务器的启动流程相关,下面详述:

首先调用AbstractBootstrap类中的bind方法,最后调用进AbstractBootstrap类中的doBind方法:

private ChannelFuture doBind(final SocketAddress localAddress) {
final ChannelFuture regPromise = initAndRegister();
final Channel channel = regPromise.channel();
final ChannelPromise promise = channel.newPromise();
if (regPromise.isDone()) {
doBind0(regPromise, channel, localAddress, promise);
} else {
regPromise.addListener(new ChannelFutureListener() {
@Override
public void operationComplete(ChannelFuture future) throws Exception {
doBind0(future, channel, localAddress, promise);
}
});
}
return promise;
}

以上是doBind方法的实现代码:

首先调用initAndRegister方法,这是一个对channel的初始化与启动类,调用这个方法这后,以本例来讲,将会使用ServerBootstrap类中的channelFactory属性,来生成一个ServerChannel实例。channelFactory属性是一个ServerBootstrapChannelFactory对象,这个属性在ServerBootstrap调用b.channel(NioServerSocketChannel.class)(详细可以参考上面telnetServer的run方法)这一方法时,默认设置为ServerBootstrap的内部类ServerBootstrapChannelFactory(图1-1中有体现)。调用这一内部类的newChannel方法,就会实例化一个NioServerSocketChannel对象实例。然后,再对这个实例对象进行注册,产生selectkeys。这一过程相信熟悉java的niosocketapi的童鞋们,都非常熟悉。这里就不详细叙述了,那么,我们来看一下具体代码:

final ChannelFuture initAndRegister() {
Channel channel = createChannel();
try {
init(channel);
} catch (Throwable t) {
channel.unsafe().closeForcibly();
return channel.newFailedFuture(t);
}
ChannelPromise regPromise = channel.newPromise();
channel.unsafe().register(regPromise);
if (regPromise.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 beause 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 regPromise;
}

首先createChannel方法产生一个服务器管道:

Channel createChannel() {
EventLoop eventLoop = group().next();//这里得到线程池中的一根子线程
return channelFactory().newChannel(eventLoop, childGroup);//调用ServerBootstrapChannelFactory对象实例产生设置的NioServerSocketChannel对象实例
}

这里的channelFactory方法得到b.channel(NioServerSocketChannel.class)调用时设置的默认的内部类ServerBootstrapChannelFactory的实例,再根据设置的class类别实例化之,得到一个NioServerSocketChannel对象,童鞋们可以比照netty4.0项目的源代码,看清楚这一过程。

接下去调用ServerBootstrap对象实例的init方法,对管道进行初始化:

@Override
void init(Channel channel) throws Exception {
final Map<ChannelOption<?>, Object> options = options();//这里的选项是对于NioServerSocketChannel而言的
synchronized (options) {
channel.config().setOptions(options);
}
final Map<AttributeKey<?>, Object> attrs = attrs();
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();
if (handler() != null) {
p.addLast(handler());
}
final ChannelHandler currentChildHandler = childHandler;
final Entry<ChannelOption<?>, Object>[] currentChildOptions;
final Entry<AttributeKey<?>, Object>[] currentChildAttrs;
synchronized (childOptions) {
currentChildOptions = childOptions.entrySet().toArray(newOptionArray(childOptions.size()));//childOptions是对于NioSocketChannel而言的
}
synchronized (childAttrs) {
currentChildAttrs = childAttrs.entrySet().toArray(newAttrArray(childAttrs.size()));
}
p.addLast(new ChannelInitializer<Channel>() {
@Override
public void initChannel(Channel ch) throws Exception {
ch.pipeline().addLast(new ServerBootstrapAcceptor(currentChildHandler, currentChildOptions,
currentChildAttrs));
}
});
}

以上是ServerBootstrap类中的具体的init流程,从得到options(这里是server管道的各种设置参数),设置server管道中的各参数->得到server管道中的att以及设置->设置server管道的handler->设置childOptions(这是应答管理,相当于普通的socketChannel)->设置child管理的att->最后为NioServerSocketChannel对象实例的pipeline注册一个ServerBootstrapAcceptor对象的handler,以处理客户端向服务端发起的链接请求。

对serverChannel进行初始化后,就是对其进行register操作(channel.unsafe().register(regPromise))。上面列出的代码中,这执行register操作的是channel的unsafe属性。这是一个实现Unsafeinterface的对象实例。这里的Unsafe类的继承关系如下:

netty4.0源码分析_源码分析_02

图1-2

以上是Unsafeinterface

看了定义的方法就可以感觉到,unsafe接口定义了对于channel对象的一些封装,他是一个channel操作的代理类。netty4.0中Unsafe接口定义于Channel接口中,是一个内部类接口,这也就印证了,Unsafe是一个对Channel的代理类接口,它是专门为对Channel进行操作的代理类定义的接口。

通过unsafe对象实例,我们可以实现对于channel的register操作。从而将selector对象与channel关联,建立套接字链接。

AbstractNioChannel的doRegister方法实现了register操作,如下所示:

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;
}
}
}
}

无论是NioServerSocketChannel对象实例还是NioSocketChannel对象实例,皆使用此方法,实现Selector对象实例与channel的关联。只是最终调用的流程有些区别,这会在后面的channel篇中提到。当register结束以后,bind流程也进入到了尾我们使用Promise对象实现的sync方法来等待serverChannel被关闭。当serverChannel接收到关闭指令的时候,就会调用closeFuture的sync来等待服务器彻底关闭。从而整个服务流程结束。

*注(以上流程涉及二方面的概念:

首先是Future类的概念,以及它的子类Promise类的概念,在前几代的netty框架中,Future类一直是一个重要的概念。现在,在netty4.0中又出现了一个前代中从来没有出现的Promise类。熟悉netty的用户(包括mina用户)都应该对Future非常熟悉,它是一种多线程情况下,线程间通信的一种手段。是对wait、nofity的一次封装。以达到多线程代码中的事件一致性等目的。而在netty4.0是,Promise类作为Future的一个实现类,又加入了sync等方法,对Future进行增强。加入了线程唤醒前的listener机制。使子线程控制更加强大。这些都会在另外开辟的专题中详细论述。

其次就是pipeline(管道)。netty框架是以事件驱动为基础的套接字框架。它的实现在历代netty都非常重要。pipeline的基本实现,相信netty的老用户都很熟悉,这里就不详述了。总得来说,它是一个监听链,以事件驱动的方式,来完成监听事项,处理netty内部事务,以及用户定义的外部事务。是netty框架实现业务的主要场所。具体内部细节请关注后面的nettypipeline专题)。

这里粗略介绍了整个服务器的大致启动流程,还存有不少描述得不太清楚的点。比如,NioServerSocketChannel与NioSocketChannel调用doRegister的过程是不一样的,中间涉及到流程的不同,没有详细描述。netty4.0中的线程池中的改进,也没有论述。重要的inboundoutbound机制。以及netty框架中向来非常重要的buffer这一块,encode,decode机制、channel的继承体系,Promise类等等。

这些都将在将来的专题介绍中一一介绍。