我们从框架的应用层面来分析,NioEventLoopGroup在netty中的使用。

这是我们需要配置的地方。

紧接着我们进入netty的运行中。ServerBootstrap.bind(PORT);

这是一个bind操作。我们来看一下NioEventLoopGroup在这个操作中的使用。

ChannelFuture regFuture = config().group().register(channel);

config()返回在ServerBootstrap中内部属性:private final ServerBootstrapConfig config = new ServerBootstrapConfig(this);

注意这里传入的是this即ServerBootstrap对象。

这个类主要是用来暴露我们的配置的,直接把我们配置的ServerBootstrap传递进来。

我们继续ChannelFuture regFuture = config().group().register(channel);

这里的config()返回一个ServerBootstrapConfig,然后调用他的group()。

这个类是ServerBootstrapConfig的父类。调用了ServerBootstrap.group().方法。注意因为group方法是在弗雷AbstractBootstrap中定义的,所以这里进入父类的group()方法中来。

这个方法直接返回EventLoopGroup对象。

我们在回过头来看我们最初的配置,来发现这个group属性究竟是什么。

我们配置了两个NioEventLoopGroup,一个一个线程的传递给了父类AbstractBootstrap.一个初始化给当前的ServerBootstrap的内部属性childGroup.

我们在回到这里ChannelFuture regFuture = config().group().register(channel);

现在通过AbstractBootstrap.group()方法返回了一个NioEventLoopGroup对象,即我们配置的第一个单线程的NioEventLoopGroup对象。

现在进入他的register(channel)方法。由于这个方法是在他的父类中定义的,所以我们进入他的父类MultithreadEventLoopGroup()中。

next()方法返回一个EventLoop对象。

下面重点分析这个chooser。(在=======以内,然后接着分析next()方法)

====================================================================

这个chooser是我们在初始化NioEventLoopGroup的时候初始化的。

回到我们初始化NioEventLoopGroup的地方:

还是这里。

重点注意这下面的这个SelectorProvider.provider(),在后面的构造方法NioEventLoop(NioEventLoopGroup parent, Executor executor, SelectorProvider selectorProvider,
SelectStrategy strategy, RejectedExecutionHandler rejectedExecutionHandler)中会用到。

下面就到了初始化chooser的地方了:

/**
* Create a new instance.
*
* @param nThreads the number of threads that will be used by this instance.
* @param executor the Executor to use, or {@code null} if the default should be used.
* @param chooserFactory the {@link EventExecutorChooserFactory} to use.
* @param args arguments which will passed to each {@link #newChild(Executor, Object...)} call
*/
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);
}

这个构造方法内容比较多。别的先不管,我们来看:chooser = chooserFactory.newChooser(children);

这个EventExecutorChooserFactory chooserFactory是我们上个构造方法中传过来的:

this(nThreads, executor, DefaultEventExecutorChooserFactory.INSTANCE, args);

是一个DefaultEventExecutorChooserFactory.INSTANCE。

然后我们看他的newChooser(EventExecutor[] executors)方法。

  @SuppressWarnings("unchecked")
@Override
public EventExecutorChooser newChooser(EventExecutor[] executors) {
if (isPowerOfTwo(executors.length)) {
return new PowerOfTwoEventExecutorChooser(executors);
} else {
return new GenericEventExecutorChooser(executors);
}
}

这里有个判断就是:判断是否为2的次方。

private static boolean isPowerOfTwo(int val) {
return (val & -val) == val;
}

然后根据是否为2的次方分别进行两个构造方法。分别实现了EventExecutorChooser的next()方法。

    private static final class PowerOfTwoEventExecutorChooser implements EventExecutorChooser {
private final AtomicInteger idx = new AtomicInteger();
private final EventExecutor[] executors; PowerOfTwoEventExecutorChooser(EventExecutor[] executors) {
this.executors = executors;
} @Override
public EventExecutor next() {
return executors[idx.getAndIncrement() & executors.length - 1];
}
} private static final class GenericEventExecutorChooser implements EventExecutorChooser {
private final AtomicInteger idx = new AtomicInteger();
private final EventExecutor[] executors; GenericEventExecutorChooser(EventExecutor[] executors) {
this.executors = executors;
} @Override
public EventExecutor next() {
return executors[Math.abs(idx.getAndIncrement() % executors.length)];
}
}

这两个实现类只是对于next的方法有不同的实现。但是都是遍历每一个executors。

为什么要这样呢?

原因是位操作&  比 % 操作要高效。netty为了提高效率也是拼了。

总的来说这个DefaultEventExecutorChooserFactory非常简单,就上面这些内容。现在我们也知道了chooser是什么了,就是一个时限了遍历所有EventExecutor的next()方法的对象。功能非常简单,只有一个方法next。遍历每一个EventExecutor。

