核心是用Selector+Channel替代阻塞Socket实现单线程高并发,配合魔数/长度/类型/序列化方式组成的自定义协议帧,解决粘包与半包问题;服务端基于事件驱动处理多连接,客户端通过requestId与Promise关联实现异步发送、同步等待响应。

Java 基于 NIO 封装简易 RPC 网络通信框架,核心是用 Selector + Channel 替代传统阻塞 Socket,实现单线程高并发通信,同时配合自定义协议与序列化完成请求/响应闭环。它不依赖 Netty,但保留了异步、非阻塞、事件驱动的关键特征。
设计通信层基础结构
RPC 的网络层需承载“方法调用意图”和“执行结果”,不能只传原始字节。因此要先定义轻量协议头:
- 魔数(2 字节):标识这是本框架的合法包,如
0xCAFE - 数据长度(4 字节):后续 payload 的字节数,用于粘包处理
- 消息类型(1 字节):0=请求,1=响应,2=心跳
- 序列化方式(1 字节):0=JDK,1=JSON(便于调试)
- payload:紧接头部之后,是序列化后的
RpcRequest或RpcResponse
所有通信都围绕这个二进制帧展开,服务端读取时先解析头,再按长度读取完整 body,避免半包/粘包问题。
服务端用 Selector 处理多连接
服务端启动一个 ServerSocketChannel,注册到 Selector 监听 OP_ACCEPT;一旦有新连接,就将其 SocketChannel 设为非阻塞,并注册 OP_READ。
立即学习“Java免费学习笔记(深入)”;
- 每个连接分配一个
ByteBuffer作为读缓冲区(建议 4KB~8KB),复用避免频繁 GC - 读事件触发后,循环
channel.read(buffer)直到返回 -1(断开)或 0(暂无数据) - 收到完整帧后,解析出
RpcRequest,通过反射调用本地服务实现类,封装RpcResponse写回通道 - 写操作使用
channel.write(buffer),若未一次写完,需注册OP_WRITE并暂存 buffer,待可写时继续
客户端异步发送 + 同步等待响应
客户端不阻塞等待 reply,而是用唯一请求 ID 关联请求与响应:
- 每次调用生成 UUID 作为
requestId,放入RpcRequest - 发送前将
requestId → DefaultPromise<RpcResponse>存入本地 ConcurrentHashMap - 连接建立后,
SocketChannel注册OP_READ;收到响应帧后,解析requestId查找对应 Promise,调用setSuccess(response) - 业务线程调用
promise.get(timeout, TimeUnit.SECONDS)阻塞获取结果,超时则抛异常
这样既保持 NIO 非阻塞优势,又对上层提供同步调用语义。
序列化与反序列化适配器
NIO 只管字节流,对象需靠序列化落地。简易版可用 JDK 原生(要求所有类实现 Serializable),但更推荐 JSON(如 Jackson):
- 定义
Serializer接口:byte[] serialize(Object obj)和<T> T deserialize(byte[] bytes, Class<T> clazz) - 在协议头中写入序列化类型字段,收包时动态选择实现类
- JDK 序列化简单但性能差、不跨语言;JSON 可读性强、兼容性好,适合开发调试阶段
注意:泛型参数在运行时擦除,RpcRequest 中的 parameterTypes 字段必须传 Class<?>[] 实例,不能只靠泛型推导。


















