上一节介绍了以太坊 RPC
的目录结构,这一 节介绍 RPC
服务端的设计。
无论是使用 geth attach
请求节点数据,还是使用 curl
以 json-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,当服务器处理请求的第一步就是判断自己是否正在运行,ServeCodec
( InProc
、IPC
、WS
) 和 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()
命令启动。但是第二种方式只能启动 WS
和 HTTP
两种方式,因为 InProc
只能节点启动时使用,而 IPC
方式则是连接上相应的文件就可以使用了(当然,可以在启动时禁止)。
启动 RPC
的第一步是收集节点所有的 API
(包括私有的,因为私有的只是针对 HTTP
和 WS
两种方式生效),第二步是启动 InProc、IPC、WS、HTTP
四种传输协议的终端,如果有终端启动失败,则停止前面已经启动的终端(后三种可以禁止,如果被禁止则不会停止前面的,停止的条件是:IPC
在启动时禁止,那它的文件路径就被置空,而 HTTP
和 WS
则是在启动时不设置 URL
)。最后把收集到的所有 api
放入节点的 rpcAPIs
中,以备通过 API
调用 admin.startRPC()
命令启动时使用。
这块因为是进程内部的通信,启动比较简单,步骤如下:
1.创建一个 RPC 服务 Server
。
2.注册API,把所有的 api
注册进去即可。–详见后文————注册API
3.把创建的服务 inprocHandler
保存到节点中,以备客户端连接时使用。
这块是进程间通信,它是借助 .IPC
文件通信的。在启动之前会判断 IPC
是否被禁止,如果想要禁止,在节点启动时添加 --ipcdisable
参数,然后节点会把这个参数解析为节点的 ipcEndpoint
属性为空,那么也就不再启动 IPC
终端了。判断之后才会启动 IPC
终端,最后保存相关的信息(服务 ipcHandler
、网络监听 ipcListener
)到节点中。启动终端的步骤如下:
1.创建一个 RPC 服务 Server
。
2.注册API,把所有的 api
注册进去即可。–详见后文————注册API
3.启动一个 IPC
监听(调用 net
包的 Listen
接口,协议是 unix
)。
4.把监听到的客户端请求交给服务中的 ServeCodec
来处理。–详见后文————ServeCodec()
这块是 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()
这块是 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()
这块内容是通过 GoLang
语言的反射机制,把传入的 api
服务添加到 RPC
服务端的 services
中。执行步骤如下:
1.把接口对象转为反射对象。
2.拿到相应的回调函数。
3.把拿到的回调函数放进 services
中。
然后详细说一下上文的第二步,拿到相应的回调函数:
1.初始化一个保存回调函数的 map
, key
是方法名,value
是回调函数体。
2.遍历反射对象的方法。
3.依次构建每个回调函数体。
4.依次格式化每个回调函数名。
5.把函数名和函数体放入上面构建的 map
中。
6.返回。
该方法的执行步骤:
1.在调用之前首先判断 RPC
服务是否启动,如果未启动则不执行。
2.把 codec
添加到服务 Server
的 codecs
字段中,使用完删除它。
3.初始化一个客户端(这块内容是下一章的主题),使用完毕关闭。
该方法的特点:
1.调用时机是节点收到 JSON-RPC
请求时。
2.在调用之前首先判断 RPC
服务是否启动,如果未启动则不执行。
3.不支持订阅
该方法的执行步骤:
1.从 codec
中读取请求。
2.处理读取到的请求,处理方式分为是否批量。(具体的处理方式在 handle.go
文件中,以后章节介绍)