客户端

Json RPC API

RPC

RPC(Remote Procedure Calls )远程过程调用是一种协议,就是从一台机器(客户端)上通过参数传递的方式调用另一台机器(服务器)上的一个函数或方法(可以统称为服务)并得到返回的结果

RPC协议通常的实现有XML-RPC , JSON-RPC ,gRPC等,它们的通信方式基本相同, 所不同的只是传输数据的格式。

RPC是分布式架构的核心,按响应方式分如下两种:

  1. 同步调用:客户端调用服务方方法,等待直到服务方返回结果或者超时,再继续自己的操作。
  2. 异步调用:客户端把消息发送给中间件,不再等待服务端返回,直接继续自己的操作。

一个完整的RPC架构里面包含了四个核心的组件,分别是Client,Client Stub,Server以及Server Stub:

  1. 客户端(Client),服务的调用方。
  2. 客户端存根(Client Stub),存放服务端的地址消息,再将客户端的请求参数打包成网络消息,然后通过网络远程发送给服务方。
  3. 服务端(Server),真正的服务提供者。
  4. 服务端存根(Server Stub),接收客户端发送过来的消息,将消息解包,并调用本地的方法。

RPC的调用流程如下图所示:

该流程中的具体步骤是:

  1. 服务调用方(client)(客户端)以本地调用方式调用服务。
  2. client stub接收到调用后负责将方法、参数等组装成能够进行网络传输的消息体;在Java里就是序列化的过程。
  3. client stub找到服务地址,并将消息通过网络发送到服务端。
  4. server stub收到消息后进行解码,在Java里就是反序列化的过程。
  5. server stub根据解码结果调用本地的服务。
  6. 本地服务执行处理逻辑。
  7. 本地服务将结果返回给server stub。
  8. server stub将返回结果打包成消息,主要也是Java里的序列化过程。
  9. server stub将打包后的消息通过网络并发送至消费方。
  10. client stub接收到消息,并进行解码, Java里的反序列化。
  11. 服务调用方(client)得到最终结果。

RPC框架的目标,就是要上面步骤里2~10给封装好,让用户像调用本地服务一样的调用远程服务,实现对客户端(调用方)透明化服务。这个听起来好像不难,但真正落地实现,就要面对以下几个难题:

  1. 通讯问题 : 主要是通过在客户端和服务器之间建立TCP/UDP连接,远程过程调用的所有交换的数据都在这个连接里传输;TCP连接可以是按需连接,调用结束后就断掉,也可以是长连接,多个远程过程调用共享同一个连接。
  2. 寻址问题: A服务器上的应用怎么告诉底层的RPC框架,如何连接到B服务器(如主机或IP地址)以及特定的端口,方法的名称是什么,这样才能完成调用。比如基于Web服务协议栈的RPC,就要提供一个endpoint URI。
  3. 序列化与反序列化 : 当A服务器上的应用发起远程过程调用时,方法的参数需要通过底层的网络协议如TCP传递到B服务器,由于网络协议是基于二进制的;内存中的参数的值要序列化成二进制的形式,也就是序列化(Serialize)或编组(marshall),再发送给B服务器;B服务器接收参数要将参数反序列化;同理,B服务器应用调用自己的方法处理后返回的结果也要序列化给A服务器,A服务器接收也要经过反序列化的过程。

Json RPC

像以太坊等主流区块链实现的RPC,都是基于Json RPC的。目前的版本是V2.0。

  1. JSON-RPC是一个无状态且轻量级的远程过程调用(RPC)协议实现规范。
  2. 它主要定义了一些数据结构及其相关的处理规则。
  3. 它允许运行在基于socket,http等诸多不同消息传输环境的同一进程中。
  4. 它使用JSON(RFC 4627)作为数据格式,这是它最大的特点。
  5. JSON支持4种基本类型
  • String
  • Numbers
  • Booleans
  • Null
  1. JSON还支持两种结构化类型:
  • Objects
  • Arrays
  1. 上述数据类型的第一个字母必须大写;客户端与服务端之间交换的成员名字,也是区分大小写的。
  2. 函数、方法、过程的称谓在该规范里是可互换的。
  3. 客户端:定义为请求对象的来源及响应对象的处理程序。
  4. 服务端:定义为响应对象的起源和请求对象的处理程序。
  5. 一个请求对象包括以下成员:
  • jsonrpc:指定JSON-RPC协议版本的字符串,必须准确写为“2.0”;
  • method:包含所要调用方法名称的字符串;
  • params:调用方法所需要的结构化参数值,该成员参数可以被省略;
  • id:已建立客户端的唯一标识id,值必须包含一个字符串、数值或NULL空值;如果不包含该成员则被认定为是一个通知。

下面就是一个请求对象的例子

{ "jsonrpc": "2.0", "method": "subtract", "params": [42, 23], "id": 1}
  • 通知:没有包含“id”成员的请求对象为通知, 作为通知的请求对象表明客户端对相应的响应对象并不感兴趣,本身也没有响应对象需要返回给客户端;
  • 参数结构:rpc调用如果存在参数则必须为基本类型或结构化类型的参数值,要么为索引数组,要么为关联数组对象。
    • 索引:参数必须为数组,并包含与服务端预期顺序一致的参数值;
    • 关联名称:参数必须为对象,并包含与服务端相匹配的参数成员名称;没有在预期中的成员名称可能会引起错误。名称必须完全匹配,包括方法的预期参数名以及大小写。
  1. 响应对象也会是一个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调用的。

  1. Solana实现了基于HTTP的RPC API:
  • 默认端口:8899
  • 节点访问:例如http://localhost:8899
  1. Solana也实现了若干基于WebSocket API:
  • 默认端口:8900
  • 节点访问:例如ws://localhost:8900
  1. 下面是已实现的HTTP API