2026年7月11日 · 5 分钟阅读
Netty 实战:聊天室、Protobuf 编解码与手写 RPC 框架
基于网络编程课程笔记,梳理 Netty 客户端服务端通信、聊天室、Protobuf 编解码、RPC 原理、Client Stub、Server Stub 和动态代理。
学 Netty 不能只停在 API 名词上。真正理解它,要把连接、编解码、Handler、业务调用串起来。聊天室和 RPC 是两个很适合练手的场景:一个偏长连接消息转发,一个偏请求响应和远程调用封装。
这篇从 Netty 通信开始,逐步走到手写 RPC 框架。
Netty 客户端和服务端通信
一个基础 Netty 服务端通常包含三部分:
- 配置 BossGroup 和 WorkerGroup。
- 配置服务端 Channel 和 Handler。
- 绑定端口并启动。
客户端也类似:
- 配置 EventLoopGroup。
- 配置客户端 Channel 和 Handler。
- 连接服务端。
服务端 Handler 常见方法:
channelActive():连接建立。channelRead():读取客户端数据。exceptionCaught():异常处理。
客户端 Handler 常见方法:
channelActive():连接成功后发送消息。channelRead():接收服务端响应。
聊天室案例的核心
聊天室的核心不是 UI,而是服务端如何管理多个客户端连接。
一个简化流程:
- 客户端连接服务端。
- 服务端保存客户端 Channel。
- 某个客户端发送消息。
- 服务端收到消息后遍历其他 Channel。
- 服务端把消息广播给其他客户端。
Netty 的 ChannelGroup 很适合管理多个 Channel。
聊天室案例能帮助理解:
- 长连接。
- Channel 生命周期。
- 消息广播。
- Handler 事件传播。
- 编码和解码。
为什么需要编解码
网络传输的是二进制字节,而业务代码希望处理对象。
所以必须有两步:
编码:业务对象 -> 字节
解码:字节 -> 业务对象
如果没有清晰的协议和编解码,服务端收到的只是一段字节数组,不知道它代表登录消息、聊天消息还是 RPC 请求。
Java 序列化的问题
Java 原生序列化使用方便,但不适合高性能网络通信。
主要问题:
- 不能很好跨语言。
- 序列化后体积较大。
- 性能较低。
- 版本兼容和安全问题较多。
所以生产级 RPC 框架通常不会直接依赖 Java 原生序列化,而会使用 Protobuf、Hessian、Kryo、JSON、自定义二进制协议等。
Protobuf 的价值
Protobuf 是 Google 开源的序列化协议,适合网络通信。
它的特点:
- 序列化体积小。
- 性能较好。
- 支持跨语言。
- 通过
.proto文件定义消息结构。 - 生成强类型代码。
使用流程大致是:
- 编写
.proto文件。 - 生成 Java 类。
- Netty Pipeline 中配置 Protobuf 编解码器。
- 客户端发送 Protobuf 对象。
- 服务端接收并处理对象。
RPC 是什么
RPC 全称 Remote Procedure Call,远程过程调用。
它的目标是:让调用远程服务像调用本地方法一样简单。
例如业务代码希望这样调用:
SkuService skuService = HeroRPCProxy.create(SkuService.class);
Sku sku = skuService.findById(1001L);
调用方不想关心底层是 Socket、NIO 还是 Netty,也不想手动编码请求和解码响应。
RPC 调用链路
一次 RPC 调用大致包含这些步骤:
- 服务消费方以本地方法方式调用接口。
- Client Stub 拦截调用,封装类名、方法名、参数类型、参数值。
- Client Stub 对请求消息编码。
- 客户端通过网络发送请求。
- Server Stub 接收并解码请求。
- Server Stub 根据请求找到本地服务实现。
- 本地服务执行方法。
- Server Stub 编码返回结果。
- Client Stub 接收响应并解码。
- 服务消费方得到返回值。
RPC 框架的价值,就是把第 2 到第 9 步封装起来。
RPC 请求消息应该包含什么
一个最小可用的 RPC 请求通常包含:
| 字段 | 作用 |
|---|---|
| 接口名 | 找到服务类型 |
| 方法名 | 找到要执行的方法 |
| 参数类型 | 支持方法重载 |
| 参数值 | 实际调用参数 |
| 请求 ID | 匹配请求和响应 |
响应消息通常包含:
- 请求 ID。
- 返回值。
- 异常信息。
- 状态码。
Client Stub
Client Stub 负责把本地方法调用变成网络请求。
常见实现方式是动态代理:
Proxy.newProxyInstance(
interfaceClass.getClassLoader(),
new Class[]{interfaceClass},
(proxy, method, args) -> {
// build request
// send request
// wait response
// return result
}
);
业务方拿到的是接口代理对象,实际执行时由代理对象发送 RPC 请求。
Server Stub
Server Stub 负责接收网络请求,并把请求转换成本地方法调用。
核心步骤:
- 解码 RPC 请求。
- 根据接口名找到服务实现对象。
- 根据方法名和参数类型找到 Method。
- 通过反射调用本地方法。
- 把返回值或异常封装为响应。
- 编码并写回客户端。
这就是 RPC 框架服务端最小闭环。
RPC 框架还缺什么
手写 Demo 能跑通调用链路,但生产级 RPC 框架还需要很多能力:
- 服务注册与发现。
- 负载均衡。
- 超时控制。
- 重试策略。
- 熔断降级。
- 连接池。
- 心跳检测。
- 序列化协议选择。
- 线程池隔离。
- 调用链追踪。
Demo 解决的是“远程调用如何发生”,生产框架解决的是“远程调用如何稳定发生”。
小结
Netty 实战可以按三层理解:
- 通信层:客户端和服务端如何连接、读写、关闭。
- 协议层:消息如何编码、解码、区分边界。
- 调用层:如何把网络消息映射成本地方法调用。
聊天室练的是长连接和消息广播;Protobuf 练的是编解码;RPC 练的是把网络通信封装成服务调用。