简介
Netty 是 Java 环境里很常见的高性能网络通信框架。
它基于 NIO 和事件驱动模型,把原生 NIO 里比较繁琐的 SelectorChannelByteBuffer事件循环、半包处理、资源释放这些细节封装起来。
一句话概括的观点是,
Netty 适合开发高并发、长连接、自定义协议、网关、RPC、IM、游戏服务、物联网接入这类网络应用。
常见使用场景这方面。
| 场景 | 说明 |
| RPC 框架 | 服务之间走自定义 TCP 协议 |
| IM 聊天 | 长连接、消息推送、在线状态 |
| 游戏服务器 | 低延迟、长连接、频繁消息交互 |
| 网关 | TCP 网关、HTTP 网关、协议转换 |
| 物联网设备接入 | 心跳、指令下发 |
| WebSocket | 实时通知、行情推送、在线客服 |
Netty 本身不是业务框架,它更像网络通信底座。HTTP、WebSocket、私有 TCP 协议、二进制协议,都可以在它上面实现。
常见痛点 & 为什么不直接用 Java Socket
说到痛点一。线程爆炸 & 资源浪费
最早的阻塞 Socket 写法大概是:
一个连接 -> 一个线程 -> 阻塞读取 -> 处理业务
连接少时这种写法直观,但当并发数上升到千甚至万级别时线程数量激增导致:
-
CPU 上下文切换频繁,吞吐下降。
-
每个线程占用栈内存,导致 OOM 风险。
-
处理和资源回收变得异常复杂。
从痛点二来看,原生 NIO 使用门槛高 & 易出错
NIO 解决了阻塞问题。但原生 API 暴露了太多底层概念:
Selector / SelectionKey / SocketChannel / ByteBuffer / OP_ACCEPT / OP_READ / OP_WRITE
AOP 开发者往往在以下方面踩坑:
-
Poor selector registration leads to missed events.
-
Mishandling ByteBuffer flip/clear causes data corruption.
-
Lack of proper cleanup → file descriptor leak.
-
Poor handling of half‑packet 导致业务层协议错乱。按理说,
Nio 与 Netty 的对比
连接接入 -> EventLoop 处理 I/O 事件 -> ChannelPipeline 编排处理器 -> ChannelHandler 写业务逻辑
This abstraction lets developers focus on:
-
# 解码/编码业务数据。
-
# 业务逻辑实现。
-
# 异步结果处理。
-
# 高效资源管理。老实说,
Maven 依赖
普通 Maven 项目可以引入 netty-all{% raw %}:
io.netty
netty-all
${netty.version}
`netty-all` 方便学习和 Demo。生产项目可按需引入模块,例如 `netty-transport` + `netty-codec` 等。按理说,如果已经通过 Spring Cloud Gateway、Reactor Netty 或 gRPC 间接使用 Netty。版本通常交给上层框架管理。
主要组件
| 组件 | 作用 |
| `Bootstrap` | 客户端启动器 |
| `ServerBootstrap` | 服务端启动器 |
| `EventLoopGroup` | 事件循环线程组 |
| `EventLoop` | 单线程负责 I/O 与任务调度 |
| `Channel` | 抽象网络连接 |
| `ChannelPipeline` | Handler 链式编排 |
| `ChannelHandler` | 入站/出站业务处理器 |
| `ByteBuf` | Netty 自研字节缓冲区。支持池化与引用计数 |
`ChannelFuture` 异步操作结果,可添加监听器
|
---
**服务端整体流程**:
ServerBootstrap
-> bossGroup 接收连接
-> workerGroup 处理读写
-> Channel
-> ChannelPipeline
-> Decoder
-> Encoder
-> BusinessHandler
* `bossGroup` 负责 accept,新建 `Channel`。* `workerGroup` 为每个 `Channel` 分配 `EventLoop`,避免锁竞争。
EventLoop 线程模型
一个 Channel 注册到一个 EventLoop
一个 EventLoop 绑定一个线程
一个 EventLoop 可以管理多个 Channel
至于**好处**。同一连接上的 I/O 始终由同一线程完成,天然避免并发冲突。**注意**这方面,不要在 `ChannelHandler` 中执行耗时操作。否则会卡住整个 `EventLoop`。怎么说呢,常见阻塞场景包括:慢 SQL 查询、远程 HTTP 调用、大文件读写、高 CPU 运算还有 `Thread.sleep` 等。说到**方法**,把耗时任务交给业务线程池执行。接下来再回到当前 `EventLoop` 写回响应:
java
ctx.executor.execute -> ctx.writeAndFlush);
第一个 TCP Demo:按行收发消息
*使用者痛点*:在传统 NIO 中自行实现粘包拆包比较容易出错;Netty 已提供开箱即用的解码器方便你定位问题。
服务端 Handler
package com.example.netty.line;import io.netty.channel.ChannelHandlerContext;import io.netty.channel.SimpleChannelInboundHandler;public class LineServerHandler extends SimpleChannelInboundHandler {
@Override
public void channelActive {
System.out.println.remoteAddress);}
@Override
protected void channelRead0 {
String content = msg.trim;System.out.println;String response = "服务端已收到:" + content + "
";话说回来,ctx.writeAndFlush;}
@Override
public void channelInactive {
System.out.println.remoteAddress);}
@Override
public void exceptionCaught {
cause.printStackTrace;ctx.close,}
}
服务端启动类
package com.example.netty.line;import io.netty.bootstrap.ServerBootstrap;import io.netty.channel.ChannelFuture;import io.netty.channel.ChannelInitializer;按理说,import io.netty.channel.ChannelOption;import io.netty.channel.EventLoopGroup;import io.netty.channel.nio.NioEventLoopGroup;import io.netty.channel.socket.SocketChannel;import io.netty.channel.socket.nio.NioServerSocketChannel;import io.netty.handler.codec.LineBasedFrameDecoder;import io.netty.handler.codec.string.StringDecoder;import io.netty.handler.codec.string.StringEncoder;import io.netty.util.CharsetUtil;public class LineNettyServer {
private final int port;public LineNettyServer { this.port = port;老实说,}
public void start throws InterruptedException {
EventLoopGroup bossGroup = new NioEventLoopGroup;EventLoopGroup workerGroup = new NioEventLoopGroup;try {
ServerBootstrap bootstrap = new ServerBootstrap;bootstrap.group
.channel
.option
.childOption
.childOption
.childHandler {
@Override
protected void initChannel {
ch.pipeline
.addLast)
.addLast)
.addLast)
.addLast);}
}),ChannelFuture future = bootstrap.bind.sync;System.out.println;future.channel.closeFuture.sync;} finally {
bossGroup.shutdownGracefully;workerGroup.shutdownGracefully;}
}
public static void main throws InterruptedException {
new LineNettyServer.start;}
}
package com.example.netty.line;import io.netty.channel.ChannelHandlerContext;import io.netty.channel.SimpleChannelInboundHandler;其实,public class LineClientHandler extends SimpleChannelInboundHandler {
@Override
public void channelActive {
ctx.writeAndFlush;}
@Override
protected void channelRead0 {
System.out.println);其实,ctx.close;}
@Override
public void exceptionCaught {
cause.printStackTrace;ctx.close,}
}
客户端启动类
package com.example.netty.line;import io.netty.bootstrap.Bootstrap;import io.netty.channel.ChannelFuture;import io.netty.channel.ChannelInitializer;import io.netty.channel.EventLoopGroup;按理说,import io.netty.channel.nio.NioEventLoopGroup;import io.netty.channel.socket.SocketChannel;import io.netty.channel.socket.nio.NioSocketChannel;老实说,import io.netty.handler.codec.LineBasedFrameDecoder;import io.netty.handler.codec.string.StringDecoder;import io.netty.handler.codec.string.StringEncoder;import io.reactivex.util.CharsetUtil;public class LineNettyClient {
private final String host;
private final int port;,// constructor omitted for brevity
public void start throws InterruptedException {
EventLoopGroup group = new NioEventLoopGroup;try {
Bootstrap bootstrap = new Bootstrap;bootstrap.group
.channel
.handler {
@Override
protected void initChannel {
ch.pipeline
.addLast)
.addLast)
.addLast)
.addLast);}
}),ChannelFuture future = bootstrap.connect.sync;future.channel.closeFuture.sync;} finally { group.shutdownGracefully;}
}
public static void main throws InterruptedException { …其实,}
}
}
*运行顺序*
-
先启动 `LineNettyServer`** 再启动 **`LineNettyClient`**。
-
`LineBasedFrameDecoder + StringCodec+LineServerHandler`: 完整地演示了从字节流到字符串再到业务层的完整链路。
-
*使用者痛点*:如果忘记添加 **decoder** 或 **encoder** 会导致 “乱码” 或 “消息不完整”,调试成本急剧上升。
\
\
\
\
\
\
\
\
\
\
## 粘包与半包概念及方法
*使用者痛点*
: 同一 TCP 链路会出现数据被“粘”在一起或被“拆”成碎片,导致业务层无法直接读取完整报文。text
发送两条消息的观点是。“hello
world
”
接收方可能看到:
1. “hello
world
”
2. “hello
wor”
“ld
”
### 常用解码器对比表
\
| 方案 | \
对应 Netty 解码器 | \
适合场景 | \
<\/tr>\<\/ad>\
\
\
| 按换行切分<\/td>\
| {@link LineBasedFrameDecoder} <\/td>\
| 命令行协议/简单文本协议<\/TD>\
<\/TR>\
\ |
\| 按分隔符切分<\/TD>\ | {@link DelimiterBasedFrameDecoder} <\/TD>\ | 自定义分隔符文本协议<\/TD>\<\/TR>\\ |
\| 固定长度\<\/TD>\ | {@link FixedLengthFrameDecoder} <\/TD>\ | 每条消息长度固定\<\/TD>\<\/TR>\\ |
\| 长度字段\<\/TD>\ | {@link LengthFieldBasedFrameDecoder} <\/TD>\ | RPC、自定义二进制协议\<\/TD>\<\/TR>\<\/TBODY>\<\/TABLE>
### 长度字段协议 Demo
#### 协议约定
+----------------+----------------+
| 消息体长度| 消息体内容 |
+----------------+----------------+
*长度字段只表示后面的 payload 大小,不包括自身。*
#### Server Pipeline 配置
java
ch.pipeline
.addLast(new LengthFieldBasedFrameDecoder(
Integer.MAX_VALUE,// maxFrameLength
0。// lengthFieldOffset
4,// lengthFieldLength
0,// lengthAdjustment
4)) // initialBytesToStrip
.addLast)
.addLast)
.addLast)
.addLast);#### Server Handler
java
package com.example.nettoy.length;public class LengthFieldServerHandler extends SimpleChannelInboundHandler{
@Override
protected void channelRead0{
System.out.println;ctx.writeAndFlush;}
}
#### 完整 Server 启动代码
java
public static void mainthrows Exception{
EventLoopGroup boss= new NioEventLoopGroup;EventLoopGrou p work= new NioEventLoopGrou p;ServerBootstra p sb=new ServerBootstra p;sb.group
... // 同前面例子。仅替换 pipeline 配置为 Length‑field 系列
}
### 小结
* **长度字段方案是生产环境最常用的方式**——能够灵活携带类型标识位或请求 ID,实现可靠的 RPC 与设备通信。* **务必关注参数含义**;错误配置会导致 “frame too large” 或 “截断错误”。---
## ByteBuf 详细说明 & 内存泄漏防护
### ByteBuf vs ByteBuffer
|
特性 | | ByteBuffer | ByteBuf |
|
| 读写指针独立 | 同一 position。需要 flip
易出错 | readerIndex / writerIndex 分离 |
| 扩容方式 | 手动 allocate 并复制
无池化 | 自动扩容+池化
支持 CompositeBuffer |
| 引用计数 | 无 | 有,可手动 release/th>…<\/tbody>
<\/table>
### 基本读写示例
java
ByteBuf buf = Unpooled.buffer;buf.writeInt;// writerIndex 前移
int v = buf.readInt;// readerIndex 前移 → v==42
buf.release;// 若是池化对象必须调用
### 手动释放注意事项
* 当你继承 **`\> ChannelInboundHandlerAdapter"` 并直接操作 `ByteBuf`,必须在 **finally 块里调用 `ReferenceCountUtil.release`**。* 使用 **`\> SimpleChannelIn‑boundHandler`** 时框架会自动释放,无需额外代码。其实,---
## 异步结果 —— ChannelFuture 使用教程
java
ctx.writeAndFlush
.addListener(future -> {
if ){
System.out.println;}else{
future.cause.printStackTrace;}
}),*记住*: 所有 I/O 操作都是非阻塞的,只有当 `future.isSuccess` 为 true 时才说明数据已成功写入底层 socket。若要在发送完毕后立即关闭通道,请使用:
java
ctx.writeAndFlush
.addListener;---
## 心跳 & 空闲检测
### IdleStateHandler 示例
java
ch.pipeline
.addLast) // readIdle=60s,writeIdle=30s
.addLast);### 心跳业务实现
java
public class HeartbeatServerHandler extends ChannеlInboundHandlerAdapter{
@Overide publi c vo i d us erEve ntTrigge red{
ife).state==IdleState.READER_IDLE){
System.out.println.remoteAdd ress);ctx.close,}else{
ct x.fireUserEv entTriggered;}
}
}
*使用者痛点*: 没有心跳检测会导致“僵尸连接”占满程序 FD;使用上述方案即可实时清理。---
## 耗时业务如何脱离 EventLoop
java
public class BusinessHander extends SimpleChanne lInboundHan dler{
private final ExecutorService pool = Executors.newFixedThreadPool.availableProcessors);@Overr ide protected void channelRead0{
pool.submit -> {
// 模拟慢任务,如 DB 查询…String result = heavyBiz;// 回到当前 EventLoop 写回结果,保持顺序与线程安全。ct x.executor.execute-> ct x.writeAndFlush);}),}
}
---
## HTTP Server 示例
### Pipeline 配置
java
ch.pipeline
.addLast)
.addLast)
.addLast);### Handler 示例
java
public class SimpleHttpServerHandler extends SimpleChanne lInboundHаndlerFullHttpRеquest>{
@Ovеrride protecte dvoid chan ne lRead0{
ByteBu f cont ent = Unpooled.copiedBuffer;FullHttpResponse resp=new DefaultFullHttpResponse(HttpVersion.HTTP_1_1。HttpResponseStatus.OK,cont ent);resp.headers.set;resp.headers.set);ct x.writeAndFlush.a ddListener;老实说,}
}
* *使用者痛点*: 对于大量短请求。如果仍然走 Spring MVC,会产生额外的 servlet 容器开销;而 Nettty 的纯裸 HTTP 实现拥有更低延迟、更小内存使用,非常适合边缘网关或代理层。话说回来,*
---
## Spring Boot 集成 Nettty 生命周期管理
java
@Component
public class NetttyServi ceLifecycle{
private EventLo o pGro up boss;不过,private Even tL oo pGro up wor ker;private C ha nnel serverCh annel;@PostConstruct public voi d start throws Interrup tedExcep tion{
boss=new N i oEve ntLo opG roup;wor ker=new N i oEv e nt Lo opGr ou p;ServerBoot strap sb=new Serv erBoot strap;sb.group
,其实,// pipeline 同前例子。仅演示注入 Spring Bean 可共享配置文件等。serverChan nel=sb.bind.sync.channel;System.o ut.println;}
@PreDestroy pu blic vo i d stop{
if{ serverChan nel.close;老实说,}
wor ker.shutdownGracefully;boss.shutdownGraceful ly;}
}
---
## 常见 ChannelOption 配置教程
方法 | /说明 | / tr>/
/option// 对应 ServerChann el /
/childOption// 对应已 accept 的 client Chann el/
...
/SO_BACKLOG// accept 队列大小默认100~200/
/SO_KEEPALIVE// 心跳保活
/TCP_NODELAY// 禁用 Nagle 算法降低小报文延迟/
...
<\/tbody>
<\/table>
---
## Handler 可共享性原则
* 标记 **@ChannelHandler.Sharable** 的 Handler 必须是 **无状态** 的——如日志打印或通用解码器。
* 如果内部持有 **Connection‑level state**, 则 **不要共享**;否则会产生数据错位。
示例:
java
@C hannelHandler.Sharable
public clas s Logg ingTextHa ndler exte nds Sim pleChan nelI nbou ndHan dle r{
…}
---
## 场景适配矩阵
S cenario/th/> | I s it e?怎么说呢,th/> |
a Custom TCP Protocol/td>/true/td/>
D ata‑intensive REST API/th>/ | false/th/>
…...
⚠️ 使用者痛点提醒:如果仅仅是几百 QPS 的 CRUD 接口,用 Spring MVC 能省去大量底层调优工作;而如果是千万 QPS 的长连推送,则必须选 Nettty!
... |
<\/table>
---
## 常见问题速查
Pain Point/th/> | S olution/th/> |
| 服务端收不到完整报文/th>/ | 检查解码器是否匹配报文边界,如使用 LengthFieldBasedFrameDecod er 或 LineBasedFram eDe coder / 确认 maxFrameLength 足够大/th/>
… | handler 中出现慢任务/th>/ | 把耗时操作投递至业务 ThreadPool,并在完成后回到 event loop 写响应 — 参考 “耗时业务如何脱离 EventL oop” 示例/th/>
… | 内存一直增加/ByteBuf 泄漏/th>/ | 开启 LeakDetection 并确保每个非‑SimpleChan ne Inbound handler 手动 release / 使用 SimpleChan neInb oundH andler 自动释放 / 避免在 pipeline 中随意 fireUserEventTriggered 后忘记 release / 确保 decoder 不返回过大的对象 …/
, | write 后立即 close 导致消息未送达/th>/ | 使用 `writeAndFlush.addListener` 确保写完再关闭/channel/
…⚠️ 请务必在所有入口统一捕获异常并记录日志,以免因异常导致 EventLo op 卡死!
...
实践建议 Checklist
检查项/th/> | 建议值或做法/th/> |
| Maven 引入方式/th>/ | 开发阶段 nettx‑all;不过,生产环境按需模块化。引入 transport+codec+handler 等 / 若已有 Spring Cloud Gateway 则让上层框架统一管理版本 /… | 协议设计/th>/ | 先定义明确边界,再挑选对应 Decoder / 避免后期“粘包难以定位”问题 /
… | 长连心跳/ph>/
…
…老实说,
...etc...
小结
The core of Nettty is an asynchronous event‑driven model:
-
EventLoopGroup: 管理 I/O 与任务调度线程池。
-
Channel: 抽象一次 TCP/UDP 会话。