摘要:对于,目前大家只知道是个线程组,其内部到底如何实现的,它的作用到底是什么,大家也都不太清楚,由于篇幅原因,这里不作详细介绍,后面会有文章作专门详解。
在上一篇《ServerBootstrap 与 Bootstrap 初探》中,我们已经初步的了解了ServerBootstrap是netty进行服务端开发的引导类。 且在上一篇的服务端示例中,我们也看到了,在使用netty进行网络编程时,我们是通过bind方法的调用来完成服务器端口的侦听:
EventLoopGroup bossGroup = new NioEventLoopGroup();
EventLoopGroup workerGroup = new NioEventLoopGroup();
try {
ServerBootstrap b = new ServerBootstrap();
b.group(bossGroup, workerGroup)
.channel(NioServerSocketChannel.class)
.handler(new LoggingHandler())
.childHandler(new ChannelInitializer() {
@Override
public void initChannel(SocketChannel ch) throws Exception {
ch.pipeline().addLast(new DiscardServerHandler());
}
})
.option(ChannelOption.SO_BACKLOG, 128)
.childOption(ChannelOption.SO_KEEPALIVE, true);
// 侦听8000端口
ChannelFuture f = b.bind(8000).sync();
f.channel().closeFuture().sync();
} finally {
workerGroup.shutdownGracefully();
bossGroup.shutdownGracefully();
}
从上面的服务端示例中可以看到,我们只是定义了主线程组及worker线程组,以及指定了channel类型为NioServerSocketChannel等等一些简单的配置, 然后绑定侦听端口,用于网络服务的主体代码基本就完了(业务代码在Handler中实现,后面的文章会详细介绍。
这真的大大简化并方便了java程序员使用netty来进行网络开发,但是想要深入学习netty的人可能会有下面的一些疑问:
netty是继续Java NIO的,那么ServerSocketChannel是什么时候初始化的?
我怎么没有看到Java NIO中的selector, netty是如何实现多路复用的?
我们设置的handler 或者 childHandler,是如何应用的?
boss线程组 以及 worker线程组 其实如何分配线程的?
为什么是boss线程组,难道接收用户请求是多个线程一起工作?
。。。
本篇将带着上面这些疑问,我们将进入bind方法内部,深入浅出,对netty的工作机制一探究竟。
当我们调用ServerBootstrap的bind方法时,其实是调用的是父类AbstractBootstrap的bind方法:
public ChannelFuture bind(int inetPort) {
return bind(new InetSocketAddress(inetPort));
}
进而调用另一个重载bind方法:
public ChannelFuture bind(SocketAddress localAddress) {
validate();
if (localAddress == null) {
throw new NullPointerException("localAddress");
}
return doBind(localAddress);
}
此bind(SocketAddress localAddress)内部有两个调用:
1、 调用validate()
顾名思义,validate应该是做校验,由于ServerBootstrap覆盖了AbstractBootstrap方法,因此此validate其实是调用ServerBootstrap中的validate方法:
@Override
public ServerBootstrap validate() {
super.validate();
if (childHandler == null) {
throw new IllegalStateException("childHandler not set");
}
if (childGroup == null) {
logger.warn("childGroup is not set. Using parentGroup instead.");
childGroup = config.group();
}
return this;
}
在子类ServerBootstrap的validate方法中,首先它有调用了基类的validate()方法:
public B validate() {
if (group == null) {
throw new IllegalStateException("group not set");
}
if (channelFactory == null) {
throw new IllegalStateException("channel or channelFactory not set");
}
return self();
}
所以通过validate方法我们得出如下结论:
1) 服务端程序必须要设置boss线程组 以及 worker线程组,分别用于Acceptor 以及 I/O操作;
2)必须通过Boostrap的channel()来指定通道类型,用来生成相应的通道(ServerSocketChannel或SocketChannel);
3) 因为是服务端程序,所以必须设置ChildHandler,来指定业务处理器,否则没有业务处理的服务器hi没有意义的;
2、 调用doBind(localAddress)
首先我们先看一下AbstractBootstrap中doBind方法代码片段:
private ChannelFuture doBind(final SocketAddress localAddress) {
final ChannelFuture regFuture = initAndRegister(); // (1)
final Channel channel = regFuture.channel(); // (2)
if (regFuture.cause() != null) {
return regFuture;
}
if (regFuture.isDone()) {
ChannelPromise promise = channel.newPromise();
doBind0(regFuture, channel, localAddress, promise); // (3)
return promise;
} else {
final PendingRegistrationPromise promise = new PendingRegistrationPromise(channel);
regFuture.addListener(new ChannelFutureListener() {
@Override
public void operationComplete(ChannelFuture future) throws Exception {
Throwable cause = future.cause();
if (cause != null) {
promise.setFailure(cause);
} else {
promise.registered();
doBind0(regFuture, channel, localAddress, promise); // (3)
}
}
});
return promise;
}
}
剥去无用代码,其实doBind方法内部,只做了两件事:
一、调用了initAndRegister方法
二、调用用了doBind0方法
到底这两个方法做了啥工作,我们继续往下分析。
一、首先看看 initAndRegister方法内部代码:
final ChannelFuture initAndRegister() {
Channel channel = null;
try {
// (1) 调用工厂方法,生成channel实例
channel = channelFactory.newChannel();
// (2) 初始化通道信息
init(channel);
} catch (Throwable t) {
if (channel != null) {
channel.unsafe().closeForcibly();
}
return new DefaultChannelPromise(channel, GlobalEventExecutor.INSTANCE).setFailure(t);
}
// (3) 注册通道
ChannelFuture regFuture = config().group().register(channel);
if (regFuture.cause() != null) {
if (channel.isRegistered()) {
channel.close();
} else {
channel.unsafe().closeForcibly();
}
}
return regFuture;
}
通过上面代码,我们可以看出initAndRegister方法做了三件事:
①、调用channelFactory生成通道channel实例:
在上一篇中,我们已经知道,通过serverbootstrap的channel方法来指定通道类型,其实是调用基类AbstractBoostrap的channel方法,其内部其实是实例化了一个产生指定channel类型的channelFactory。
所以,initAndRegister中的channelFactory.newChannel()方法就是生成了一个NioServerSocketChannel的实例。 关于NioServerSocketChannel内部细节,我会有专门的文章进行分析,此处不做详述。
②、调用init(channel)初始化通道信息
init方法在基类AbstractBootstrap中是一个抽象方法:
abstract void init(Channel channel) throws Exception;
所以此处init的具体实现在子类ServerBootstrap类中:
@Override
void init(Channel channel) throws Exception {
// 设置引导类配置的option
final Map, Object> options = options0();
synchronized (options) {
setChannelOptions(channel, options, logger);
}
// 设置引导类配置的attr
final Map, Object> attrs = attrs0();
synchronized (attrs) {
for (Entry, Object> e: attrs.entrySet()) {
@SuppressWarnings("unchecked")
AttributeKey