深入理解 JSON-RPC 2.0:轻量级远程调用协议详解
在构建客户端与服务端分离的系统时(比如 CLI 与核心进程通信、微服务之间的调用),我们经常需要一种简单、清晰的远程过程调用(RPC)协议。JSON-RPC 2.0 正是为此而生。它用 JSON 作为数据格式,设计极简,却足够强大,被广泛应用于语言服务器协议(LSP)、区块链节点通信、以及各种内部工具架构中。
本文将从协议背景、核心概念、消息格式、错误处理到实际应用,全面介绍 JSON-RPC 2.0。
1. 什么是 JSON-RPC 2.0?
JSON-RPC 是一种轻量级的远程过程调用协议。它允许客户端向服务端发送「调用某个方法并携带参数」的请求,服务端执行后返回结果或错误。
2.0 是当前主流版本,由 JSON-RPC 工作组在 2010 年左右正式定稿。相比 1.0,2.0 明确了版本字段、统一了错误结构、支持通知(Notification)和批量请求,并去掉了一些历史包袱。
它的核心特点可以概括为三点:
- 轻量:消息本身就是普通 JSON,几乎没有额外开销。
- 传输无关:不绑定 HTTP、WebSocket 或任何特定传输层。
- 语言无关:只要能处理 JSON,任何语言都能实现客户端或服务端。
2. 设计哲学:简单即强大
JSON-RPC 的设计刻意保持克制。它不试图解决路由、认证、流式传输、服务发现等复杂问题,而是把这些留给上层或传输层去处理。
协议只关心一件事:如何用结构化的 JSON 消息表达一次远程函数调用。
这种「只做一件事并做好」的哲学,让它非常适合作为内部通信协议。很多现代系统(比如 VS Code 的 Language Server Protocol)都选择在 JSON-RPC 之上构建更复杂的功能。
3. 三种核心消息类型
JSON-RPC 2.0 定义了三种消息:
3.1 Request(请求)
客户端发起方法调用时使用。必须包含以下字段:
| 字段 | 类型 | 说明 |
|---|---|---|
jsonrpc |
string | 必须为 "2.0" |
method |
string | 要调用的方法名 |
params |
object / array | 参数(可选) |
id |
string / number / null | 请求标识,用于匹配响应 |
示例:
json
{
"jsonrpc": "2.0",
"method": "execute_task",
"params": {
"task_id": "t-001",
"prompt": "写一个快速排序"
},
"id": 1
}
params 既可以是命名参数(对象),也可以是位置参数(数组),服务端需要约定好使用哪种风格。
3.2 Response(响应)
服务端处理完请求后返回。成功和失败的结构不同:
成功响应:
json
{
"jsonrpc": "2.0",
"result": {
"output": "def quick_sort(arr): ...",
"status": "success"
},
"id": 1
}
错误响应:
json
{
"jsonrpc": "2.0",
"error": {
"code": -32602,
"message": "Invalid params",
"data": "task_id is required"
},
"id": 1
}
注意:result 和 error 字段互斥 ,不能同时出现。id 必须与请求中的 id 保持一致,这样才能让客户端正确匹配异步返回的结果。
3.3 Notification(通知)
不需要服务端返回结果的请求。它和普通 Request 几乎一样,但没有 id 字段。
json
{
"jsonrpc": "2.0",
"method": "log",
"params": {
"level": "info",
"message": "任务已开始执行"
}
}
服务端收到 Notification 后直接处理,不返回任何响应。这种机制非常适合事件推送、日志上报、状态同步等场景。
4. 错误处理机制
JSON-RPC 2.0 定义了一套标准错误码,方便客户端统一处理:
| 错误码 | 含义 | 说明 |
|---|---|---|
| -32700 | Parse error | 服务端收到的不是合法 JSON |
| -32600 | Invalid Request | JSON 合法,但不是合法的 Request 对象 |
| -32601 | Method not found | 方法不存在 |
| -32602 | Invalid params | 参数无效 |
| -32603 | Internal error | 服务端内部错误 |
| -32000 到 -32099 | Server error | 预留给服务端自定义错误 |
错误对象结构固定:
json
{
"code": -32602,
"message": "Invalid params",
"data": "可选的额外信息,可以是任意 JSON 值"
}
data 字段是可选的,常用于传递更详细的调试信息或业务错误细节。
5. 批量请求(Batch)
JSON-RPC 2.0 支持一次发送多个请求。客户端只需把多个 Request 对象放进一个 JSON 数组即可:
json
[
{
"jsonrpc": "2.0",
"method": "add",
"params": [1, 2],
"id": 1
},
{
"jsonrpc": "2.0",
"method": "subtract",
"params": [10, 3],
"id": 2
},
{
"jsonrpc": "2.0",
"method": "log",
"params": {"message": "批量调用完成"}
}
]
服务端会返回对应的响应数组(Notification 不会出现在响应中)。批量请求能有效减少网络往返次数,适合需要同时执行多个独立操作的场景。
6. 传输层无关性
JSON-RPC 最大的优势之一是不绑定任何传输协议。常见的承载方式包括:
- stdin / stdout:非常适合 CLI 与本地守护进程通信
- HTTP:用 POST 发送 JSON 消息,简单直接
- WebSocket:适合需要双向通信和服务器主动推送的场景
- Unix Domain Socket / TCP:高性能本地或远程通信
- 消息队列:作为消息体在 RabbitMQ、NATS 等系统中传递
正是因为这种解耦,同一个 JSON-RPC 服务可以同时支持多种接入方式,而业务逻辑完全不需要修改。
7. 与其他协议的对比
| 特性 | JSON-RPC 2.0 | REST | gRPC | XML-RPC |
|---|---|---|---|---|
| 数据格式 | JSON | JSON / 其他 | Protobuf | XML |
| 调用风格 | 方法调用 | 资源 + HTTP 动词 | 方法调用 | 方法调用 |
| 性能 | 中等 | 中等 | 高 | 较低 |
| 可读性 | 高 | 高 | 低(二进制) | 中等 |
| 流式支持 | 需自行实现 | 有限 | 原生支持 | 无 |
| 学习成本 | 低 | 中 | 中高 | 低 |
| 典型场景 | 内部工具、LSP、节点通信 | 公开 API | 高性能微服务 | 老旧系统 |
JSON-RPC 在「简单」和「表达力」之间取得了很好的平衡。它不像 REST 那样强调资源建模,也不像 gRPC 那样追求极致性能和强类型,而是专注于「远程函数调用」这件事本身。
8. 实际应用场景
- 编辑器与语言服务器:VS Code、Neovim 等通过 Language Server Protocol(基于 JSON-RPC)与各种语言服务器通信。
- 区块链节点:以太坊等很多节点使用 JSON-RPC 对外提供接口。
- CLI 与核心进程:本地工具把核心逻辑做成常驻进程,CLI 通过 JSON-RPC 调用,实现状态共享和快速启动。
- 插件系统:主程序与插件之间用 JSON-RPC 通信,降低耦合。
- 微服务内部调用:在对性能要求不极端的场景下,用 JSON-RPC 替代更重的框架。
9. 实现时的注意事项
-
必须严格校验
jsonrpc字段只接受
"2.0",拒绝其他值。 -
id的处理要小心- 请求有
id→ 必须返回对应响应 - 请求没有
id→ 当作 Notification,禁止返回响应 - 响应的
id必须与请求一致
- 请求有
-
错误不能包含
result,成功不能包含error两者互斥。
-
批量请求中的顺序
响应数组的顺序应与请求数组保持一致(虽然规范没有强制,但实际实现中最好保持)。
-
大参数与流式数据
JSON-RPC 本身不适合传输超大二进制数据或真正的流式场景,这时通常需要配合其他机制(如先返回一个资源 ID,再通过其他通道传输数据)。
-
安全
协议本身不提供认证和加密,这些必须由传输层或上层协议解决。
10. 总结
JSON-RPC 2.0 是一个设计克制、实用主义的远程调用协议。它用最简单的 JSON 消息完成了「远程函数调用」这一核心需求,同时保持了极高的可读性和实现灵活性。
它的价值不在于功能有多么丰富,而在于边界清晰。当你需要一个轻量、易调试、不绑定特定传输方式的内部通信协议时,JSON-RPC 2.0 往往是非常合适的选择。
在实际项目中,它可以作为通信的「第一层」,在此之上再构建任务管理、事件通知、流式输出等更高级的能力。理解它的设计取舍,有助于我们在架构决策时做出更清醒的判断。
后记
2026年10月29日于上海,在grok辅助下完成。