=====================================================================

好了到此为止我们分析玩了chooser,现在进入主逻辑。

上面我们分析道了这里:

同时我们通过====中的内容我们也知道了EventLoopGroup是如何初始化内部属性EventExecutors的:

children[i] = newChild(executor, args);

但是这个newChild(executor,args)方法是在子类NioEventLoopGroup中实现的:

    @Override
protected EventLoop newChild(Executor executor, Object... args) throws Exception {
return new NioEventLoop(this, executor, (SelectorProvider) args[0],
((SelectStrategyFactory) args[1]).newSelectStrategy(), (RejectedExecutionHandler) args[2]);
}

==============这里插入分析一下NioEventLoop构造方法=====================================

上面的selectorProvider就是在上面我们划了重点注意的地方:

上面紧接着初始化了内部属性private Selector selector;这是一个java.nio.channels.Selector类型的内部属性。

这个属性在后面注册channel到selector的时候将作为参数传入channel的注册方法中。

所以我们接着看下这个openSelector()方法:

    private Selector openSelector() {
try {
unwrappedSelector = provider.openSelector();
} catch (IOException e) {
throw new ChannelException("failed to open a new selector", e);
} if (DISABLE_KEYSET_OPTIMIZATION) {
return unwrappedSelector;
} final SelectedSelectionKeySet selectedKeySet = new SelectedSelectionKeySet(); Object maybeSelectorImplClass = AccessController.doPrivileged(new PrivilegedAction<Object>() {
@Override
public Object run() {
try {
            //重点2-1。这里设置了SelectorImpl.
return Class.forName(
"sun.nio.ch.SelectorImpl",
false,
PlatformDependent.getSystemClassLoader());
} catch (Throwable cause) {
return cause;
}
}
}); if (!(maybeSelectorImplClass instanceof Class) ||
// ensure the current selector implementation is what we can instrument.
!((Class<?>) maybeSelectorImplClass).isAssignableFrom(unwrappedSelector.getClass())) {
if (maybeSelectorImplClass instanceof Throwable) {
Throwable t = (Throwable) maybeSelectorImplClass;
logger.trace("failed to instrument a special java.util.Set into: {}", unwrappedSelector, t);
}
return unwrappedSelector;
} final Class<?> selectorImplClass = (Class<?>) maybeSelectorImplClass; Object maybeException = AccessController.doPrivileged(new PrivilegedAction<Object>() {
@Override
public Object run() {
try {
Field selectedKeysField = selectorImplClass.getDeclaredField("selectedKeys");
Field publicSelectedKeysField = selectorImplClass.getDeclaredField("publicSelectedKeys"); Throwable cause = ReflectionUtil.trySetAccessible(selectedKeysField);
if (cause != null) {
return cause;
}
cause = ReflectionUtil.trySetAccessible(publicSelectedKeysField);
if (cause != null) {
return cause;
} selectedKeysField.set(unwrappedSelector, selectedKeySet);
publicSelectedKeysField.set(unwrappedSelector, selectedKeySet);
return null;
} catch (NoSuchFieldException e) {
return e;
} catch (IllegalAccessException e) {
return e;
}
}
}); if (maybeException instanceof Exception) {
selectedKeys = null;
Exception e = (Exception) maybeException;
logger.trace("failed to instrument a special java.util.Set into: {}", unwrappedSelector, e);
return unwrappedSelector;
}
selectedKeys = selectedKeySet;
logger.trace("instrumented a special java.util.Set into: {}", unwrappedSelector);
return new SelectedSelectionKeySetSelector(unwrappedSelector, selectedKeySet);
}

nio中大量使用了AccessController.doPrivileged可以关于这个的使用可以参考《java 的 AccessController.doPrivileged使用》。

======================================================================

也就是说上面箭头所指的2处返回的是一个NioEventLoop对象。

也就是说next().register(channel)调用的是NioEventLoop.register(channel);

这就是netty的eventloop线程模型的应用了。每个EventLoop有一个Selector, boss用Selector处理accept, worker用Selector处理read,write等

这个方法是在它的父类SingleThreadEventLoop中定义的:

首先构造一个DefaultChannelPromise(channel, this)对象。

然后进入register(new DefaultChannelPromise(channel, this))方法。

promise.channel()返回的就是我们构造promise时传入的channel。还记得channel是怎么构造的吗:

这里的channelFactory.newChannel()返回的是一个我们之前配置的channel类型的channel实例:(这块细节可以参考我的另一篇博客《ChannelFactory初始化》)

因为NioServerSocketChannel没有定义unsafe()这个方法。所以我们一直找到了它的祖祖父类:

这个NioMessageUnsafe是AbstractNioMessageChannel的内部类。

