接着上一篇博客,我们继续来讲解作为客户端的btcwallet
是如何工作的。也即,作为客户端的btcwallet
是如何与btcd
进程交互的。
III. btcwallet中的相关代码
...
B. 作为客户端的btcwallet
1 | // walletMain[btcwallet.go] -> rpcClientConnnectLoop[btcwallet.go] |
rpcClientConnectLoop
函数主要通过startChainRPC
函数创建并启动了一个连接btcd
的RPC
客户端。startChainRPC
函数的定义如下所示: 1
2
3
4
5
6
7
8// walletMain[btcwallet.go] -> rpcClientConnnectLoop[btcwallet.go] -> startChainRPC[btcwallet.go]
func startChainRPC(certs []byte) (*chain.RPCClient, error) {
...
rpcc, err := chain.NewRPCClient(activeNet.Params, cfg.RPCConnect, cfg.BtcdUsername, cfg.BtcdPassword, certs, cfg.DisableClientTLS, 0) // L268
...
err = rpcc.Start() // L272
...
}
1. 创建连接btcd的RPC客户端
rpc.go
中的NewRPCClient
函数又调用了btcsuite
套件中的infrastructure.go
文件中的New
函数(补充说明:btcsuite
是独立于btcd
和btcwallet
源代码的一套共用的套件)。 1
2
3
4
5
6
7
8
9
10
11// walletMain[btcwallet.go] -> rpcClientConnnectLoop[btcwallet.go] -> startChainRPC[btcwallet.go] -> NewRPCClient[rpc.go] -> New[infrastructure.go]
func New(config *ConnConfig, ntfnHandlers *NotificationHandlers) (*Client, error) {
...
client := &Client{
...
wsConn: wsConn, // L1252
...
}
client.start() // L1269
}client
,但client
的启动其实也是在该函数中的L1269行完成的。 在L1252行初始化了一个wsConn
连接,该连接用于真实向btcd
发送数据,但此处的初始化其实是将一个nil
值赋值给wsConn
。在后面,我们将会看到对该连接的重新赋值,那时候是赋予了一个真实可用的值。 start
函数的源码如下所示: 1
2
3
4
5
6
7// walletMain[btcwallet.go] -> rpcClientConnnectLoop[btcwallet.go] -> startChainRPC[btcwallet.go] -> NewRPCClient[rpc.go] -> New[infrastructure.go] -> start[infrastructure.go]
func (c *Client) start() {
...
go c.wsInHandler() // L1040
go c.wsOutHandler() // L1041
...
}for
循环,用于接收来自websocket
连接上的数据,这里主要是接收来自btcd
的response
数据(如新生成的区块的hash)。2)L1041行的代码也启动了一个for
循环的协程,用于接受btcwallet
内部产生的发送数据的请求,并在接收到请求后向btcd
发送数据。
通常地,btcwallet
会利用III.A节中的代码接收到来自btcctl
发送的请求,然后通过wsOutHandler
中的函数将请求发送给btcd
,接着利用wsInHandler
接收btcd
的返回数据。 下面我们将分为两个小节分别介绍wsInHandler
和wsOutHandler
方法。
1) wsInHandler函数相关
我们首先来看一下wsInHandler
函数中的 1
2
3
4
5
6
7
8
9
10
11// walletMain[btcwallet.go] -> rpcClientConnnectLoop[btcwallet.go] -> startChainRPC[btcwallet.go] -> NewRPCClient[rpc.go] -> New[infrastructure.go] -> start[infrastructure.go] -> wsInHandler[infrastructure.go]
func (c *Client) wsInHandler() {
out:
for {
...
_, msg, err := c.wsConn.ReadMessage() // L412
...
c.handleMessage(msg) // L421
...
}
}websocket
连接中接收btcd
返回的信息(比如生成的新区块的hash值),并在L421利用handleMessage
方法激活之前的responseChan
管道。 1
2
3
4
5
6
7
8
9
10
11// walletMain[btcwallet.go] -> rpcClientConnnectLoop[btcwallet.go] -> startChainRPC[btcwallet.go] -> NewRPCClient[rpc.go] -> New[infrastructure.go] -> start[infrastructure.go] -> wsInHandler[infrastructure.go] -> handlerMessage[infrastructure.go]
func (c *Client) handleMessage(msg []byte) {
var in inMessage
...
err := json.Unmarshal(msg, &in)
...
request := c.removeRequest(id) // L357
...
result, err := in.rawResponse.result()
request.responseChan <- &response{result: result, err: err} // L373
}handleMessage
首先通过解析msg
变量生成result
变量,然后在L373行中向responseChan
管道中填充数据。下面我们重点来关注一下这个responseChan
管道。
我们注意到request
是在L357行生成的,removeRequest
方法如下所示: 1
2
3
4
5
6// 关注responseChan管道, removeRequest[infrastructure.go]
func (c *Client) removeRequest(id uint64) *jsonRequest {
...
request := c.requestList.Remove(element).(*jsonRequest) //L212
...
}list
中取出一个jsonRequest
,相应地,这些jsonRequest
是在addRequest
方法中被加入的。addRequest
方法又是在sendRequest
方法中被调用的,如下所示: 1
2
3
4
5
6// 关注responseChan管道, sendRequest[infrastructure.go]
func (c *Client) sendRequest(jReq *jsonRequest) {
...
if err := c.addRequest(jReq); err != nil { //L855
...
}jReq
是在RawRequestAsync
函数中定义的,其在L64行对jsonRequest
中的responseChan
字段进行了赋值,并在L68行将responseChan
作为返回值返回,如下所示: 1
2
3
4
5
6
7
8
9
10// 关注responseChan管道, RawRequestAsync[rawrequest.go]
func (c *Client) RawRequestAsync(method string, params []json.RawMessage) FutureRawResult {
...
jReq := &jsonRequest{
...
responseChan: responseChan, // L64
}
...
return responseChan // L68
}responseChan
最终返回到了RawRequest
函数中,并通过Receive
方法对该管道进行接收,具体内容已经在上一篇博客中的III.A.2节进行了讲解。这样responseChan
就把这两部分内容串起来了,本节对responseChan
管道填充btcd
的返回值,III.A.2节从responseChan
中接收该值。
2) wsOutHandler函数相关
下面我们来看一下wsOutHandler
函数中的实现细节。总地来说,wsOutHandler
是利用sendChan
管道接收"由III.A节接收到的btcctl
发送来的请求",并将该请求进一步发送给btcd
。 1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16// walletMain[btcwallet.go] -> rpcClientConnnectLoop[btcwallet.go] -> startChainRPC[btcwallet.go] -> NewRPCClient[rpc.go] -> New[infrastructure.go] -> start[infrastructure.go] -> wsOutHandler[infrastruture.go]
func (c *Client) wsOutHandler() {
out:
for {
select {
case msg := <-c.sendChan: // L449
err := c.wsConn.WriteMessage(websocket.TextMessage, msg) // L450
if err != nil {
c.Disconnect()
break out
}
...
}
}
...
}sendChan
通道阻塞了,其由III.A.1节中sendMessage
函数的L481行激活,其向sendChan
管道中存放的是从btcctl
发送来的请求,前面也已经介绍过。 L450行的代码完成了向btcd
发送数据的任务。发送数据借助于wsConn
这个websocket
连接。
2. 启动连接btcd的RPC客户端
回到startChainRPC
函数中的L272行,我们来看一下在btcwallet
中用于连接btcd
的客户端是如何启动的。RPC
客户端的start
函数定义如下所示: 1
2
3
4
5// walletMain[btcwallet.go] -> rpcClientConnnectLoop[btcwallet.go] -> startChainRPC[btcwallet.go] -> Start[rpc.go]
func (c *RPCClient) Start() error {
err := c.Connect(c.reconnectAttempts) // L101
...
}Start
函数调用了RPC
客户端的Connect
函数, 该函数真实建立了和btcd
的websocket
连接,也即对RPC
客户端中的wsConn
变量进行了重新赋值。 1
2
3
4
5
6
7
8// walletMain[btcwallet.go] -> rpcClientConnnectLoop[btcwallet.go] -> startChainRPC[btcwallet.go] -> Start[rpc.go] -> Connect[infrastructure.go]
func (c *Client) Connect(tries int) error {
...
wsConn, err = dial(c.config) // L1309
...
c.wsConn = wsConn // L1324
...
}wsConn
变量重新进行了赋值。
V. btcd中的相关代码
同样地,为避免这一篇博客过长,第V节的内容将放在下一篇博客中。