客户端
Json RPC API
RPC
RPC(Remote Procedure Calls )远程过程调用是一种协议,就是从一台机器(客户端)上通过参数传递的方式调用另一台机器(服务器)上的一个函数或方法(可以统称为服务)并得到返回的结果。
RPC协议通常的实现有XML-RPC , JSON-RPC ,gRPC等,它们的通信方式基本相同, 所不同的只是传输数据的格式。
RPC是分布式架构的核心,按响应方式分如下两种:
- 同步调用:客户端调用服务方方法,等待直到服务方返回结果或者超时,再继续自己的操作。
- 异步调用:客户端把消息发送给中间件,不再等待服务端返回,直接继续自己的操作。
一个完整的RPC架构里面包含了四个核心的组件,分别是Client,Client Stub,Server以及Server Stub:
- 客户端(Client),服务的调用方。
- 客户端存根(Client Stub),存放服务端的地址消息,再将客户端的请求参数打包成网络消息,然后通过网络远程发送给服务方。
- 服务端(Server),真正的服务提供者。
- 服务端存根(Server Stub),接收客户端发送过来的消息,将消息解包,并调用本地的方法。
RPC的调用流程如下图所示:
该流程中的具体步骤是:
- 服务调用方(client)(客户端)以本地调用方式调用服务。
- client stub接收到调用后负责将方法、参数等组装成能够进行网络传输的消息体;在Java里就是序列化的过程。
- client stub找到服务地址,并将消息通过网络发送到服务端。
- server stub收到消息后进行解码,在Java里就是反序列化的过程。
- server stub根据解码结果调用本地的服务。
- 本地服务执行处理逻辑。
- 本地服务将结果返回给server stub。
- server stub将返回结果打包成消息,主要也是Java里的序列化过程。
- server stub将打包后的消息通过网络并发送至消费方。
- client stub接收到消息,并进行解码, Java里的反序列化。
- 服务调用方(client)得到最终结果。
RPC框架的目标,就是要上面步骤里2~10给封装好,让用户像调用本地服务一样的调用远程服务,实现对客户端(调用方)透明化服务。这个听起来好像不难,但真正落地实现,就要面对以下几个难题:
- 通讯问题 : 主要是通过在客户端和服务器之间建立TCP/UDP连接,远程过程调用的所有交换的数据都在这个连接里传输;TCP连接可以是按需连接,调用结束后就断掉,也可以是长连接,多个远程过程调用共享同一个连接。
- 寻址问题: A服务器上的应用怎么告诉底层的RPC框架,如何连接到B服务器(如主机或IP地址)以及特定的端口,方法的名称是什么,这样才能完成调用。比如基于Web服务协议栈的RPC,就要提供一个endpoint URI。
- 序列化与反序列化 : 当A服务器上的应用发起远程过程调用时,方法的参数需要通过底层的网络协议如TCP传递到B服务器,由于网络协议是基于二进制的;内存中的参数的值要序列化成二进制的形式,也就是序列化(Serialize)或编组(marshall),再发送给B服务器;B服务器接收参数要将参数反序列化;同理,B服务器应用调用自己的方法处理后返回的结果也要序列化给A服务器,A服务器接收也要经过反序列化的过程。
Json RPC
像以太坊等主流区块链实现的RPC,都是基于Json RPC的。目前的版本是V2.0。
- JSON-RPC是一个无状态且轻量级的远程过程调用(RPC)协议实现规范。
- 它主要定义了一些数据结构及其相关的处理规则。
- 它允许运行在基于socket,http等诸多不同消息传输环境的同一进程中。
- 它使用JSON(RFC 4627)作为数据格式,这是它最大的特点。
- JSON支持4种基本类型
- String
- Numbers
- Booleans
- Null
- JSON还支持两种结构化类型:
- Objects
- Arrays
- 上述数据类型的第一个字母必须大写;客户端与服务端之间交换的成员名字,也是区分大小写的。
- 函数、方法、过程的称谓在该规范里是可互换的。
- 客户端:定义为请求对象的来源及响应对象的处理程序。
- 服务端:定义为响应对象的起源和请求对象的处理程序。
- 一个请求对象包括以下成员:
- jsonrpc:指定JSON-RPC协议版本的字符串,必须准确写为“2.0”;
- method:包含所要调用方法名称的字符串;
- params:调用方法所需要的结构化参数值,该成员参数可以被省略;
- id:已建立客户端的唯一标识id,值必须包含一个字符串、数值或NULL空值;如果不包含该成员则被认定为是一个通知。
下面就是一个请求对象的例子
{ "jsonrpc": "2.0", "method": "subtract", "params": [42, 23], "id": 1}
- 通知:没有包含“id”成员的请求对象为通知, 作为通知的请求对象表明客户端对相应的响应对象并不感兴趣,本身也没有响应对象需要返回给客户端;
- 参数结构:rpc调用如果存在参数则必须为基本类型或结构化类型的参数值,要么为索引数组,要么为关联数组对象。
- 索引:参数必须为数组,并包含与服务端预期顺序一致的参数值;
- 关联名称:参数必须为对象,并包含与服务端相匹配的参数成员名称;没有在预期中的成员名称可能会引起错误。名称必须完全匹配,包括方法的预期参数名以及大小写。
- 响应对象也会是一个JSON对象,它的成员包括:
- jsonrpc:指定JSON-RPC协议版本的字符串,必须准确写为“2.0”。
- result:该成员在成功时必须包含,其值由服务端中的被调用方法决定;当调用方法引起错误时必须不包含该成员。
- error:当没有引起错误的时必须不包含该成员;该成员在失败时必须包含,且其值可以为以下对象:
- code:使用数值表示该异常的错误类型, 必须为整数。
- message:对该错误的简单描述字符串;该描述应尽量限定在简短的一句话。
- data:包含关于错误附加信息的基本类型或结构化类型;该成员可忽略; 该成员值由服务端定义(例如详细的错误信息,嵌套的错误等)。
- id:该成员必须包含;该成员值必须与请求对象中的id成员值一致;若在检查请求对象id时错误(例如参数错误或无效请求),则该值必须为空值。
下面是一个响应对象的例子:
{ "jsonrpc": "2.0", "result": 19, "id": 1}
Solana Json RPC
Solana也是基于JSON RPC来实现客户RPC调用的。
- Solana实现了基于HTTP的RPC API:
- 默认端口:8899
- 节点访问:例如http://localhost:8899
- Solana也实现了若干基于WebSocket API:
- 默认端口:8900
- 节点访问:例如ws://localhost:8900
- 下面是已实现的HTTP API