知道了unsafe的类型后我们继续主流程:promise.channel().unsafe().register(this, promise);

现在进入NioMessageUnsafe.register(this,promise);同样调用的是它的父类中的register方法:

内容比较多我们用代码展示:

下面体现了netty中channel与eventloop的一一对应关系。体现了eventloop线程处理channel工作的逻辑。

        @Override
public final void register(EventLoop eventLoop, final ChannelPromise promise) {
if (eventLoop == null) {
throw new NullPointerException("eventLoop");
}
       //这里体现了一个channel只能对应一个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;
}
       //注册到channel
AbstractChannel.this.eventLoop = eventLoop;
       //判断当前线程是否为此eventloop线程
if (eventLoop.inEventLoop()) {
register0(promise);
} else {
          //如果当前线程不是此eventloop线程
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);
}
}
}

上面就体现了eventloop的在netty中的应用。

我们直接看这个方法好了:register0(promise);这个方法就定义在上面方法的下面:

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

下面分析下doRegister()然后回来继续分析

==========我们开个分区分析下doRegister()方法=================================

eventloop中除了定义了一个线程外,它还包含了selector等操作。

循环执行selectionKey = javaChannel().register(eventLoop().unwrappedSelector(), 0, this);

一直到成功为止。

import java.nio.channels.SelectableChannel;
public abstract SelectionKey register(Selector sel, int ops, Object att);这歌是个抽象方法。

然后这歌ServerSocketChannelImpl中并没有定义register(Selector sel, int ops, Object att)方法,所以我们就在SelectableChannel的子类中找。在AbstractSelectableChannel中找到了这个方法的实现,通过debug的确也定为到了这个方法:

java.nio.channels.spi.AbstractSelectableChannel;
    /**
* Registers this channel with the given selector, returning a selection key.
*
* <p> This method first verifies that this channel is open and that the
* given initial interest set is valid.
*
* <p> If this channel is already registered with the given selector then
* the selection key representing that registration is returned after
* setting its interest set to the given value.
*
* <p> Otherwise this channel has not yet been registered with the given
* selector, so the {@link AbstractSelector#register register} method of
* the selector is invoked while holding the appropriate locks. The
* resulting key is added to this channel's key set before being returned.
* </p>
*
* @throws ClosedSelectorException {@inheritDoc}
*
* @throws IllegalBlockingModeException {@inheritDoc}
*
* @throws IllegalSelectorException {@inheritDoc}
*
* @throws CancelledKeyException {@inheritDoc}
*
* @throws IllegalArgumentException {@inheritDoc}
*/
public final SelectionKey register(Selector sel, int ops,
Object att)
throws ClosedChannelException
{
synchronized (regLock) {
if (!isOpen())
throw new ClosedChannelException();
if ((ops & ~validOps()) != 0)
throw new IllegalArgumentException();
if (blocking)
throw new IllegalBlockingModeException();
SelectionKey k = findKey(sel);
if (k != null) {
k.interestOps(ops);
k.attach(att);
}
if (k == null) {
// New registration
synchronized (keyLock) {
if (!isOpen())
throw new ClosedChannelException();
k = ((AbstractSelector)sel).register(this, ops, att);
addKey(k);
}
}
return k;
}
}

该实现是通过spi的机制实现的(关于spi可参考《java中的SPI机制》)

SelectableChannel的register(Selector selector, ...)和Selector的select()方法都会操作Selector对象的共享资源all-keys集合.

根据这个方法的注释我们可以很直观的看到这个方法的作用:Registers this channel with the given selector, returning a selection key

仔细看k = ((AbstractSelector)sel).register(this, ops, att);

这个地方喝我们平时经常写的nio代码是一样的。

分析完这个方法后我们再来分析下eventLoop().unwrappedSelector();

这个东西我们在上面分析过了,直接返回NioEventLoop 的private Selector unwrappedSelector;属性。

=============================================================

然后我们继续回到register0(ChannelPromise promise)方法中来。上面我们分析完了doRegister();

doRegister()主要完成了channel注册到eventloop中selector的操作。

下面分析isActive()方法,这个方法定义在NioServerSocketChannel中:

  @Override
public boolean isActive() {
return javaChannel().socket().isBound();
}

这个方法Returns the binding state of the ServerSocket.

如果是绑定状态并且是第一次注册则进入:

pipeline.fireChannelActive();

((ChannelInboundHandler) handler()).channelInactive(this);

这里穿进来this(AbstractChannelHandlerContext)。ChannelInboundHandler.channelInactive(this)中完成业务逻辑后还可以返回到这个方法。

正如我所说,默认情况下所有的handler都没有具体的业务都返回到上层方法来。

