
简介面向Java Web开发者的JSON-RPC入门案例包围绕轻量级远程调用协议的核心概念帮助读者理解客户端与服务端基于JSON格式的通信机制并掌握jsonrpc4j等库的实战集成方式。压缩包共35个文件以25个jar依赖为主辅以5个class类文件、2个xml配置、1个jsp入口页面、1个properties配置和1个mf清单整体18.86MB结构清晰。已有223人浏览学习。案例通过index.jsp演示请求构建WEB-INF下的类文件展示服务器端处理逻辑META-INF保留应用元数据并结合Spring、Jackson、jsonrpc4j等组件覆盖JSON-RPC 2.0的请求/响应结构、方法调用、异常处理等关键知识点。读者可借助该案例快速搭建Java Web环境体验从发起请求到解析响应的完整流程为在分布式服务或前后端交互中应用JSON-RPC打下基础。1. 拿到 jsonRPC.rar 之后先搞清楚它到底是什么经常有同学从网上下到或者从前辈手里拷到一个叫jsonRPC.rar的压缩包解压开发现里面躺着一个工程目录有client.py、server.py、protocol.py、tcp_transport.py、handler.py、README.md这种结构。第一反应往往是这玩意儿和我平时用的 HTTP API 有什么区别它到底解决了什么问题我直接拿过来能用吗这里我先说人话。JSON-RPC 是一种基于 JSON 格式的远程过程调用协议核心思想是“我这边调用一个函数但函数实际在另一台机器上执行”。它不像 REST 那样把资源拆成 GET/POST/PUT/DELETE而是通过一个统一的入口发送一条指令方法名 参数 请求 ID然后等对方返回“结果”或者“错误”。我用它写过几个内部工具和自动化脚本最大的感受是当你要做的不是资源管理而是“让远程机器执行某个操作”的时候JSON-RPC 比 REST 直观得多代码量也能砍掉一半以上。这份压缩包里封装的其实就是一套“开箱即用”的 JSON-RPC 2.0 实现覆盖了 TCP 长连接和 HTTP 短连接两种传输方式还顺手做了请求 ID 管理、超时控制、批量调用、错误码统一这些工程上绕不开的东西。适合谁适合正在写前后端联调接口、做微服务内部通信、搞设备控制或者写自动化测试平台的同学。你不需要掌握多少底层网络知识照着 README 把 client 模块 import 进去填上 IP 和端口就能跑通第一个调用。我接下来会把压缩包里的每个文件、每段关键逻辑、每个容易踩坑的点全拆开讲。不管你是想直接拿去用还是想参考它的设计思路自己造轮子这篇都够用。2. 压缩包内部模块拆解每一层都在解决什么问题2.1 工程结构与设计定位先看整体设计这份代码的目录规划是典型的“协议、传输、业务分离”jsonRPC/ ├── protocol.py # JSON-RPC 2.0 协议层请求/响应/错误对象 ├── server.py # 服务端方法注册、分发、调用 ├── client.py # 客户端同步/异步调用入口 ├── tcp_transport.py # TCP 传输层粘包处理、半包读取 ├── http_transport.py # HTTP 传输层requests 封装 ├── handler.py # 业务处理器基类 ├── exceptions.py # 自定义异常体系 ├── utils.py # ID 生成器、时间戳、参数校验 └── README.md它没有把代码堆在一个文件里而是把“协议格式”“底层传输”“业务处理”三层拆开。这样做的好处很实在如果你只写一个 Python 脚本跑通 RPC全部塞一个文件当然也行但一旦要加权限校验、日志、请求链路追踪拆分开的代码能让你在不影响传输层的前提下快速扩展。我在自己的项目里也沿用这个分层思路后面加个 WebSocket 传输层只要实现send()和receive()两个方法上层的 client/server 完全不用动。protocol.py是整份代码的核心它严格遵循 JSON-RPC 2.0 规范定义了请求对象和响应对象的结构# 请求对象结构示例实际代码为 dataclass class RPCRequest: jsonrpc: str 2.0 method: str params: object None id: int class RPCResponse: jsonrpc: str 2.0 result: object None error: dict None id: int注意一个细节请求必须有method和id通知请求可以没有id但必须有jsonrpc字段并固定为2.0。压缩包里的代码对 id 做了强制校验如果客户端传了 None服务端会抛InvalidRequestError。从协议设计的角度说id 的作用是让客户端能把响应和请求对应上尤其在批量调用时这个字段不够严谨会导致整套请求响应配对错乱。2.2 为什么选择“双传输层”而不是只做一种很多同学看到这肯定会问为什么 JSON-RPC 项目要同时支持 TCP 和 HTTP直接用一个不行吗这里的选择取决于使用场景。我的实际体会是TCP 传输适合客户端和服务端之间需要保持长连接、大量高频调用的场景比如设备指令下发、实时数据采集。此时每一条请求复用同一个连接避免了反复握手的开销性能会好很多。HTTP 传输更适合跨网络、跨防火墙、临时调用的场景比如第三方系统回调、脚本里偶尔调一次远程方法。HTTP 更容易穿透各种网络限制也更容易配合现有的负载均衡、日志中间件一起工作。压缩包里的tcp_transport.py处理了一个我在实践中觉得最讨厌的问题TCP 流式传输没有消息边界。它采用“4 字节头表示消息体长度”的方案每次收包先读 4 字节长度再按长度读取完整的 JSON 字符串。这个思路很经典和很多消息队列协议比如 RabbitMQ 的帧格式是一致的。你要在自己项目里实现长连接通信直接照抄这一套准没错。3. 协议层的核心机制请求、响应、通知与批量调用的闭环3.1 请求命名的设计意图JSON-RPC 2.0 规范里方法名使用类似模块名.方法名的点分格式比如user.getInfo、order.create。压缩包里的 server 端实现了一个 decorator让开发者可以这样注册方法from server import RPCServer server RPCServer() server.register(user.getInfo) def get_user_info(user_id: int): return db.query(user_id)它内部其实是一个字典self._method_map {user.getInfo: get_user_info}。收到请求后根据method字符串查字典找不到就返回“方法不存在”错误。用点分命名的好处有两个一是方便分组和管理二是在做权限控制的时候可以按前缀匹配。我在实际项目中就遇到过这样的需求所有带admin.前缀的方法只能允许机器 A 调用普通方法允许所有人调用。这种设计让权限策略的代码写起来极其直观。3.2 响应与错误码的统一约定这份代码里错误码不是随手瞎写的它遵循了 JSON-RPC 2.0 规范中预定义的错误码范围并额外补充了一些业务常用的错误码错误码含义压缩包内说明-32700解析错误收到的 JSON 格式不正确-32600无效请求请求对象结构不符合规范-32601方法不存在调用的 method 未注册-32602无效参数参数类型或数量不匹配-32603内部错误方法执行过程中抛出异常-32000 及以后服务端自定义业务错误业务逻辑层自行定义在实际开发中我最常踩的坑就是客户端把 HTTP 状态码和 RPC 错误码混为一谈。HTTP 200 只表示 HTTP 层面传输成功RPC 调用是否成功要看响应体里的error字段是否为 null。压缩包里的client.py做了这一层转换它会判断error字段有错误时抛出RPCError而不是让你自己去解析 JSON。这一点对调用方非常友好我在写自动化脚本时只需要try/except RPCError就能捕获到所有的远程调用异常。3.3 通知请求和批量调用的边界条件JSON-RPC 2.0 公认最实用但新手最容易搞混的写法有两种通知Notification和批量Batch。通知请求没有id字段服务端处理完不会返回任何响应。适合“发个日志”“触发一下重试任务”这种不需要知道结果的场景。批量请求把多个请求放进一个数组里一次性发送服务端也要返回一个数组作为响应。压缩包里这两块都做了。注意服务端在处理批量请求时如果其中一条请求格式错误不能直接整批返回错误而是要把可用的请求都处理掉再在结果数组里混合塞入错误对象。这个设计我在最初写的时候没注意结果客户端那边经常收到一个“神秘失败”的批量响应后来对照规范才发现是这个细节。4. 实操走一遍从解压到完成第一次远程调用4.1 快速启动跑通自带的回环测试压缩包 README 里给了最简启动方式实际跑起来只需要三步。先打开两个终端第一个终端启动服务端cd jsonRPC python examples/simple_server.py # 服务启动在 127.0.0.1:8000第二个终端运行客户端示例python examples/simple_client.py # 输出: 8我自己试的时候客户端默认调用math.add(3, 5)返回结果 8。整个过程非常短你不需要懂网络编程也能看到效果因为代码已经把 manual 的事情做完了。如果你不想依赖示例自己写最小调用也很直接from client import JSONRPCClient client JSONRPCClient(host127.0.0.1, port8000) result client.call(math.add, {a: 2, b: 3}) print(result) # 5关键点在于call方法内部做了三件事生成唯一请求 id、序列化请求对象、通过传输层发送并等待匹配 id 的响应。从使用者的角度看就像调用本地函数一样简单。4.2 超时、重试与连接池的参数选择参数设计是这种框架代码里最容易被忽视、但又是实际运行中最影响稳定性的部分。压缩包里的client.py给了几个关键参数client JSONRPCClient( host127.0.0.1, port8000, timeout10, # 单个请求超时时间单位秒 max_retries2, # 失败后重试次数 retry_interval0.5 # 重试间隔单位秒 )超时时间我建议按“最慢方法执行时间 网络往返时间 余量”来设定。比如你的业务方法在最坏情况下执行 3 秒网络 RTT 在 30ms 左右那超时设置为 5~6 秒是比较合理的。设太短稍微一抖动就大面积超时设太长客户端缓存一大堆 pending 请求故障恢复链路会变得非常迟钝。另外压缩包里的 TCP 客户端内置了连接池。若你的服务端有多个 worker 进程客户端会按轮询策略分配连接避免所有请求都挤在同一根连接上。这点在并发要求高的时候非常关键我见过太多人 TCP 长连接没做连接池高并发下一旦重启服务端一堆 ESTABLISHED 状态变成 TIME_WAIT。4.3 身份验证与参数校验的接入点压缩包里的 server 端留了一个before_request钩子可以让你插入鉴权逻辑。我在实际项目里是在这个钩子里解析请求头里带的一段 token和远端认证服务校验通过后才进入方法分发server RPCServer() server.before_request def auth_check(request): token request.meta.get(token, ) if not validate_token(token): raise AuthError(invalid token)另外参数校验我推荐在业务方法内部处理而不是在协议层全盘接管。JSON-RPC 本来就是一种很薄的信令协议把所有参数规则堆在协议层会让代码变得又臭又硬。用 Python 的话配合pydantic写个ValidationModel方法入口直接一行UserCreate(**params)就能完成绝大部分校验工作。5. 排错实战我在使用过程中遇到的 5 个典型问题5.1 服务端老是报“JSON 解析错误”但客户端明明发送的是合法 JSON这个问题我印象最深。排查过程非常曲折客户端打印出来的 payload 是正确的服务端却拿不到完整字符串。后来用 tcpdump 抓包才发现客户端在发送后立刻关闭了连接服务端只收到半截数据。根本原因是客户端发送后没有等待发送缓冲区 flush就直接关了 socket。解决办法分两层短连接场景客户端在send()之后要调用shutdown(SHUT_WR)告诉对方“数据发完了”再等响应。长连接场景客户端不能随意关连接只剩服务端有数据完整性的判断。压缩包里的tcp_transport.py是长连接模式它不会主动关闭所以理论上不该遇到这问题。如果你改造了源码自己写短连接一定要留意这个发送时序。5.2 批量请求返回结果顺序和请求顺序不一致JSON-RPC 2.0 规范明确说批量响应里的顺序可以任意排列客户端必须根据id去匹配而不能假设数组顺序。压缩包的客户端实现是老老实实按 id 匹配的所以没问题。但如果你自己在浏览器控制台调试直接Promise.all收发批量请求然后按数组 index 取结果可能会踩坑。正确做法是收到响应数组后先循环一遍建立id - result的字典再按需取用。5.3 响应内容出现中文乱码JSON 序列化失败这个在 Python 2 时代非常头疼Python 3 也有坑当json.dumps默认ensure_asciiTrue时会把中文转成\uXXXX序列TCP 传输本身没问题但日志和抓包工具里看就是一团乱码。你在打印调试或者落盘的时候手动转一下编码就行print(json.dumps(request, ensure_asciiFalse, indent2))5.4 服务端业务方法抛出异常后客户端拿到的是超时而非错误信息出现这个情况多半是服务端在dispatch的时候把异常吞掉了但没正确回填error字段。我在一个生产环境碰到过服务端业务代码里自己写了except Exception: pass导致 RPC 框架以为方法还在执行最终等到超时。排查方法很简单临时把服务端的异常处理改成打印 traceback看方法是否真的跑完。import traceback from exceptions import InternalError try: result method(**params) except Exception: traceback.print_exc() raise InternalError(str(e))5.5 TCP 粘包导致第二条请求解析失败TCP 粘包是经典问题压缩包用“4 字节长度头”已基本解决。但如果你自己改写了传输层要特别注意读 4 字节头时read 返回的数据可能不足 4 字节读 body 时也可能只读到 body 的一部分。必须循环读取直到读满为止代码里大概是def recv_all(sock, n): data b while len(data) n: chunk sock.recv(n - len(data)) if not chunk: raise ConnectionError(connection closed) data chunk return data别看这个函数简单我见过至少三次线上粘包问题就是因为recv一次没收满导致的。6. 踩坑后的几点心得和后续扩展建议这套 JSON-RPC 代码用下来的整体感受是协议简单工程不简单。我写自动化脚本、内部系统联动时使用 JSON-RPC 确实省掉了不少 REST 那种“建资源、改字段、查列表”的样板代码。但协议简单也意味着它不强加给你业务规范权限、限流、日志、监控、服务发现这些全要靠自己在框架外层补。在压缩包之外我认为接下来最值得做的扩展有三个方向异步化改造目前 client 是同步阻塞的如果同时要调几十个远程方法建议用 Python 的async/await或concurrent.futures把调用并发出去能降低一半以上的总耗时。集成注册发现服务端在多机部署后客户端直接用 IP 列表会越来越难维护可以接 ZooKeeper、Consul 或 etcd让客户端动态感知服务节点的上下线。请求链路追踪在before_request钩子里生成trace_id传给业务方法业务日志和 RPC 日志都带上这个字段排错成本会大幅降低。最后再说一个我自己的习惯凡是项目里引入这种“通用封装型”代码第一件事不是跑通示例而是把协议层的单元测试补上。JSON-RPC 的协议层足够简单测试用例覆盖好请求序列化、响应解析、错误码、批量调用这四块后面再改传输层、加鉴权逻辑心里都有底。压缩包里的代码我建议你至少跑一遍它的自测脚本再在这上面改出适合自己的版本。本文还有配套的精品资源点击获取