我们从框架的应用层面来分析,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. [转载]基于Redis的Bloomfilter去重(附Python代码)

    前言: “去重”是日常工作中会经常用到的一项技能,在爬虫领域更是常用,并且规模一般都比较大.去重需要考虑两个点:去重的数据量.去重速度.为了保持较快的去重速度,一般选择在内存中进行去重. 数据量不大时 ...

  2. url 拼接的一个模块furl

    from furl import furl getlongtexturl="https://weibo.com/p/aj/mblog/getlongtext" params={ & ...

  3. Ubunt 服务教程集锦

    1.Ubuntu管理服务安装(强烈推荐最好用Xshell和Xftp): 序号 服务名 介绍 教程地址 windows客户端 1 VNC 可以图形界面管理Ubuntu ubuntu安装vncserver ...

  4. KDJ金叉测试

    # -*- coding: utf-8 -*- import os import pandas as pd # ========== 遍历数据文件夹中所有股票文件的文件名,得到股票代码列表stock_ ...

  5. Selenium2+python自动化34-获取百度输入联想词【转载】

    前言 最近有小伙伴问百度输入后,输入框下方的联想词如何定位到,这个其实难度不大,用前面所讲的元素定位完全可以定位到的. 本篇以百度输入框输入关键字匹配后,打印出联想词汇. 一.定位输入框联想词 1.首 ...

  6. 抽象语法树简介(ZZ)

    转载自: http://www.cnblogs.com/cxihu/p/5836744.html (一)简介 抽象语法树(abstract syntax code,AST)是源代码的抽象语法结构的树状 ...

  7. ModelMap和ModelAndView区别

    首先介绍ModelMap和ModelAndView的作用 ModelMap ModelMap对象主要用于传递控制方法处理数据到结果页面,也就是说我们把结果页面上需要的数据放到ModelMap对象中即可 ...

  8. Oracle迁移到DB2常用转换

    因为项目需要,要将Oracle上的东西转移到DB2,于是收集整理了一些需要修改点的注意事项,拿出来大家分享. ORACLE和DB2实现相同功能的实例(主要以Oracle8I和DB2 7.X为例,已测试 ...

  9. centos 7下查找大文件、大目录和常见文件查找操作

    根据园子 潇湘隐者的文章 <Linux如何查找大文件或目录总结>结合实际运维需要整理出常用命令 目标文件和目录查找主要使用 find 命令 结合 xargs (给命令传递参数的一个过滤器, ...

  10. 微服务之SpringCloud实战(三):SpringCloud Eureka高可用

    高可用Eureka 高可用我就不再过多解释了,Eureka Server的设计一开始就考虑了高可用的问题,在Eureka的服务治理设计中,所有的节点即是服务提供方也是消费方,注册中心也不例外,上一章中 ...