2026年7月11日 · 5 分钟阅读

Netty 实战:聊天室、Protobuf 编解码与手写 RPC 框架

基于网络编程课程笔记,梳理 Netty 客户端服务端通信、聊天室、Protobuf 编解码、RPC 原理、Client Stub、Server Stub 和动态代理。

学 Netty 不能只停在 API 名词上。真正理解它,要把连接、编解码、Handler、业务调用串起来。聊天室和 RPC 是两个很适合练手的场景:一个偏长连接消息转发,一个偏请求响应和远程调用封装。

这篇从 Netty 通信开始,逐步走到手写 RPC 框架。

Netty 客户端和服务端通信

一个基础 Netty 服务端通常包含三部分:

  1. 配置 BossGroup 和 WorkerGroup。
  2. 配置服务端 Channel 和 Handler。
  3. 绑定端口并启动。

客户端也类似:

  1. 配置 EventLoopGroup。
  2. 配置客户端 Channel 和 Handler。
  3. 连接服务端。

服务端 Handler 常见方法:

  • channelActive():连接建立。
  • channelRead():读取客户端数据。
  • exceptionCaught():异常处理。

客户端 Handler 常见方法:

  • channelActive():连接成功后发送消息。
  • channelRead():接收服务端响应。

聊天室案例的核心

聊天室的核心不是 UI,而是服务端如何管理多个客户端连接。

一个简化流程:

  1. 客户端连接服务端。
  2. 服务端保存客户端 Channel。
  3. 某个客户端发送消息。
  4. 服务端收到消息后遍历其他 Channel。
  5. 服务端把消息广播给其他客户端。

Netty 的 ChannelGroup 很适合管理多个 Channel。

聊天室案例能帮助理解:

  • 长连接。
  • Channel 生命周期。
  • 消息广播。
  • Handler 事件传播。
  • 编码和解码。

为什么需要编解码

网络传输的是二进制字节,而业务代码希望处理对象。

所以必须有两步:

编码:业务对象 -> 字节
解码:字节 -> 业务对象

如果没有清晰的协议和编解码,服务端收到的只是一段字节数组,不知道它代表登录消息、聊天消息还是 RPC 请求。

Java 序列化的问题

Java 原生序列化使用方便,但不适合高性能网络通信。

主要问题:

  • 不能很好跨语言。
  • 序列化后体积较大。
  • 性能较低。
  • 版本兼容和安全问题较多。

所以生产级 RPC 框架通常不会直接依赖 Java 原生序列化,而会使用 Protobuf、Hessian、Kryo、JSON、自定义二进制协议等。

Protobuf 的价值

Protobuf 是 Google 开源的序列化协议,适合网络通信。

它的特点:

  • 序列化体积小。
  • 性能较好。
  • 支持跨语言。
  • 通过 .proto 文件定义消息结构。
  • 生成强类型代码。

使用流程大致是:

  1. 编写 .proto 文件。
  2. 生成 Java 类。
  3. Netty Pipeline 中配置 Protobuf 编解码器。
  4. 客户端发送 Protobuf 对象。
  5. 服务端接收并处理对象。

RPC 是什么

RPC 全称 Remote Procedure Call,远程过程调用。

它的目标是:让调用远程服务像调用本地方法一样简单。

例如业务代码希望这样调用:

SkuService skuService = HeroRPCProxy.create(SkuService.class);
Sku sku = skuService.findById(1001L);

调用方不想关心底层是 Socket、NIO 还是 Netty,也不想手动编码请求和解码响应。

RPC 调用链路

一次 RPC 调用大致包含这些步骤:

  1. 服务消费方以本地方法方式调用接口。
  2. Client Stub 拦截调用,封装类名、方法名、参数类型、参数值。
  3. Client Stub 对请求消息编码。
  4. 客户端通过网络发送请求。
  5. Server Stub 接收并解码请求。
  6. Server Stub 根据请求找到本地服务实现。
  7. 本地服务执行方法。
  8. Server Stub 编码返回结果。
  9. Client Stub 接收响应并解码。
  10. 服务消费方得到返回值。

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 负责接收网络请求,并把请求转换成本地方法调用。

核心步骤:

  1. 解码 RPC 请求。
  2. 根据接口名找到服务实现对象。
  3. 根据方法名和参数类型找到 Method。
  4. 通过反射调用本地方法。
  5. 把返回值或异常封装为响应。
  6. 编码并写回客户端。

这就是 RPC 框架服务端最小闭环。

RPC 框架还缺什么

手写 Demo 能跑通调用链路,但生产级 RPC 框架还需要很多能力:

  • 服务注册与发现。
  • 负载均衡。
  • 超时控制。
  • 重试策略。
  • 熔断降级。
  • 连接池。
  • 心跳检测。
  • 序列化协议选择。
  • 线程池隔离。
  • 调用链追踪。

Demo 解决的是“远程调用如何发生”,生产框架解决的是“远程调用如何稳定发生”。

小结

Netty 实战可以按三层理解:

  1. 通信层:客户端和服务端如何连接、读写、关闭。
  2. 协议层:消息如何编码、解码、区分边界。
  3. 调用层:如何把网络消息映射成本地方法调用。

聊天室练的是长连接和消息广播;Protobuf 练的是编解码;RPC 练的是把网络通信封装成服务调用。