了解了 Netty 的整体结构,今天了解第一个核心组件:Bootstrap。
Bootstrap 基本介绍
Bootstrap 其实我们理解为一个工厂,我们利用这个工厂可以完成客户端、服务端的初始化,构造一个 Bootstrap 对象后,我们调用其方法为服务端或者客户端设置相关组件和参数,如下:
Bootstrap bootstrap = new Bootstrap()
.group(new NioEventLoopGroup())
.channel(NioSocketChannel.class)
.option(ChannelOption.SO_REUSEADDR,true)
...
Netty 为我们提供一个 Bootsstrap , ServerBootstrap 来完成客户端和服务端的初始化。如下:
两个类的配置方式大致相同,我们以
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共用一个线程组。
设置 Channel 类型
在 NIO 中 Channel 是通信的根本,我们收发数据都是基于 Channel 来实现的,对于 Netty 来说 Channel 也是它通信的通道,不过 Netty 不仅仅只支持 NIO 模式,还有 OIO 。
调用 channel() 方法即可设置通道的 IO 类型。
Netty 支持的 IO 类型有如下几种:
NioSocketChannel:异步的客户端TCPSocket连接.NioServerSocketChannel:异步的服务器端TCPSocket连接.NioDatagramChannel:异步的UDP连接NioSctpChannel:异步的客户端Sctp连接.NioSctpServerChannel:异步的Sctp服务器端连接.OioSocketChannel:同步的客户端TCPSocket连接.OioServerSocketChannel:同步的服务器端TCPSocket连接.OioDatagramChanne:同步的UDP连接OioSctpChanne:同步的Sctp服务器端连接.OioSctpServerChannel:同步的客户端TCPSocket连接.
从上面可以看出,Netty 不仅仅只支持 TCP 协议,还支持 UDP 、STCP 协议,同时每种协议都有 NIO 和 OIO 模式。
配置 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不建议手动设置,因为操作系统会根据当前占用,进行自动的调整。
装配流水线
装配流水线其实就是配置处理的 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,ServiceBootstrap在 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 处理器 |