前言

前几节深入学习了ChannelPipeline、ChannelHandler和EventLoop后,接下来的问题是:“如何让这些ChannalPipline、ChannelHandler和EventLoop一起有效组合成一个可以实际运行的应用程序呢?”答案是:Bootstrap(引导)。简单来说Bootstrap是一个应用程序中启动过程中的配置,通过这些配置让应用程序能正常的运行起来的一个过程;这个概念有点像操作系统的启动过相近,操作系统的启动过程同样有一个引导的过程。

Bootstrap

在开始讲Netty框架入门系列文章时我们有写过一个包含服务端与客户端相互通信的一个示例(Service与Client示例入门);在这个示例中就使用了BootStrap进行服务端与客户端网络启动的引导过程,下面再来简单的回顾下这个示例:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
//服务端使用ServerBootstrap引导的示例
public static void main(String[] args) throws InterruptedException {
//创建EventLoop
NioEventLoopGroup master = new NioEventLoopGroup();
//创建服务端引导
ServerBootstrap bootstrap = new ServerBootstrap();
bootstrap.group(master);
bootstrap.option(ChannelOption.SO_KEEPALIVE, true);
//指定使用NioServerSocketChannel作为Channel类型
bootstrap.channel(NioServerSocketChannel.class);
//注册一个ChannelInboundHandlerAdapter类型处理channel的连接
bootstrap.childHandler(new ChannelInitializer<SocketChannel>() {
@Override
protected void initChannel(SocketChannel ch) throws Exception {
//TODO 在有连接进入后进行ChannelHandler的编排
}
});
//使用bind方法启动服务端的监听
ChannelFuture future = bootstrap.bind(30888).sync();
future.channel().closeFuture().sync();
master.shutdownGracefully().sync();
}
//客户端使用Bootstrap引导的示例
public static void main(String[] arg) throws InterruptedException {
NioEventLoopGroup group=new NioEventLoopGroup()
Bootstrap bootstrap = new Bootstrap();
bootstrap.channel(NioSocketChannel.class);
bootstrap.group(group)
bootstrap.option(ChannelOption.SO_KEEPALIVE,true);
//绑定远程服务地址与端口
bootstrap.remoteAddress("127.0.0.1", 30888);
final MessageChannel channel = new MessageChannel();
bootstrap.handler(new ChannelInitializer<SocketChannel>() {
@Override
protected void initChannel(SocketChannel ch) throws Exception {
//TODO 在有连接进入后进行ChannelHandler的编排
}
});
//连接远程服务器
ChannelFuture channelFuture = bootstrap.connect().sync();
channelFuture.channel().closeFuture().sync();
group.shutdownGracefully();
}

在上面的例子中可以看到,服务端的引导使用了ServiceBootstrap类进行引导的启动;而在客户端则使用的是Bootstrap类型进行引导的启动。这样做的本意是用来支持不同的应用程序的功能各有裨益。这也就是说服务器端致力于使用一个父Channel来接受来自客户端的连接并创建一个新的子Channel,再通过这个Channel与客户端进行通信;而客户端则只需要一个单独的没有父Channel的Channel来用于所有的网络交互。这也适用于无连接的传输协议,如UDP,因为它们并不是每个连接都需要一个单独的Channel。下面通过一张类图来看看Netty框架中Bootstarp类与ServiceBootstrap类的类关系图:

image-20210801155909215

前面学习的几个Netty组件都有参与到引导过程中,而且其中一些在客户端和服务器端都有用到。两种应用程序类型之间能用的引导步骤邮AbstractBootstrap类处理,而特定于客户端与服务端的引导步骤刚分别由BootStrap与ServiceBootstrap处理。

这里有个问题:为什么引导类是Cloneable的?
AbstractBootstrap类的完整声明是这样的:

1
public abstract class AbstractBootstrap<B extends AbstractBootstrap<B, C>, C extends Channel> implements Cloneable

