文章目录
前言
从本篇文章开始,将开启一系列的Netty源码分析。Netty的版本均基于4.1.37.Final作分析。Netty的各个版本的一些实现可能不同,但大致的思想是相同的,希望读者观其大意,理解其思想为主
至于BIO与NIO的优势劣势、Netty是什么、why Netty这些内容都在—> Netty前言 中,这里就不多赘述,直入正题。本篇文章主要分析Netty服务端在启动的时候都做了什么,由于接下来的几篇文章都会围绕服务端启动后的详细细节,所以这里首先介绍一下服务端的启动源码
Sample
先来看一段服务端启动的代码
public static void start(int port) {
// 创建两个线程池,在此可看成一个黑盒,在下篇文章中会介绍到
EventLoopGroup bossEventLoop = new NioEventLoopGroup(1);
EventLoopGroup workerEventLoop = new NioEventLoopGroup();
try {
// 服务端启动引导类
ServerBootstrap bootstrap = new ServerBootstrap();
// 设置线程池
bootstrap.group(bossEventLoop, workerEventLoop)
// 设置服务端channel
.channel(NioServerSocketChannel.class)
// 设置一个处理客户端channel的handler
.childHandler(new ChannelInitializer() {
@Override
protected void initChannel(Channel ch) throws Exception {
ChannelPipeline pipeline = ch.pipeline();
// 添加某个Handler
pipeline.addLast(null);
}
});
// 绑定端口
ChannelFuture channelFuture = bootstrap.bind(port);
// 阻塞在此,直到被关闭
channelFuture.channel().closeFuture().sync();
} catch (Exception e) {
e.printStackTrace();
} finally {
// 优雅关闭资源
bossEventLoop.shutdownGracefully();
workerEventLoop.shutdownGracefully();
}
}
ServerBootstrap为本章分析的重点,这个类引导了各个Netty组件的初始化工作,它存在的意义就是为了简化Netty的启动,内部封装了各种流程。
以此为切入点,接下来我们来一步步的剖析这个引导类的工作
引导类的启动
线程池的设置
这里先剧透一下,EventLoopGroup这个类可以说是Netty的发动机,整个Netty服务端的运转都靠这个类来驱动着,在前言中我们提到,Netty是异步-事件-驱动的,这里的驱动,就是指EventLoopGroup这个线程组类。
在这里只介绍它于引导类中的设置,对于EventLoopGroup的详细介绍将放到下面几篇文章中去。
group(bossEventLoop, workerEventLoop)
在上面的例子代码中,我们看到首先调用了这个方法,设置了两个线程池给引导类,跟进去看看:
public ServerBootstrap group(EventLoopGroup parentGroup, EventLoopGroup childGroup) {
// 将此变量设置为自己的成员变量
// this.group = parentGroup
super.group(parentGroup);
if (childGroup == null) {
throw new NullPointerException("childGroup");
}
if (this.childGroup != null) {
throw new IllegalStateException("childGroup set already");
}
// 将第二个gourp设置为childGroup成员变量
this.childGroup = childGroup;
return this;
}
这里只需要记住,引导类将其设置为两个成员变量即可
Channel的设置
在例子中,我们调用这段代码将channel设置进去
.channel(NioServerSocketChannel.class)
其实就是将Class保存在一个工厂里,保存在成员变量中
public B channel(Class<? extends C> channelClass) {
if (channelClass == null) {
throw new NullPointerException("channelClass");
}
return channelFactory(new ReflectiveChannelFactory<C>(channelClass));
}
public B channelFactory(ChannelFactory<? extends C> channelFactory) {
...
this.channelFactory = channelFactory;
return (B) this;
}
这个工厂是干啥用的呢?我们这里进入上面具体实例化的工厂类ReflectiveChannelFactory中:
public T newChannel() {
try {
return clazz.newInstance();
} catch (Throwable t) {
throw new ChannelException("Unable to create Channel from class " + clazz, t);
}
}
其实就是利用反射去实例化一个对象,这个对象就是我们最上面调用channel()方法中的参数,也就是NioServerSocketChannel这个类。在什么时候会实例化呢?下面就会看到,这里只是简单的设置一下
Hanlder的设置
public ServerBootstrap childHandler(ChannelHandler childHandler) {
...
this.childHandler = childHandler;
return this;
}
这里简要带过一下,其实以上设置都只是将实例保存到引导类的成员变量中而已,这里记住,设置了childHandler这个成员变量的值
引导类正式启动
到这里,引导类才真正开始做事情,由以下代码开始驱动:
bootstrap.bind(port);
所以,以此为入口来看看都做了什么
public ChannelFuture bind(InetAddress inetHost, int inetPort) {
// 简单将端口和host包装成一个对象
return bind(new InetSocketAddress(inetHost, inetPort));
}
public ChannelFuture bind(SocketAddress localAddress) {
...
return doBind(localAddress);
}
private ChannelFuture doBind(final SocketAddress localAddress) {
// 初始化并注册
final ChannelFuture regFuture = initAndRegister();
...
// 真正将channel进行绑定
doBind0(regFuture, channel, localAddress, promise);
}
这里省略了大量判断的代码,只是确保第一步初始化并注册完成之后,才进行真正的channel绑定到端口的工作。
这里可以将channel看成是socket的一个抽象,可以往channel里写数据读数据,是双向的,一个客户端连接就是一个客户端channel,服务端也有服务端自己的channel。
就像在项目描述那篇文章中的那样,在聊天室的实现中,或是仿支付的实现中,我们都会把客户端channel保存下来,因为这对应了一个连接,到我们需要向客户端写数据的时候就会把该channel拿出来写,就会通过TCP连接写数据到对端。当然除了TCP协议,像UDP之类的协议Netty也有封装,不过Channel是别的对象,但概念都是一样的。
channel的初始化与注册工作
可以说,到现在才来到了最为关键的地方,回忆一下,我们在开头设置了NioServerSocketChannel这个channel类,这个channel属于服务端的channel,主要负责接受客户端的连接,它的初始化工作就在上面initAndRegister这个方法中做的:
final ChannelFuture initAndRegister() {
Channel channel = null;
try {
// 这里调用了我们上面说的工厂,实例化了一个channel
channel = channelFactory.newChannel();
// 初始化channel
init(channel);
}
// 注册channel
ChannelFuture regFuture = config().group().register(channel);
}
这里分了3个步骤:
- 实例化一个channel
- 对channel实例进行初始化工作
- 注册channel
以上3步都可以是一段长逻辑,所以这里分开解析
实例化Channel
我们可以看看,NioServerSocketChannel这个类的构造函数都做了什么
// JDK的nio类,负责创建一个channel
SelectorProvider DEFAULT_SELECTOR_PROVIDER = SelectorProvider.provider();
public