在上一篇博客中,介绍了netty其实是有其背后的理论支撑的——reactor模式,并且详细介绍了reactor模式。而这边博客将会详细介绍netty是怎么运用reactor模式的,代码是怎么设计和封装的。
现在看一段使用netty编写的服务端代码:
public class MyServer {
public static void main(String[] args) throws Exception {
EventLoopGroup bossGroup = new NioEventLoopGroup();
EventLoopGroup workerGroup = new NioEventLoopGroup();
try {
ServerBootstrap serverBootstrap = new ServerBootstrap();
serverBootstrap.group(bossGroup, workerGroup).channel(NioServerSocketChannel.class)
.childHandler(new MyServerInitializer());
ChannelFuture channelFuture = serverBootstrap.bind(8899).sync();
channelFuture.channel().closeFuture().sync();
} finally {
bossGroup.shutdownGracefully();
workerGroup.shutdownGracefully();
}
}
}
public class MyServerInitializer extends ChannelInitializer<SocketChannel> {
@Override
protected void initChannel(SocketChannel ch) throws Exception {
ChannelPipeline pipeline = ch.pipeline();
pipeline.addLast(new LengthFieldBasedFrameDecoder(Integer.MAX_VALUE, 0, 4, 0, 4));
pipeline.addLast(new LengthFieldPrepender(4));
pipeline.addLast(new StringDecoder(CharsetUtil.UTF_8));
pipeline.addLast(new StringEncoder(CharsetUtil.UTF_8));
pipeline.addLast(new MyServerHandler());
}
}
public class MyServerHandler extends SimpleChannelInboundHandler<String> {
@Override
protected void channelRead0(ChannelHandlerContext ctx, String msg) throws Exception {
System.out.println(ctx.channel().remoteAddress() + ", " + msg);
ctx.channel().writeAndFlush("from server: " + UUID.randomUUID());
}
@Override
public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {
cause.printStackTrace();
ctx.close();
}
}
这里做一个简单介绍,第一段代码是服务端启动类代码,而且这个启动类代码套用的都是一个通用的模板,一个bossGroup,一个workerGroup。然后再新建一个ServerBootstrap实例,并且调用这个实例的相关方法将新建好的bossGroup和workerGroup传入,然后传入要创先的Channel类型以及childHandler。而ChildHandler一般是我们自己编写的ChannelHandler。
ChildHandler我这里使用的是ChannelInitializer,这是一种特殊的ChannelInboundHandler,用于注册的时候给绑定的Channel做一些初始化工作,比如给Channel关联的ChannelPipeline添加Handler,包括用户自己编写(上面的MyServerHandler就是用户自己编写的)的以及Netty自带的。值得注意的是ChannelInitializer的initChannel方法只会被调用一次,initChannel调用返回后,当前的ChannelInitializer实例会被从绑定的Channel关联的ChannelPipeline中移除。
这里值得一说的是:使用netty,用户编写的代码是很简单的,套用的是一套通用的代码模板,并且如果你不想使用NIO,有时为了兼容以前代码还得继续使用OIO,Netty也提供了支持,只需要把NioEventLoopGroup改成OioEventLoopGroup,channel方法的参数由NioServerSocketChannel.class改成OioServerSocketChannel.class就可以了,但是模板使用的还是那一套。对用户来说,使用起来非常简单,大量的复杂的初始化工作以及实现细节都有netty框架本身进行了封装和实现。还有Netty框架本身的代码抽象、封装、设计也非常棒,层次结构很清晰,有兴趣的可以根据上面的代码,跟进去看下Bootstrap、EventLoopGroup、EventLoop、Channel、ChannelPipeline、ChannelHandler、ChannelHandlerContext这些组件的源码,真的很棒,值得学习。
根据上面的代码,使用的是NioEventLoopGroup,可以猜测的出底层使用的是NIO来进行注册的。如果使用的是NIO,那么肯定是在绑定端口号后,进行NIO的Channel注册,然后后面通过Selector的select方法来实现通道IO事件的监听。那么我们直接跟进上面代码的bind方法找下注册的代码。
在AbstractBootstrap里的doBind方法可以看到以下的一段代码:
看名字,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();
}
}
这里解释以下:
channel = channelFactory.newChannel();
init(channel);
这两行代码,显然是用来创建Channel和初始化Channel的,这里的Channel并不是java NIO原生的Channel,而是Netty自己写的Channel接口,其中这个Channel的一个实现NioServerSocketChannel内部在实例生成时会创建一个Java Nio的ServerSocketChannel用于注册。可以看下代码:
private static final SelectorProvider DEFAULT_SELECTOR_PROVIDER = SelectorProvider.provider();
public NioServerSocketChannel() {
this(newSocket(DEFAULT_SELECTOR_PROVIDER));
}
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 #2308.
*/
return provider.openServerSocketChannel();
} catch (IOException e) {
throw new ChannelException(
"Failed to open a server socket.", e);
}
}
public NioServerSocketChannel(ServerSocketChannel channel) {
super(null, channel, SelectionKey.OP_ACCEPT);
config = new NioServerSocketChannelConfig(this, javaChannel().socket());
}
// 父类构造方法
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);
}
}
接着回来看注册的代码:
ChannelFuture regFuture = config().group().register(channel);
解释下,config()返回的是ServerBootstrapConfig,
private final ServerBootstrapConfig config = new ServerBootstrapConfig(this);
这里可以看到,这个ServerBootstrapConfig里面会包含之前已经初始化好的ServerBootstrap。
接着看group方法:
public final EventLoopGroup group() {
return bootstrap.group();
}
上面代码中的bootstrap的真是类型其实就是之前初始化好的ServerBootstrap。跟进去:
public final EventLoopGroup group() {
return group;
}
这里的group就是之前我们自己创建的NioEventLoopGroup(跟代码其实是bossGroup,由此可见Channel注册,其实是bossGroup来做的)。
接着看register方法(MultithreadEventLoopGroup的register方法):
public ChannelFuture register(Channel channel) {
return next().register(channel);
}
next()方法逻辑感兴趣的可以自行查看,我这里简单介绍下这个方法功能。我们知道EventLoopGroup是有多个EventLoop构成的,而这个next方法的功能就是根据一定算法从EventLoopGroup中选择一个EventLoop出来。所以继续看NioEventLoop的register方法,其实实现在其父类SingleThreadEventLoop:
public ChannelFuture register(Channel channel) {
return register(new DefaultChannelPromise(channel, this));
}
public ChannelFuture register(final ChannelPromise promise) {
ObjectUtil.checkNotNull(promise, "promise");
promise.channel().unsafe().register(this, promise);
return promise;
}
最终的实现在AbstractChannel里:
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);
}
}
}
从上面的代码可以看出,netty保证了注册的逻辑必须是由NioEventLoopGroup中的NioEventLoop持有的那个线程去执行的。看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;
// 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);
}
}
protected void doRegister() throws Exception {
boolean selected = false;
for (;;) {
try {
selectionKey = javaChannel().register(eventLoop().unwrappedSelector(), 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;
}
}
}
}
这个地方感兴趣的ops=0表示所有的IO都监听,read、write、connect、accept。
注册完成后会调用 pipeline.fireChannelRegistered();,我们添加到ChannelPipeline中的Handler的channelRegistered()方法会被调用。
这里讲的是服务端NioServerSocketChannel的注册,客户端使用的NioSocketChannel的注册逻辑一样,只不过Channel的初始化逻辑不一样。NioServerSocketChannel初始化的时候回向ChannelPipeline添加一个ServerBootstrapAcceptor(实际上是一个ChannelInboundHandler),主要做的事是将NioSocketChannel注册到workerGroup上。注册的逻辑还是使用的上面一套代码。
// ServerBootstrapAcceptor 的channelRead方法
public void channelRead(ChannelHandlerContext ctx, Object msg) {
final Channel child = (Channel) msg;
child.pipeline().addLast(childHandler);
setChannelOptions(child, childOptions, logger);
for (Entry<AttributeKey<?>, Object> e: childAttrs) {
child.attr((AttributeKey<Object>) e.getKey()).set(e.getValue());
}
try {
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);
}
}
上面说的注册操作,通过上面的分析,我们知道一定是由NioEventLoop中的持有的那个线程去执行的。所以要了解Netty的线程模型,那就要先研究下NioEventLoop这个类。而NioEventLoop又是被NioEventLoopGroup所持有,并且一个NioEventLoopGroup会持有一个或者多个NioEventLoop。先看NioEventLoopGroup的创建,最后执行的是下面代码:
protected MultithreadEventExecutorGroup(int nThreads, Executor executor,
EventExecutorChooserFactory chooserFactory, Object... args) {
if (nThreads <= 0) {
throw new IllegalArgumentException(String.format("nThreads: %d (expected: > 0)", nThreads));
}
if (executor == null) {
executor = new ThreadPerTaskExecutor(newDefaultThreadFactory());
}
children = new EventExecutor[nThreads];
for (int i = 0; i < nThreads; i ++) {
boolean success = false;
try {
children[i] = newChild(executor, args);
success = true;
} catch (Exception e) {
// TODO: Think about if this is a good exception type
throw new IllegalStateException("failed to create a child event loop", e);
} finally {
if (!success) {
for (int j = 0; j < i; j ++) {
children[j].shutdownGracefully();
}
for (int j = 0; j < i; j ++) {
EventExecutor e = children[j];
try {
while (!e.isTerminated()) {
e.awaitTermination(Integer.MAX_VALUE, TimeUnit.SECONDS);
}
} catch (InterruptedException interrupted) {
// Let the caller handle the interruption.
Thread.currentThread().interrupt();
break;
}
}
}
}
}
chooser = chooserFactory.newChooser(children);
final FutureListener<Object> terminationListener = new FutureListener<Object>() {
@Override
public void operationComplete(Future<Object> future) throws Exception {
if (terminatedChildren.incrementAndGet() == children.length) {
terminationFuture.setSuccess(null);
}
}
};
for (EventExecutor e: children) {
e.terminationFuture().addListener(terminationListener);
}
Set<EventExecutor> childrenSet = new LinkedHashSet<EventExecutor>(children.length);
Collections.addAll(childrenSet, children);
readonlyChildren = Collections.unmodifiableSet(childrenSet);
}
这里会初始化一个执行器executor,然后把这个执行器传入给每一个child。children[i] = newChild(executor, args);表示给每个child进行初始化,跟进去其实就是创建一个NioEventLoop。
protected EventLoop newChild(Executor executor, Object... args) throws Exception {
return new NioEventLoop(this, executor, (SelectorProvider) args[0],
((SelectStrategyFactory) args[1]).newSelectStrategy(), (RejectedExecutionHandler) args[2]);
}
前面 在看注册的时候,有这么一行代码:
AbstractChannel.this.eventLoop = eventLoop;
表示在注册时,会把从NioEventLoopGroup中选出来的那个NioEventLoop跟当前的Channel进行绑定,那之后Channel上的所有的操作都会使用这个绑定的NioEventLoop。所以我们使用NioEventLoop来执行Channel上的操作一般会这么来操作:
channel.eventLoop().execute(Runnable r);
所以来看一看这里面的实现,找到NioEventLoop的execute方法,其实现其实是在它的父类SingleThreadEventExecutor:
public void execute(Runnable task) {
if (task == null) {
throw new NullPointerException("task");
}
boolean inEventLoop = inEventLoop();
if (inEventLoop) {
addTask(task);
} else {
startThread();
addTask(task);
if (isShutdown() && removeTask(task)) {
reject();
}
}
if (!addTaskWakesUp && wakesUpForTask(task)) {
wakeup(inEventLoop);
}
}
这里同样保证了执行一定是在NioEventLoop所持有的那个线程去执行的。如果不是,并且当前NioEventLoop没有绑定线程,就会开启一个新的线程,并且把这个线程跟当前的NioEventLoop进行绑定。如果当前NioEventLoop绑定了线程,就直接添加任务,任务会在未来某个时刻被NioEventLoop持有的线程去执行。
可以看看startThread方法逻辑:
private void startThread() {
if (state == ST_NOT_STARTED) {
if (STATE_UPDATER.compareAndSet(this, ST_NOT_STARTED, ST_STARTED)) {
doStartThread();
}
}
}
private void doStartThread() {
assert thread == null;
executor.execute(new Runnable() {
@Override
public void run() {
thread = Thread.currentThread();
if (interrupted) {
thread.interrupt();
}
boolean success = false;
updateLastExecutionTime();
try {
SingleThreadEventExecutor.this.run();
success = true;
} catch (Throwable t) {
logger.warn("Unexpected exception from an event executor: ", t);
} finally {
for (;;) {
int oldState = state;
if (oldState >= ST_SHUTTING_DOWN || STATE_UPDATER.compareAndSet(
SingleThreadEventExecutor.this, oldState, ST_SHUTTING_DOWN)) {
break;
}
}
// Check if confirmShutdown() was called at the end of the loop.
if (success && gracefulShutdownStartTime == 0) {
logger.error("Buggy " + EventExecutor.class.getSimpleName() + " implementation; " +
SingleThreadEventExecutor.class.getSimpleName() + ".confirmShutdown() must be called " +
"before run() implementation terminates.");
}
try {
// Run all remaining tasks and shutdown hooks.
for (;;) {
if (confirmShutdown()) {
break;
}
}
} finally {
try {
cleanup();
} finally {
STATE_UPDATER.set(SingleThreadEventExecutor.this, ST_TERMINATED);
threadLock.release();
if (!taskQueue.isEmpty()) {
logger.warn(
"An event executor terminated with " +
"non-empty task queue (" + taskQueue.size() + ')');
}
terminationFuture.setSuccess(null);
}
}
}
}
});
}
上面使用的executor就是NioEventLoopGroup初始化的时候创建的那个executor,它的execute逻辑是:
public void execute(Runnable command) {
threadFactory.newThread(command).start();
}
就是创建一个线程启动,而doStartThread方法里面我们看到,执行逻辑中,会把当前执行任务的线程跟NioEventLoop进行绑定。
看到这里,我们可以做几点总结:
1. 一个EventLoopGroup当中会包含一个或者多个EventLoop。
2. 一个EventLoop在它的整个生命周期当中都只会与唯一的一个Thread进行绑定。
3. 所有的EventLoop所处理的I/O事件都将在它所关联的那个Thread上进行处理。
4. 一个Channel在它的整个生命周期中只会注册在一个EventLoop上。
5. 一个EventLoop在运行的过程当中,会被分配给一个或者多个Channel。
下面用两张图来展示,这样看起来更加清晰:
EventLoop的执行逻辑:
Netty NIO线程模型:
注意上面的这个模型图是基于非阻塞传输的(Nio和Aio)。我们知道Netty同样支持阻塞传输(Oio)。Oio的模型图会有所不同:
图中EventLoop跟Channel是一一对应的,这个也跟我们传统的Oio编程模型相吻合。