这样的做的目的是为了在需要创建多个具有类似配置或者完全相同配置的Channel。为了支持这种模式而又不需要为每个Channel都创建并配置一个新的引导类实例,AbstractBootstrap被标记为了Cloneable;这样在一个已经配置完成了的AbstractBootstrap类型实例上调用clone()方法就可以得到一个相同引导类实例。但这样使用时需要注意,clone()方法得到的引导类实例只是一个浅拷贝,也就是说修改clone后的实例会影响到原有的实例值的变化。

Channel和EventLoopGroup的兼容性

在上面客户端与服务端的例子中分别使用了Bootstrap和ServiceBootStrap类进行各个端的引导启动,在这个过程中还使用了EventLoopGroup与Channel类型,例子中使用了NioEventLoopGroup与NioServerSocketChannel引导服务端启动,使用NioEventLoopGroup与NioSocketChannel引导客户端启动。

有同学可能会想,如果在引导过程中EventLoopGroup的类型使用NioEventLoopGroup,而Channel的类型使用OIO的OioServiceSocketChannel进行服务端的引用行不行呢?

答案在运行程序后Netty就会告诉你这样是不行的,因为NioEventLoopGroup与OioServiceSocketChannel是不兼容的,并会提示一个异常错误:

1
Exception in thread "main" java.lang.IllegalStateException: incompatible event loop type: io.netty.channel.nio.NioEventLoop

这会得到一个java.lang.IllegalStateException的异常提示。所以当我们在编写客户端或者服务端时使用Nio模式的话EventLoopGroup与Channel类型都需要使用Nio开头的对应的EventLoopGroup与Channel类型,使用Oio或者Epoll都是一样,这也算是Netty框架中引导类的一个约定吧。

给Channel添加选项(ChannelOption)

ChannelOption这个功能在之前的文章中并没有提到过,但它也是Netty中一个重要的功能。有时需要为Channel添加一些配置或者属性,可以手动获取到channel后一个一个的添加,但这样做是乏味的无趣的,在后续维护中可能还会带来些未知的问题。为此Netty的引导类提供了一个配置方法option(),通过这个方法就可以将配置的ChannelOption类型实例应用到引导中。所提供的值将会自动应用到引导所创建的所有Channel。ChannelOption可以配置的选项包含了底层连接的详细信息,如keep-alive或者超时属性以及缓冲区的设置。通过一个简单的例子演示下如何使用吧:

1
2
3
ServerBootstrap bootstrap = new ServerBootstrap();
bootstrap.option(ChannelOption.SO_KEEPALIVE, true)
.option(ChannelOption.CONNECT_TIMEOUT_MILLIS,5000);

关闭

有启动就会有关闭,那如何优雅的关闭我们的应用程序呢?在引导过程中创建了很多需要使用的资源,这些资源都是应用程序根据需要自动创建的,比如有一个新的连接时创建一个新的Channel资源,接收到消息或者需要发送消息时创建的ByteBuf资源,还有启动时创建的EventLoopGroup等;这些资源都需要在应用程序关闭时进行释放。好在EventLoopGroup提供了shutdownGracefully()方法,调用shutdownGracefully()方法后会返回一个Future类型的实例,这说shutdownGracefully()是一个异步的操作,应用程序的关闭需要阻塞直到它完成。优雅关闭一个应用程序的示例如下:

1
2
3
4
5
6
7
8
9
10
11
12
//创建EventLoopGroup
NioEventLoopGroup master = new NioEventLoopGroup();
//创建引导类
ServerBootstrap bootstrap = new ServerBootstrap();
//绑定EventLoopGroup至引导类
bootstrap.group(master);
//启动服务端的连接监听
ChannelFuture future = bootstrap.bind(30888).sync();
//关闭所有的Channel并阻塞直到完成
future.channel().closeFuture().sync();
//关闭EventLoopGroup并阻塞直到完成
master.shutdownGracefully().sync();

总结

到这你已经学会了所有Netty组件的使用,现在你可以自己编写一个使用Netty框架进行通信的应用程序。通过这些天的学习我们了解了Netty的Bootstrap、ByteBuf、ChanneHandler、ChannelPepiline、EventLoopt等重要组件,通过这些组件的组合使用完成了一个简单的示例。