以太坊的RPC简介(二)

上一节介绍了以太坊 RPC 的目录结构,这一 节介绍 RPC 服务端的设计。

前言

无论是使用 geth attach 请求节点数据,还是使用 curljson-rpc 格式请求节点数据,甚至使用第三方库 web3.js、web3j 请求节点数据,最终都是通过 RPC 服务端获取数据的。

服务端核心数据结构

这里先讲一下 RPC 服务端的核心数据结构

 type Server struct {
	services serviceRegistry // 这是个map,记录了所有注册的方法和类
	idgen    func() ID
	run      int32  // 这个参数用来控制RPC服务的启动和停止
	codecs   mapset.Set // 这是个set,记录了所有的编码解码器,其实是所有的连接
}

1.services 是以太坊 RPC 服务对外提供的保存所有服务的 map ,它是在 RPC 服务启动的时候注册进去的。
2.idgen 不说了,它就是在创建 server、client、handler 时用来标识唯一的字符串。
3.run 则是标识 rpc 是否处于运行状态,运行状态是1,停止状态是0,当服务器处理请求的第一步就是判断自己是否正在运行,ServeCodecInProcIPCWS ) 和 serveSingleRequest ( HTTP )。
4.codecs 则是一个 Set 类型的数据结构,保存的对象是 jsonCodec。因为 RPC 的数据传输是不关心底层的实现方式,而这个字段就是用于向外发送数据的,真正发送的时候,它就成了 jsonWriter 接口了。

四种传输协议

以太坊 RPC 服务提供了四种对外服务方式,分别是 InProc、IPC、WS、HTTP
1.InProc 用于进程内通信,它是节点启动时添加 console 参数启动,即 geth console
2.IPC 是用于进程间通信,它是借助 *.IPC 文件通信的,连接的时候使用的命令是 geth attach path/to/*.IPC
3.WS 是websocket通信。
4.HTTP 是HTTP通信,使用 curl 发送 json-rpc 或使用命令 geth attach URL

源码分析

本文从以太坊启动 RPC 服务讲起,启动 RPC 服务有两种方式,一种是节点启动的时候启动,另一种是在命令行通过 API 调用 admin.startRPC() 命令启动。但是第二种方式只能启动 WSHTTP 两种方式,因为 InProc 只能节点启动时使用,而 IPC 方式则是连接上相应的文件就可以使用了(当然,可以在启动时禁止)。


因为第二种启动方式是第一种的子集,这里就只讲述第一种方式。

启动RPC服务

启动 RPC 的第一步是收集节点所有的 API (包括私有的,因为私有的只是针对 HTTPWS 两种方式生效),第二步是启动 InProc、IPC、WS、HTTP 四种传输协议的终端,如果有终端启动失败,则停止前面已经启动的终端(后三种可以禁止,如果被禁止则不会停止前面的,停止的条件是:IPC 在启动时禁止,那它的文件路径就被置空,而 HTTPWS 则是在启动时不设置 URL)。最后把收集到的所有 api 放入节点的 rpcAPIs 中,以备通过 API 调用 admin.startRPC() 命令启动时使用。

启动InProc

这块因为是进程内部的通信,启动比较简单,步骤如下:
1.创建一个 RPC 服务 Server
2.注册API,把所有的 api 注册进去即可。–详见后文————注册API
3.把创建的服务 inprocHandler 保存到节点中,以备客户端连接时使用。

启动IPC

这块是进程间通信,它是借助 .IPC 文件通信的。在启动之前会判断 IPC 是否被禁止,如果想要禁止,在节点启动时添加 --ipcdisable 参数,然后节点会把这个参数解析为节点的 ipcEndpoint 属性为空,那么也就不再启动 IPC 终端了。判断之后才会启动 IPC 终端,最后保存相关的信息(服务 ipcHandler、网络监听 ipcListener)到节点中。启动终端的步骤如下:
1.创建一个 RPC 服务 Server
2.注册API,把所有的 api 注册进去即可。–详见后文————注册API
3.启动一个 IPC 监听(调用 net 包的 Listen 接口,协议是 unix)。
4.把监听到的客户端请求交给服务中的 ServeCodec 来处理。–详见后文————ServeCodec()

启动HTTP

这块是 http 通信,如果 URL httpEndpoint 为空(节点启动时不加参数 --rpc,如果添加了该参数而不设置 --rpcaddr,则默认为 127.0.0.1),代表被禁止。判断之后才会启动 HTTP 终端,最后保存相关的信息(URL httpEndpoint、服务 httpHandler、网络监听 ihttpListener)到节点中。启动终端的步骤如下:
1.准备 api 的白名单 (节点启动时使用参数 --rpcapi 添加进去的)。
2.注册API,把白名单的 api 和公开的 api 注册进去即可。–详见后文————注册API
3.启动一个 HTTP 监听(调用 net 包的 Listen 接口,协议是 tcp)。
4.创建一个 HTTP 服务
5.把监听到的客户端请求交给服务中的 serveSingleRequest 来处理。–详见后文————serveSingleRequest()

启动WS

这块是 websocket 通信,如果 URL wsEndpoint 为空(节点启动时不加参数 --rpc,如果添加了该参数而不设置 --wsaddr,则默认为 127.0.0.1),代表被禁止。判断之后才会启动 WS 终端,最后保存相关的信息(URL wsEndpoint、服务 wsHandler、网络监听 wsListener)到节点中。启动终端的步骤如下:
1.准备 api 的白名单 (节点启动时使用参数 --rpcapi 添加进去的)。
2.注册API,把白名单的 api 和公开的 api 注册进去即可。–详见后文————注册API
3.启动一个 WS 监听(调用 net 包的 Listen 接口,协议是 tcp)。
4.创建一个 WebSocket 服务
5.把监听到的客户端请求交给服务中的 ServeCodec 来处理。–详见后文————ServeCodec()

注册API

这块内容是通过 GoLang 语言的反射机制,把传入的 api 服务添加到 RPC 服务端的 services 中。执行步骤如下:
1.把接口对象转为反射对象。
2.拿到相应的回调函数。
3.把拿到的回调函数放进 services 中。

然后详细说一下上文的第二步,拿到相应的回调函数:
1.初始化一个保存回调函数的 map, key 是方法名,value 是回调函数体。
2.遍历反射对象的方法。
3.依次构建每个回调函数体。
4.依次格式化每个回调函数名。
5.把函数名和函数体放入上面构建的 map 中。
6.返回。

ServeCodec()

该方法的执行步骤:
1.在调用之前首先判断 RPC 服务是否启动,如果未启动则不执行。
2.把 codec 添加到服务 Servercodecs 字段中,使用完删除它。
3.初始化一个客户端(这块内容是下一章的主题),使用完毕关闭。

serveSingleRequest()

该方法的特点:
1.调用时机是节点收到 JSON-RPC 请求时。
2.在调用之前首先判断 RPC 服务是否启动,如果未启动则不执行。
3.不支持订阅

该方法的执行步骤:
1.从 codec 中读取请求。
2.处理读取到的请求,处理方式分为是否批量。(具体的处理方式在 handle.go 文件中,以后章节介绍)

你可能感兴趣的:(区块链,以太坊,RPC)