本文基于netty-4.0.19.Final包
本文使用netty事例包中的EchoServer.java类来进行分析
先看EchoServer.java中run方法代码
public void run() throws Exception {
// Configure the server.
EventLoopGroup bossGroup = new NioEventLoopGroup(1);
EventLoopGroup workerGroup = new NioEventLoopGroup();
try {
ServerBootstrap b = new ServerBootstrap();
b.group(bossGroup, workerGroup)
.channel(NioServerSocketChannel.class)
.option(ChannelOption.SO_BACKLOG, 100)
.handler(new LoggingHandler(LogLevel.INFO))
.childHandler(new ChannelInitializer<SocketChannel>() {
@Override
public void initChannel(SocketChannel ch) throws Exception {
ch.pipeline().addLast(
//new LoggingHandler(LogLevel.INFO),
new EchoServerHandler());
}
});
// Start the server.
ChannelFuture f = b.bind(port).sync();
// Wait until the server socket is closed.
f.channel().closeFuture().sync();
} finally {
// Shut down all event loops to terminate all threads.
bossGroup.shutdownGracefully();
workerGroup.shutdownGracefully();
}
}
为什么两个group?它们有什么作用?为什么一个handler?为什么又来一个childHandler?
先不管它,直接看重点的bind方法
进入bind方法后,最终会调用到AbstractBootstrap.java中doBind方法,然后调用initAndRegister方法
final ChannelFuture regFuture = initAndRegister();
进入initAndRegister方法后,发现先生成了一个新的channel
final Channel channel = channelFactory().newChannel();
channelFactory如何创建的?
在EchoServer.java中run方法中发现调用了一个channel方法,channel方法中就会调用channelFactory创建
BootstrapChannelFactory对象的实例,由于是调用的channelFactory().newChannel()方法,我们进入BootstrapChannelFactory类查看newChannel方法,会发现其实是通过反射创建了一个NioServerSocketChannel类的实例
public T newChannel() {
try {
return clazz.newInstance();
} catch (Throwable t) {
throw new ChannelException("Unable to create Channel from class " + clazz, t);
}
}
接着看initAndRegister方法
initAndRegister方法中调用了init方法,查看发现init方法是抽象方法,在ServerBootstrap类中实现
try {
init(channel);
} catch (Throwable t) {
channel.unsafe().closeForcibly();
return channel.newFailedFuture(t);
}
查看ServerBootstrap类的init方法,发现做了两件事情
1、设置了options和attrs,这两个选项在EchoServer类的run方法中没有调用,先忽略
2、在channel的pipeline中增加了两个ChannelHandler,第一个是EchoServer类的run方法中指定的LoggingHandler,第二个是ChannelInitializer
继续看initAndRegister方法
调用group的register方法,而group其实是EchoServer类中run方法中调用group方法设置的NioEventLoopGroup,另外一个为group为childGroup,是定义在ServerBootstrap类中变量,目前还没有用到。
在NioEventLoopGroup方法中查找register方法,发现在父类MultithreadEventLoopGroup中有定义,于是我们跳到了MultithreadEventLoopGroup类的register方法中
MultithreadEventLoopGroup中register方法很简单,委派给EventLoop的register方法,而获取EventLoop是通过调用父类的next方法来做到的
@Override
public EventLoop next() {
return (EventLoop) super.next();
}
@Override
public ChannelFuture register(Channel channel) {
return next().register(channel);
}
现在我们来到了父类MultithreadEventExecutorGroup的next方法中
@Override
public EventExecutor next() {
return chooser.next();
}
chooser是在MultithreadEventExecutorGroup构造方法中实例化的,查看构造方法,发现chooser的构造又和children数组有关系,而children数组长度是由传入的nThreads来决定的
protected MultithreadEventExecutorGroup(int nThreads, ThreadFactory threadFactory, Object... args) {
if (threadFactory == null) {
threadFactory = newDefaultThreadFactory();
}
children = new SingleThreadEventExecutor[nThreads];
if (isPowerOfTwo(children.length)) {
chooser = new PowerOfTwoEventExecutorChooser();
} else {
chooser = new GenericEventExecutorChooser();
}
for (int i = 0; i < nThreads; i ++) {
boolean success = false;
try {
children[i] = newChild(threadFactory, 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);
}
}
那nThreads值是什么呢?threadFactory是否为null呢?args值是什么呢? 由于前面说过,MultithreadEventExecutorGroup类是NioEventLoopGroup类的父类,我们还需要倒回到实例化NioEventLoopGroup类的EchoServer的run方法中找答案 通过在NioEventLoopGroup类中不停地点啊点,发现了所有的答案: 1、如果调用的是NioEventLoopGroup的int参数构造方法(参数值非0),那么nThreads值为传入的值;如果调用的是默认构造方法,那么nThreads值为DEFAULT_EVENT_LOOP_THREADS的值,而DEFAULT_EVENT_LOOP_THREADS值为io.netty.eventLoopThreads属性指定值,如果没有指定io.netty.eventLoopThreads属性,那么DEFAULT_EVENT_LOOP_THREADS值为两倍处理器个数(在MultithreadEventLoopGroup构造方法中给出)
static {
DEFAULT_EVENT_LOOP_THREADS = Math.max(1, SystemPropertyUtil.getInt(
"io.netty.eventLoopThreads", Runtime.getRuntime().availableProcessors() * 2));
}
protected MultithreadEventLoopGroup(int nThreads, ThreadFactory threadFactory, Object... args) {
super(nThreads == 0? DEFAULT_EVENT_LOOP_THREADS : nThreads, threadFactory, args);
}
2、threadFactory值为null(在NioEventLoopGroup构造方法中给出)
public NioEventLoopGroup(int nThreads) {
this(nThreads, null);
}
3、args值为SelectorProvider实例(在NioEventLoopGroup构造方法中给出)
public NioEventLoopGroup(int nThreads, ThreadFactory threadFactory) {
this(nThreads, threadFactory, SelectorProvider.provider());
}
这些问题弄清楚后,终于可以继续往下走了 接下来是判断children长度是否为2的幂,这个方法自认为很简洁,长知识了
private static boolean isPowerOfTwo(int val) {
return (val & -val) == val;
}
然后是实例化children数组的每个实例,调用的是newChild方法,找来找去,发现newChild方法是在NioEventLoopGroup中实现的,返回了NioEventLoop的实例 现在知道MultithreadEventLoopGroup中next方法返回的是children数组中的下一个实例,即NioEventLoop实例,而register方法的调用当然也就是委派给NioEventLoop的实例来调用register方法了 这里有一个疑问:children长度是否为2的幂导致调用next方法返回children数组中值的方式有差别,但是有必要这样吗?也许是性能上的考虑吧 写了不少了,下次有时间继续走读NioEventLoop的register方法吧