JSON-RPC协议
JSON-RPC 是一种轻量级的远程过程调用(RPC)协议,用 JSON 作为数据编码格式。它的核心思想很简单:让客户端可以像调用本地函数一样调用远程服务器上的方法,而消息载荷是一段结构化的 JSON。
一、本质
RPC = Remote Procedure Call。客户端发一段消息说"请帮我执行 subtract(42, 23)",服务端执行后把结果回传。JSON-RPC 就是用 JSON 来描述这段"调用请求"和"结果响应"的协议规范。它与传输层无关——可以跑在 HTTP、WebSocket、TCP、stdio(进程管道)甚至 Unix socket 上,协议本身只规定消息格式。
目前主流是 JSON-RPC 2.0(2010 年发布),相比 1.0 增加了批量调用、命名参数、明确的错误码等。
二、消息格式
- 请求(Request)
{
"jsonrpc": "2.0",
"method": "subtract",
"params": [42, 23],
"id": 1
}
四个字段:
jsonrpc:固定 “2.0”,标识协议版本
method:要调用的方法名(字符串)
params:参数,可以是数组(按位置)或对象(按名字,如 {“minuend”: 42, “subtrahend”: 23})
id:请求标识符,服务端必须在响应中原样返回,用于客户端把响应对应到请求
2. 成功响应
{ "jsonrpc": "2.0", "result": 19, "id": 1 }
- 错误响应
{
"jsonrpc": "2.0",
"error": { "code": -32601, "message": "Method not found" },
"id": 1
}
result 与 error 必须二选一。错误码有一组保留范围:-32700 解析错误、-32600 无效请求、-32601 方法不存在、-32602 参数无效、-32603 内部错误、-32000~-32099 服务端自定义。
- 通知(Notification)
{ "jsonrpc": "2.0", "method": "log", "params": ["hello"] }
不带 id 的请求叫通知,服务端不需要响应。常用于"只发不收"的事件推送。
- 批量调用(Batch)
把多个请求装进一个数组一次发出,服务端返回对应数组:
[
{ "jsonrpc": "2.0", "method": "sum", "params": [1,2,4], "id": 1 },
{ "jsonrpc": "2.0", "method": "subtract", "params": [42,23], "id": 2 }
]
三、与其他 RPC/接口形式对比
相比 REST:JSON-RPC 是"调用方法"思维(动词 + 参数),REST 是"操作资源"思维(URL + HTTP 动词)。JSON-RPC 通常一个端点跑所有方法,请求体决定行为。
相比 gRPC:gRPC 用 Protobuf 二进制编码 + HTTP/2,体积小性能高但需要 IDL 和代码生成;JSON-RPC 纯文本、人类可读、零依赖,适合调试和动态语言。
相比 GraphQL:GraphQL 强调按需取数据;JSON-RPC 强调调用动作。
四、典型应用场景
- 以太坊 / 区块链节点接口:以太坊节点的 eth_blockNumber、eth_call 等就是标准的 JSON-RPC over HTTP/WebSocket
- 编辑器协议:LSP(Language Server Protocol)、DAP(Debug Adapter Protocol)都是 JSON-RPC over stdio
- MCP(Model Context Protocol):Anthropic 推的模型上下文协议也基于 JSON-RPC
- 桌面应用 IPC:VSCode 扩展主进程↔渲染进程、各种 Electron 应用
- 轻量内部服务:当不想上 gRPC 又嫌 REST 表达动作笨拙时
五、如何发出
HTTP
绝大多数公开的 JSON-RPC 服务(比如以太坊节点)都跑在 HTTP 上。客户端把请求体作为 POST body 发出去,服务端在响应体里返回结果。
curl 示例(调用以太坊主网的 eth_blockNumber):
curl -X POST https://eth.llamarpc.com \
-H "Content-Type: application/json" \
-d '{"jsonrpc":"2.0","method":"eth_blockNumber","params":[],"id":1}'
返回:
{"jsonrpc":"2.0","id":1,"result":"0x14a2b3c"}
Node.js / 浏览器 fetch:
const res = await fetch("https://eth.llamarpc.com", {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({
jsonrpc: "2.0",
method: "eth_blockNumber",
params: [],
id: 1,
}),
})
const { result, error } = await res.json()
WebSocket
需要服务端主动推送(订阅事件、流式更新)时用 WebSocket。同一个连接上可以发任意多条消息,每条都是独立的 JSON 文本帧。
const ws = new WebSocket("wss://eth.llamarpc.com")
ws.onopen = () => {
ws.send(JSON.stringify({
jsonrpc: "2.0",
method: "eth_subscribe",
params: ["newHeads"],
id: 1,
}))
}
ws.onmessage = (e) => {
const msg = JSON.parse(e.data)
// 可能是响应(含 id),也可能是服务端推送的通知(含 method)
console.log(msg)
}
要点:WebSocket 上的 JSON-RPC 是双向的——服务端也能给你发 method 通知;你需要自己用 id 维护一张 pending 表来匹配响应和请求。
进程间:stdio(LSP、MCP 都用这种)
LSP、MCP、各种 CLI 协议跑在 stdin/stdout 上。这种场景必须用 Content-Length 帧头(LSP 风格)或换行分隔(NDJSON 风格)来切分消息,否则 JSON 流没法切开。
LSP / MCP 风格(带 Content-Length):
每条消息前面加一个 HTTP-like 头,空行后跟 JSON 体:
Content-Length: 76\r\n \r\n {"jsonrpc":"2.0","method":"initialize","params":{},"id":1}
Node.js 里写一条到子进程:
import { spawn } from "node:child_process"
const child = spawn("my-language-server", [])
function send(msg) {
const body = JSON.stringify(msg)
const header = `Content-Length: ${Buffer.byteLength(body, "utf8")}\r\n\r\n`
child.stdin.write(header + body)
}
send({ jsonrpc: "2.0", method: "initialize", params: {}, id: 1 })
读取的时候要先解析 header 拿到长度,再按字节数读 body。
NDJSON 风格(一行一条 JSON):
{"jsonrpc":"2.0","method":"ping","id":1}\n
简单但要求 JSON 内不能有真实换行,序列化时确保 JSON.stringify 输出单行即可。
openEuler 是由开放原子开源基金会孵化的全场景开源操作系统项目,面向数字基础设施四大核心场景(服务器、云计算、边缘计算、嵌入式),全面支持 ARM、x86、RISC-V、loongArch、PowerPC、SW-64 等多样性计算架构
更多推荐



所有评论(0)