到此为止我们就分析完了AbstactBootstrap.doBind(..)方法中的initAndRegister()方法,并在这个过程中观察了NioEventLoopGroup的使用。

结下来就到了bind环节。有兴趣的可以参考我的另一篇博客《netty4 ServerBootstrap.bind(port) debug》。

Netty4 initAndRegister 解析的更多相关文章

  1. netty4 ServerBootstrap.bind(port) debug

    代码是netty4自带的例子 我们在bind的行前加个断电,下面是ServerBootstrap.bind(port)方法所经历的一些步骤. 在AbstractBootstrap.initAndReg ...

  2. Netty4.0源码解析 NioServerSocketChannel

    一.引言Netty的Channel在JDK NIO的Channel基础上做了一层封装,提供了更多的功能.Netty的中的Channel实现类主要有:NioServerSocketChannel(用于服 ...

  3. 基于Netty4的HttpServer和HttpClient的简单实现

    Netty的主页:http://netty.io/index.html 使用的Netty的版本:netty-4.0.23.Final.tar.bz2 ‐ 15-Aug-2014 (Stable, Re ...

  4. 【原创】NIO框架入门(四):Android与MINA2、Netty4的跨平台UDP双向通信实战

    概述 本文演示的是一个Android客户端程序,通过UDP协议与两个典型的NIO框架服务端,实现跨平台双向通信的完整Demo. 当前由于NIO框架的流行,使得开发大并发.高性能的互联网服务端成为可能. ...

  5. 【原创】NIO框架入门(一):服务端基于Netty4的UDP双向通信Demo演示

    申明:本文由作者基于日常实践整理,希望对初次接触MINA.Netty的人有所启发.如需与作者交流,见文签名,互相学习. 学习交流 更多学习资料:点此进入 推荐 移动端即时通讯交流: 215891622 ...

  6. Netty4.x中文教程系列(五)编解码器Codec

    Netty4.x中文教程系列(五)编解码器Codec 上一篇文章详细解释了ChannelHandler的相关构架设计,版本和设计逻辑变更等等. 这篇文章主要在于讲述Handler里面的Codec,也就 ...

  7. Netty 4源码解析:服务端启动

    Netty 4源码解析:服务端启动 1.基础知识 1.1 Netty 4示例 因为Netty 5还处于测试版,所以选择了目前比较稳定的Netty 4作为学习对象.而且5.0的变化也不像4.0这么大,好 ...

  8. Netty5客户端源码解析

    Netty5客户端源码解析 今天来分析下netty5的客户端源码,示例代码如下: import io.netty.bootstrap.Bootstrap; import io.netty.channe ...

  9. Netty5服务端源码解析

    Netty5源码解析 今天让我来总结下netty5的服务端代码. 服务端(ServerBootstrap) 示例代码如下: import io.netty.bootstrap.ServerBootst ...

随机推荐

  1. SuSE Linux修改网卡配置

    http://blog.chinaunix.net/uid-20765159-id-1850854.html SuSE Linux修改网卡配置             2010 SuSE linux ...

  2. 在oracle官网上,找到我们所需版本的jdk

    oracle的官网,因为都是英文,而且内容还特别多,经常的找不到历史版本的JDK. 特地,将找历史版本JDK的方法记录下来. 访问:http://www.oracle.com/technetwork/ ...

  3. 如何生成pyc/pyo/pyd文件

    # 一.如何生成pyc/pyo文件 # 1.通过编写代码生成 import py_compile # 参数如下 ''' def compile(file, cfile=None, dfile=None ...

  4. [ Mongodb ] 问题总汇

    1. Mongodb备份 [root@localhost ~]# mongodump -h /users 2. Mongodb恢复 [root@localhost ~]# mongorestore - ...

  5. 点击回到之前页面,并不刷新js histroy

    history是你浏览过的网页的url(简单的说就是网址)的集合,也就是你的浏览器里的那个历史记录.它在js里是一个内置对象,就跟document一样,它有自己的方法,go就是其中一个. 这个方法的参 ...

  6. python unittest 快速入门

    import unittest def add(x, y): return x + y class TestLearning(unittest.TestCase): def setUp(self): ...

  7. spring FieldRetrievingFactoryBean

    Spring : 基于XML Schema的配置(一): http://www.tuicool.com/articles/mMjY3uI http://www.cnblogs.com/jifeng/a ...

  8. zabbix通过snmp监控vmware vpshere5.5

    https://www.iyunv.com/thread-516343-1-1.html

  9. IE webkit 滚动条样式!

    <style type="text/css"> // IE 模式 *{ scrollbar-face-color:#F3F3F3; /*面子*/ scrollbar-a ...

  10. 自定义的一个JDBC工具类

    package JDBCutils; import java.io.File;import java.io.FileInputStream;import java.sql.Connection;imp ...