上篇文章(Netty 入门 — 要想掌握 Netty,你必须知道它的这些核心组件)大明哥阐述了 Netty 的整体结构,从这篇文章开始大明哥就将这个整体进行拆分讲解,今天是第一个核心组件:Bootstrap。
一句话来概括 Bootstrap,无论是 Netty 的客户端还是服务端程序,都是从这里开始的。
在Netty 入门 — 亘古不变的Hello World一文的 demo 中,我们首先创建一个 ServerBootstrap 对象(服务端)或者 Bootstrap 对象(客户端),然后调用他们的各个方法进行组装整个 Netty 服务端和客户端,从代码中我们可以看出这两个类是一个辅助类,用来辅助服务端或者客户端初始化和启动的。
Bootstrap 基本介绍
Bootstrap 其实我们理解为一个工厂,我们利用这个工厂可以完成客户端、服务端的初始化,构造一个 Bootstrap 对象后,我们调用其方法为服务端或者客户端设置相关组件和参数,如下:
Bootstrap bootstrap = new Bootstrap()
.group(new NioEventLoopGroup())
.channel(NioSocketChannel.class)
.option(ChannelOption.SO_REUSEADDR,true)
...
这个过程是不是非常想一个构造器模式?其实我们真的将其理解为一个构造器,它就是一个构造服务端、客户端的构造器。
那我们是不是可以不使用 Bootstrap 来完成服务端和客户端的初始化呢?其实是可以的,只不过非常麻烦,既然 Netty 为我们提供一个这么好用的工厂类我们为什么不用呢?
启动器有两个,一个是客户端,一个是服务端,如下:
两个类的配置方式大致相同,我们以 ServerBootstrap 为介绍对象。
ServerBootstrap 核心方法
我们使用 ServerBootstrap 来启动服务端,配置流程如下:
对应的代码如下:
// 创建 ServerBootstrap 对象
ServerBootstrap bootstrap = new ServerBootstrap();
// 设置 EventLoopGroup 线程组
bootstrap.group(new NioEventLoopGroup());
// 设置 Channel 类型
bootstrap.channel(NioServerSocketChannel.class);
//配置 option 参数
bootstrap.option(ChannelOption.SO_REUSEADDR,false);
bootstrap.option(ChannelOption.SO_KEEPALIVE, true);
// 定义处理器 Handler
bootstrap.childHandler(new ChannelInitializer<NioSocketChannel>() {
@Override
protected void initChannel(NioSocketChannel ch) throws Exception {
// 解码
ch.pipeline().addLast(new StringDecoder());
ch.pipeline().addLast(new ChannelInboundHandlerAdapter(){
@Override
public void channelActive(ChannelHandlerContext ctx) throws Exception {
System.out.println(ctx.channel() + ",hello world");
}
@Override
public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
System.out.println(new Date() + ":" + msg);
}
});
}
});
// 绑定 8081 端口
ChannelFuture channelFuture = bootstrap.bind(8081).sync();
// 监听关闭事件
// 这里会一直阻塞,知道 channel 关闭
ChannelFuture closeFuture= channelFuture.channel().closeFuture();
closeFuture.sync();
设置 EventLoopGroup 线程组
调用 group()
可以设置 EventLoopGroup 的线程组,该线程组其实就是 Reactor 的线程组,我们知道 Netty 是基于 Reactor 模式的,group()
就是设置 Reactor 模式的线程组。
我们知道 Reactor 模式,线程组有两个:
- BossGroup:服务器连接监听线程组,该线程组专门用来处理客户端的连接请求
- WorkGroup:工作线程组,即业务处理线程组,用来处理每一条连接的数据收发的线程组。
ServerBootstrap 提供了两个 group()
方法用来设置线程组:
group(EventLoopGroup parentGroup, EventLoopGroup childGroup)
:设置 BossGroup 和 WorkGroup。group(EventLoopGroup group)
:这里只配置了一个线程组,也就是 BossGroup 和 WorkGroup 共用一个线程组。
注:有小伙伴如果对 Reactor 模式不是很理解,可以看看大明哥这篇文章:【死磕 NIO】— Reactor 模式就一定意味着高性能吗?
设置 Channel 类型
在 NIO 中 Channel 是通信的根本,我们收发数据都是基于 Channel 来实现的,对于 Netty 来说 Channel 也是它通信的通道,不过 Netty 不仅仅只支持 NIO 模式,还有 OIO 。
调用 channel()
方法即可设置通道的 IO 类型。
Netty 支持的 IO 类型有如下几种:
- NioSocketChannel:异步的客户端 TCP Socket 连接.
- NioServerSocketChannel:异步的服务器端 TCP Socket 连接.
- NioDatagramChannel:异步的 UDP 连接
- NioSctpChannel:异步的客户端 Sctp 连接.
- NioSctpServerChannel:异步的 Sctp 服务器端连接.
- OioSocketChannel:同步的客户端 TCP Socket 连接.
- OioServerSocketChannel:同步的服务器端 TCP Socket 连接.
- OioDatagramChanne:同步的 UDP 连接
- OioSctpChanne:同步的 Sctp 服务器端连接.
- OioSctpServerChannel:同步的客户端 TCP Socket 连接.
从上面可以看出,Netty 不仅仅只支持 TCP 协议,还支持 UDP 、STCP 协议,同时每种协议都有 NIO 和 OIO 模式,从 Netty 支持的通道类型就可以看出,Netty 功能有多么强大了。
配置 Option 参数
调用 option()
可以设置 Channel 相关的参数,其实 ServerBootstrap 还有一个 childOption()
方法。两个方法的区别是:
option()
:是给 parent Channel 设置参数的childOption()
:是给 child Channel 设置参数的。
为什么这里会有一个 parent Channel 和 child Channel 呢?首先我们需要明确一点,Channel 是 Socket 连接的一个抽象,我们可以理解它对 Socket 做了一些封装。当 Netty 建立一个连接后,它会为该连接 new 一个 Channel 实例。同时,它也有了父子的概念了,服务端监听的 Channel 叫做 parent Channel,对应每一个 Socket 连接的 Channel 叫做 child Channel。其实我们从设置 EventLoopGroup 的时候就可以看出,group(EventLoopGroup parentGroup, EventLoopGroup childGroup)
。
可以设置的参数比较多,之类列几个常见的:
- ChannelOption.CONNECT_TIMEOUT_MILLIS:客户端建立连接时,如果超过指定的时间仍未连接,则抛出 timeout 异常。
- ChannelOption.SO_KEEPALIVE:是否开启 TCP 底层心跳机制,true 表示开启。
- ChannelOption.TCP_NODELAY:是否启用 Nagle 算法,true 表示开启。开启可能会对消息的实时性有影响,因为为了提升效率, Nagle 算法会将一些较小的数据包收集后再进行发送,这样就会造成我们的消息有延迟。所以如果实时性要求高的话,一般不建议开启。
- ChannelOption.SO_RCVBUF:设置接收缓冲区的大小
- ChannelOption.SO_SNDBUF:设置发送缓冲区的大小,一般 SO_RCVBUF 和 SO_SNDBUF 不建议手动设置,因为操作系统会根据当前占用,进行自动的调整。
其他参数,大明哥就不一一介绍了,有兴趣的小伙伴可以自行研究,大明哥在后面的 Netty 调优部分会对这些字段进行详细讲解的。
装配流水线
装配流水线其实就是配置处理的 Handler,ChannelPipeline 负责协调这些 Handler,它是 Netty 处理请求的责任链,该链上每个节点都是 ChannelHandler,而这些 ChannelHandler 就是用来处理这些请求的。
ServerBoostrap 提供了 childHandler()
方法用来装配这些 ChannelHandler ,用来组装成一个处理请求的流水线。我们传递一个 ChannelInitializer 实例,ChannelInitializer 是一个特殊的 ChannelHandler,它主要是为我们提供了一个简单的工具,用于在某个 Channel 注册到 EventLoop 后,对这个 Channel 执行一些初始化操作。
ChannelInitializer 是一个抽象类,我们需要实现它的 initChannel()
,在该方法中我们通过 ChannelPipeline 来完成流水线的装配。
ch.pipeline().addLast(new StringDecoder());
ch.pipeline().addLast(new ChannelInboundHandlerAdapter(){//});
ChannelHandler 有两种,分别是 ChannelInboundHandler 和 ChannelOutboundHandler,一个是处理进站,一个处理出站,通俗理解就是,ChannelInboundHandler 处理读入 IO 请求,ChannelOutboundHandler 处理写出 IO 请求。
我们调用 ch.pipeline().addLast()
方法可以装配一个 ChannelHandler 链,结构如下:
当读请求进来时,传播链从 head 出发,依次经过 InboundHandlerA,InboundHandlerB,InboundHandlerC,InboundHandlerD,最后在 Tail 终止。而写出 IO 请求则相反,它是从 Tail 出发,依次经过 OutboundHandlerB,OutboundHandlerA,最后在 Head 终止。
最后
Bootstrap,在 Netty 中是一个比较简单的且容易理解的组件,它仅仅只是服务端、客户端启动的引导器,通过调用相对应的方法,为不同的功能设置不同的组件,组装成一个可以运行的完整的服务端或者客户端。
当然,Bootstrap 还有一些方法大明哥没有介绍,但并妨碍我们去理解 Netty,去理解这些组件,最后,大明哥将 Bootstrap 的方法列举出来,感兴趣的小伙伴可以尝试着实验下:
AbstractBootstrap
方法 | 描述 |
---|---|
group | 设置用于处理所有事件的 EventLoopGroup |
channel | 指定服务端或客户端的 Channel |
channelFactory | 如果引导没有指定 Channel,那么可以指定 ChannelFactory 来创建 Channel |
localAddress | 指定 Channel 需要绑定的本地地址,如果不指定,则将由系统随机分配一个地址 |
remoteAddress | 设置 Channel 需要连接的远程地址 |
attr | 指定新创建的 Channel 的属性值 |
handler | 设置添加到 ChannelPipeline 中的 ChannelHandler |
connect | 连接到远程主机,返回 ChannelFuture,用于连接完成的回调 |
option | 设置 Channel 参数 |
register | 创建一个 Channel,并注册到 Eventloop |
bind | 绑定端口 |
ServerBoostrap
方法 | 描述 |
---|---|
childOption | 为 child Channel 设置参数 |
childAttr | 为 child Channel 设置属性值 |
childHandler | 添加 ChannelHandler 处理器 |