Netty 的 Bootstrap 和 ServerBootstrap 是什么?启动引导做了哪些配置?
简化版
Bootstrap(客户端)和 ServerBootstrap(服务端)是 Netty 的「启动引导类」——它们用「链式配置」把启动一个 Netty 应用需要的各种组件(线程组、Channel 类型、处理器、参数)组装起来,最后启动。ServerBootstrap(服务端)的核心配置:① group(bossGroup, workerGroup)——两个线程组,boss 负责接受连接、worker 负责处理连接的 IO(主从 Reactor 模型);② channel(NioServerSocketChannel.class)——用哪种 Channel(NIO/Epoll);③ childHandler(...)——给每个新连接(客户端 Channel)配置处理器链(ChannelPipeline);④ option(...) / childOption(...)——设置服务端 Channel 和客户端 Channel 的 TCP 参数(如 SO_BACKLOG、TCP_NODELAY);⑤ bind(port)——绑定端口启动。Bootstrap(客户端)类似,但只有一个线程组、用 handler(...) 配处理器、用 connect(host, port) 连接。关键区别:ServerBootstrap 有两个线程组(boss+worker)和 childHandler(区分「服务端 Channel」和「连接 Channel」),Bootstrap 只有一个线程组和 handler。
详细版
ServerBootstrap vs Bootstrap:
| 维度 | ServerBootstrap(服务端) | Bootstrap(客户端) |
|---|---|---|
| 线程组 | group(boss, worker)(两个) | group(worker)(一个) |
| Channel | NioServerSocketChannel | NioSocketChannel |
| 处理器 | childHandler(连接的处理器) | handler(连接的处理器) |
| 参数 | option(服务端)+ childOption(连接) | option(连接) |
| 启动 | bind(port) | connect(host, port) |
// ServerBootstrap(服务端)
EventLoopGroup bossGroup = new NioEventLoopGroup(1); // 接受连接
EventLoopGroup workerGroup = new NioEventLoopGroup(); // 处理 IO
try {
ServerBootstrap b = new ServerBootstrap();
b.group(bossGroup, workerGroup) // 两个线程组
.channel(NioServerSocketChannel.class) // NIO 服务端 Channel
.option(ChannelOption.SO_BACKLOG, 128) // 服务端 Channel 参数
.childOption(ChannelOption.SO_KEEPALIVE, true) // 连接 Channel 参数
.childHandler(new ChannelInitializer<SocketChannel>() { // 给每个连接配处理器
protected void initChannel(SocketChannel ch) {
ch.pipeline().addLast(new MyHandler());
}
});
ChannelFuture f = b.bind(8080).sync(); // 绑定端口
f.channel().closeFuture().sync(); // 等待关闭
} finally {
bossGroup.shutdownGracefully();
workerGroup.shutdownGracefully(); // 优雅关闭
}
// Bootstrap(客户端)
Bootstrap b = new Bootstrap();
b.group(new NioEventLoopGroup()) // 一个线程组
.channel(NioSocketChannel.class)
.handler(new MyClientHandler()); // handler 不是 childHandler
ChannelFuture f = b.connect("localhost", 8080).sync(); // connect 不是 bind
⚠️
ServerBootstrap最容易混淆的是「handlervschildHandler」和「optionvschildOption」——理解它源于服务端有「两种 Channel」。服务端有一个ServerChannel(如NioServerSocketChannel) 负责「监听端口、接受新连接」,每接受一个客户端连接就产生一个 子 Channel(SocketChannel) 负责「和这个客户端通信」。所以:handler(...)配的是ServerChannel自己的处理器(很少用,一般不配)、childHandler(...)配的是每个新连接(子 Channel)的处理器(这才是你写业务逻辑的地方,最常用);同理option(...)设ServerChannel的参数(如SO_BACKLOG连接队列大小)、childOption(...)设子 Channel 的参数(如TCP_NODELAY、SO_KEEPALIVE)。记住带child的是给「每个客户端连接」的,不带的是给「服务端监听 Channel」的。这也对应了主从 Reactor:boss 处理 ServerChannel(接受连接)、worker 处理子 Channel(IO)。
完整版教学
一、Bootstrap 是什么:启动引导
先理解 Bootstrap 的定位:
启动一个 Netty 应用,需要组装很多组件:
- 线程组(EventLoopGroup)
- Channel 类型(NIO/Epoll)
- 处理器链(ChannelPipeline / ChannelHandler)
- TCP 参数(各种 ChannelOption)
- 绑定端口 / 连接地址
Bootstrap(引导类)的作用:
用"链式配置"把这些组件组装起来,然后启动
→ 是 Netty 应用的"入口"和"装配器"
两种引导类:
ServerBootstrap:服务端(监听端口、接受连接)
Bootstrap:客户端(连接服务器)
链式配置风格:
b.group(...).channel(...).option(...).childHandler(...).bind(...)
→ 每个方法配置一项,返回自身,链式调用
→ 声明式地"描述"要启动的应用长什么样
所以 Bootstrap = Netty 应用的启动装配器(链式配置各组件)
Bootstrap 是「启动引导类」——启动 Netty 应用要组装很多组件(线程组、Channel 类型、处理器链、TCP 参数、端口),Bootstrap 用链式配置把它们组装起来再启动(是 Netty 应用的入口和装配器)。两种:ServerBootstrap(服务端)、Bootstrap(客户端)。链式配置风格(b.group().channel().childHandler().bind(),每个方法配一项返回自身)。理解「Bootstrap 是启动引导类:链式配置组装组件(线程组/Channel/处理器/参数/端口)再启动、是入口和装配器;ServerBootstrap 服务端、Bootstrap 客户端;链式配置声明式描述应用」,就理解了 Bootstrap 的定位。
二、线程组:boss 和 worker
ServerBootstrap 的核心是「两个线程组」——主从 Reactor:
group(bossGroup, workerGroup):两个 EventLoopGroup
bossGroup(主 Reactor):
负责"接受新连接"(ServerChannel 的 accept 事件)
通常线程数很少(1 个就够,一个端口一个监听)
new NioEventLoopGroup(1)
workerGroup(从 Reactor):
负责"处理连接的 IO"(子 Channel 的读写)
处理所有客户端连接的数据读写、业务逻辑
线程数默认 = 2 × CPU 核数
new NioEventLoopGroup() // 默认线程数
主从 Reactor 模型:
boss 接受连接 → 把新连接注册到 worker
worker 处理这个连接的所有 IO
→ 接受连接和处理 IO 分离,各司其职
→ 一个 boss 线程接连接,多个 worker 线程处理 IO(并发高)
客户端(Bootstrap)只有一个线程组:
客户端只连接一个/少量服务器,不用接受连接
→ 一个线程组处理连接的 IO 即可
b.group(new NioEventLoopGroup())
所以服务端两个线程组(boss 接连接、worker 处理 IO)
= 主从 Reactor(见 reactor-thread-model 题)
ServerBootstrap 的核心是「两个线程组」(主从 Reactor):bossGroup(主 Reactor)负责接受新连接(ServerChannel 的 accept,通常 1 个线程)、workerGroup(从 Reactor)负责处理连接的 IO(子 Channel 读写、业务,默认 2×CPU 核数线程)。主从 Reactor:boss 接受连接→注册到 worker→worker 处理该连接所有 IO(接受连接和处理 IO 分离、各司其职、并发高)。客户端只有一个线程组(不用接受连接)。理解「ServerBootstrap 两个线程组:bossGroup 接受连接(1 个线程)、workerGroup 处理 IO(2×CPU 线程);主从 Reactor(boss 接连接注册到 worker、worker 处理 IO);客户端一个线程组」,就掌握了线程组。
三、Channel 类型
channel(...) 指定用哪种 Channel 实现:
channel(XxxChannel.class):指定 Channel 类型
服务端 Channel:
NioServerSocketChannel:NIO 的服务端 Channel(跨平台,最常用)
EpollServerSocketChannel:Linux 的 Epoll 服务端(Linux 上性能更好)
客户端 Channel:
NioSocketChannel:NIO 客户端
EpollSocketChannel:Linux Epoll 客户端
NIO vs Epoll:
NioXxxChannel:基于 Java NIO(跨平台,Windows/Linux/Mac 都能用)
EpollXxxChannel:基于 Linux 的 epoll(native,只能 Linux,性能更好)
→ Linux 生产环境可用 Epoll(更少 GC、更高性能)
→ 通用/开发用 NIO
Channel 类型和线程组要匹配:
NioServerSocketChannel + NioEventLoopGroup
EpollServerSocketChannel + EpollEventLoopGroup
→ 别混用(NIO Channel 配 Epoll 线程组会出错)
所以 channel() 指定 IO 模型的实现(NIO 跨平台 / Epoll Linux 高性能)
channel(...) 指定 Channel 类型:服务端 NioServerSocketChannel(NIO 跨平台最常用)/EpollServerSocketChannel(Linux Epoll 性能更好)、客户端 NioSocketChannel/EpollSocketChannel。NIO vs Epoll:NIO 基于 Java NIO(跨平台)、Epoll 基于 Linux epoll(native、只 Linux、性能更好、更少 GC)——Linux 生产可用 Epoll、通用/开发用 NIO。Channel 类型和线程组要匹配(NIO Channel 配 NioEventLoopGroup、别混用)。理解「channel()指定 Channel 类型:NioServerSocketChannel(跨平台)/EpollServerSocketChannel(Linux 高性能);NIO 跨平台 vs Epoll Linux native 性能好;Channel 和线程组要匹配」,就掌握了 Channel 类型。
四、childHandler 与 handler
handler 和 childHandler 的区别——服务端两种 Channel:
服务端有"两种 Channel":
① ServerChannel(NioServerSocketChannel):
监听端口、接受连接(一个)
② 子 Channel(SocketChannel):
每个客户端连接一个(接受连接时创建)
对应两种 handler:
handler(...):配 ServerChannel 自己的处理器
→ 处理"接受连接"这个事件(很少用,一般不配)
childHandler(...):配"每个新连接(子 Channel)"的处理器
→ 处理和客户端的通信(★ 这才是写业务逻辑的地方,最常用)
childHandler 通常用 ChannelInitializer:
.childHandler(new ChannelInitializer<SocketChannel>() {
protected void initChannel(SocketChannel ch) {
// 给这个新连接配置处理器链(ChannelPipeline)
ch.pipeline().addLast(new Decoder());
ch.pipeline().addLast(new BusinessHandler());
}
})
→ 每个新连接建立时,initChannel 被调用,配置它的 pipeline
客户端(Bootstrap)只有一种 Channel(连接):
用 handler(...)(不是 childHandler)
→ 客户端只有一个连接,直接配 handler
所以:
服务端 childHandler = 给每个客户端连接配处理器(业务逻辑)
客户端 handler = 给连接配处理器
记住带 child 的是给"每个连接"的
handler vs childHandler——服务端有两种 Channel:ServerChannel(监听/接受连接,一个)和子 Channel(每个客户端连接一个)。对应:handler(...) 配 ServerChannel 处理器(处理接受连接,很少用)、childHandler(...) 配每个新连接的处理器(和客户端通信、写业务逻辑的地方、最常用)。childHandler 通常用 ChannelInitializer(每个新连接建立时 initChannel 被调用配置 pipeline)。客户端只有一种 Channel 用 handler。记住带 child 的是给「每个连接」的。理解「服务端两种 Channel:ServerChannel(监听)+子 Channel(每连接);handler 配 ServerChannel(很少用)/childHandler 配每个连接(业务逻辑最常用,用 ChannelInitializer 配 pipeline);客户端用 handler;带 child 是给每个连接的」,就掌握了 handler/childHandler。
五、option 与 childOption
option 和 childOption 设置 TCP 参数——也对应两种 Channel:
option(...) 和 childOption(...):设置 ChannelOption(TCP 参数)
option(...):设 ServerChannel 的参数
SO_BACKLOG:TCP 连接队列大小(等待被 accept 的连接数)
→ 服务端监听相关的参数
childOption(...):设子 Channel(每个连接)的参数
SO_KEEPALIVE:TCP 保活
TCP_NODELAY:禁用 Nagle 算法(小包立即发,降低延迟)
SO_RCVBUF / SO_SNDBUF:收发缓冲区大小
→ 每个客户端连接相关的参数
常用参数:
SO_BACKLOG(option):连接队列,高并发调大(如 1024)
TCP_NODELAY(childOption):延迟敏感设 true(小包立即发)
SO_KEEPALIVE(childOption):长连接保活
WRITE_BUFFER_WATER_MARK(childOption):写缓冲水位(背压)
同样的规律(带 child 是给连接的):
option → ServerChannel(监听)
childOption → 子 Channel(每个连接)
例:
.option(ChannelOption.SO_BACKLOG, 1024) // 监听队列
.childOption(ChannelOption.TCP_NODELAY, true) // 每个连接禁 Nagle
option vs childOption 设 TCP 参数——也对应两种 Channel:option(...) 设 ServerChannel 参数(SO_BACKLOG 连接队列大小,监听相关)、childOption(...) 设子 Channel 参数(SO_KEEPALIVE 保活、TCP_NODELAY 禁 Nagle 小包立即发降延迟、SO_RCVBUF/SNDBUF 缓冲区,每个连接相关)。常用:SO_BACKLOG(option,高并发调大)、TCP_NODELAY(childOption,延迟敏感设 true)、WRITE_BUFFER_WATER_MARK(背压)。同样规律带 child 是给连接的。理解「option 设 ServerChannel 参数(SO_BACKLOG 连接队列)、childOption 设子 Channel 参数(TCP_NODELAY 禁 Nagle/SO_KEEPALIVE 保活/写缓冲水位);常用 SO_BACKLOG/TCP_NODELAY;带 child 是给连接的」,就掌握了 option/childOption。
六、启动与关闭
bind/connect 启动,shutdownGracefully 关闭:
服务端启动:bind(port)
ChannelFuture f = b.bind(8080).sync();
→ 绑定端口,返回 ChannelFuture(异步)
→ sync() 等待绑定完成
f.channel().closeFuture().sync();
→ 等待服务端 Channel 关闭(阻塞主线程,保持服务运行)
客户端启动:connect(host, port)
ChannelFuture f = b.connect("localhost", 8080).sync();
→ 连接服务器
异步和 ChannelFuture:
bind/connect 都是异步的,返回 ChannelFuture
sync():阻塞等待完成
addListener():注册回调(异步处理结果)
优雅关闭:
bossGroup.shutdownGracefully();
workerGroup.shutdownGracefully();
→ 优雅关闭线程组(处理完手头任务再关,见 graceful-shutdown-netty 题)
→ 放在 finally 里保证关闭
完整流程:
配置(group/channel/childHandler/option)
→ bind/connect 启动
→ 运行(处理连接/请求)
→ shutdownGracefully 关闭
所以 Bootstrap 完整生命周期:配置 → 启动 → 运行 → 优雅关闭
启动与关闭:服务端 bind(port)(绑定端口返回 ChannelFuture、sync() 等待、closeFuture().sync() 保持运行)、客户端 connect(host, port)(连接)。异步:bind/connect 返回 ChannelFuture(sync() 阻塞等待、addListener() 回调)。优雅关闭:bossGroup.shutdownGracefully() + workerGroup.shutdownGracefully()(放 finally 保证关闭)。完整流程:配置→bind/connect 启动→运行→shutdownGracefully 关闭。理解「服务端 bind(port)绑定/closeFuture().sync()保持运行、客户端 connect;异步返回 ChannelFuture(sync 等待/addListener 回调);优雅关闭 shutdownGracefully(放 finally);生命周期配置→启动→运行→关闭」,就掌握了启动与关闭。
记忆钩子:「Netty 启动引导:ServerBootstrap(服务端)/Bootstrap(客户端);ServerBootstrap 核心配置:①group(bossGroup 接受连接 1 个线程,workerGroup 处理 IO 2×CPU 线程)=主从 Reactor②channel(NioServerSocketChannel 跨平台/EpollServerSocketChannel Linux 高性能)③★childHandler 给每个连接配处理器(业务逻辑,用 ChannelInitializer 配 pipeline)vs handler 给 ServerChannel(很少用)④option 设 ServerChannel 参数(SO_BACKLOG 连接队列)vs childOption 设子 Channel 参数(TCP_NODELAY/SO_KEEPALIVE)⑤bind(port)启动;Bootstrap 客户端只一个线程组+handler+connect;★带 child 是给每个连接的、不带是给 ServerChannel 监听的;shutdownGracefully 优雅关闭」。
七、常见误区与追问
- 误区:ServerBootstrap 和 Bootstrap 只是名字不同。 有本质区别——ServerBootstrap(服务端)有两个线程组(boss 接受连接 + worker 处理 IO)、用 childHandler 配连接处理器、用 bind 启动;Bootstrap(客户端)只有一个线程组、用 handler 配处理器、用 connect 连接;服务端要区分「监听 Channel」和「连接 Channel」。
- 误区:handler 和 childHandler 一样。 不一样——handler 配的是 ServerChannel(监听 Channel)自己的处理器(处理接受连接事件,很少用);childHandler 配的是每个新连接(子 Channel)的处理器(和客户端通信、写业务逻辑,最常用);带 child 的是给「每个客户端连接」的。
- 误区:option 和 childOption 随便用哪个。 对应不同 Channel——option 设 ServerChannel(监听 Channel)的参数(如 SO_BACKLOG 连接队列大小);childOption 设子 Channel(每个连接)的参数(如 TCP_NODELAY、SO_KEEPALIVE);用错了参数设不到正确的 Channel 上、不生效。
- 误区:boss 线程组要配很多线程。 boss 只负责接受连接(accept),通常 1 个线程就够(一个端口一个监听);worker 才需要多线程(处理所有连接的 IO,默认 2×CPU 核数);boss 配太多线程浪费(accept 不是瓶颈)。
- 追问:ServerBootstrap 的 boss 和 worker 线程组各负责什么? boss(bossGroup,主 Reactor)负责接受新连接——处理 ServerChannel 的 accept 事件,通常 1 个线程就够;worker(workerGroup,从 Reactor)负责处理连接的 IO——每个客户端连接的数据读写、业务逻辑,默认 2×CPU 核数线程;这就是主从 Reactor 模型:接受连接和处理 IO 分离,boss 接连接后把连接注册给 worker,worker 处理该连接的所有后续 IO。
- 追问:handler 和 childHandler 的区别是什么? 服务端有两种 Channel:ServerChannel(监听端口、接受连接)和子 Channel(每个客户端连接);handler(…) 配 ServerChannel 自己的处理器(处理接受连接事件,一般不用配);childHandler(…) 配每个新连接(子 Channel)的处理器(和客户端通信、写业务逻辑,通常用 ChannelInitializer 配置连接的 ChannelPipeline,是最常用的);记住带 child 的是给「每个客户端连接」的。
- 追问:Netty 应用怎么优雅关闭? 调用线程组的 shutdownGracefully()——bossGroup.shutdownGracefully() 和 workerGroup.shutdownGracefully();它会优雅地关闭 EventLoopGroup(停止接受新任务、处理完手头的任务、然后关闭线程);通常放在 finally 块里保证关闭;这样能让正在处理的连接/任务完成,避免直接关闭导致的数据丢失。
八、加强记忆
ServerBootstrap(服务端)和 Bootstrap(客户端)是 Netty 的「启动引导类」——用链式配置组装启动一个 Netty 应用需要的组件。ServerBootstrap 核心配置:① group(bossGroup, workerGroup)——两个线程组,boss 接受连接(主 Reactor,通常 1 个线程)、worker 处理连接 IO(从 Reactor,默认 2×CPU 核数)(主从 Reactor 模型);② channel(...)——Channel 类型(NioServerSocketChannel 跨平台最常用、EpollServerSocketChannel Linux 高性能);③ childHandler(...)——给每个新连接(子 Channel)配处理器链(写业务逻辑的地方,用 ChannelInitializer 配 pipeline,最常用)vs handler(...) 配 ServerChannel(很少用);④ option(...) 设 ServerChannel 参数(SO_BACKLOG 连接队列)vs childOption(...) 设子 Channel 参数(TCP_NODELAY/SO_KEEPALIVE);⑤ bind(port) 绑定端口启动。Bootstrap(客户端):只有一个线程组、用 handler 配处理器、用 connect(host, port) 连接。关键:带 child 的是给「每个客户端连接(子 Channel)」的,不带的是给「服务端监听 Channel(ServerChannel)」的(对应主从 Reactor)。用 shutdownGracefully() 优雅关闭线程组。一句话「ServerBootstrap 启动引导:group(boss 接连接+worker 处理 IO)/channel(NioServerSocketChannel)/childHandler(每个连接配处理器业务逻辑)/option(ServerChannel 参数)vs childOption(连接参数)/bind(port);Bootstrap 客户端一个线程组+handler+connect;带 child 是给每个连接的、不带是给监听 Channel 的」。