当前位置: 首页 > 工具软件 > btcd > 使用案例 >

btcd源码解析——从“新区块的生成”开始(2)

庄萧迟
2023-12-01

4.2 作为客户端的btcwallet

我们接着上一篇博客,继续来讲解作为客户端的btcwallet是如何工作的。
也即,作为客户端的btcwallet是如何与btcd进程交互的。

// walletMain[btcwallet.go] -> rpcClientConnnectLoop[btcwallet.go]
func rpcClientConnectLoop(legacyRPCServer *legacyrpc.Server, loader 
*wallet.Loader) {
...
chainClient, err = startChainRPC(certs)                     // L188
...
}

rpcClientConnectLoop函数主要通过startChainRPC函数创建并启动了一个连接btcdRPC客户端。startChainRPC函数的定义如下所示:

// 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
...
}

其中L268创建了该客户端,而L272行启动了该客户端。以下分别分两小节介绍客户端的创建和启动过程。

4.2.1 创建连接btcd的RPC客户端

rpc.go中的NewRPCClient函数又调用了btcsuite套件中的infrastructure.go文件中的New函数(补充说明:btcsuite是独立于btcdbtcwallet源代码的一套共用的套件)。

// 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函数的源码如下所示:

// 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
    ...
}

L1040行和L1041行的的代码非常关键。1)L1040行的代码启动了一个协程,该协程开启了一个for循环,用于接收来自websocket连接上的数据,这里主要是接收来自btcdresponse数据(如新生成的区块的hash)。2)L1041行的代码也启动了一个for循环的协程,用于接受btcwallet内部产生的发送数据的请求,并在接收到请求后向btcd发送数据。

通常地,btcwallet会利用4.1节中的代码接收到来自btcctl发送的请求,然后通过wsOutHandler中的函数将请求发送给btcd,接着利用wsInHandler接收btcd的返回数据。
下面我们将分为两个小节分别介绍wsInHandlerwsOutHandler方法。

4.2.1.1 wsInHandler函数相关

我们首先来看一下wsInHandler函数中的

// 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
        ...
    }
}

L412行的代码主要用于从websocket连接中接收btcd返回的信息(比如生成的新区块的hash值),并在L421利用handleMessage方法激活之前的responseChan管道。

// 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方法如下所示:

// 关注responseChan管道, removeRequest[infrastructure.go]
func (c *Client) removeRequest(id uint64) *jsonRequest {
    ...
    request := c.requestList.Remove(element).(*jsonRequest)         //L212
    ...
}

L212是从一个list中取出一个jsonRequest,相应地,这些jsonRequest是在addRequest方法中被加入的。addRequest方法又是在sendRequest方法中被调用的,如下所示:

// 关注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作为返回值返回,如下所示:

// 关注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方法对该管道进行接收,具体内容已经在上一篇博客中的4.1.2节进行了讲解。这样responseChan就把这两部分内容串起来了,本节对responseChan管道填充btcd的返回值,4.1.2节从responseChan中接收该值。

4.2.1.2 wsOutHandler函数相关

下面我们来看一下wsOutHandler函数中的实现细节。总地来说,wsOutHandler是利用sendChan管道接收"由4.1节接收到的btcctl发送来的请求",并将该请求进一步发送给btcd

// 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                 
            }
         ...
        }    
    }
...
}

L449行的代码被sendChan通道阻塞了,其由4.1.1节中sendMessage函数的L481行激活,其向sendChan管道中存放的是从btcctl发送来的请求,前面也已经介绍过。
L450行的代码完成了向btcd发送数据的任务。发送数据借助于wsConn这个websocket连接。

4.2.2 启动连接btcd的RPC客户端

回到startChainRPC函数中的L272行,我们来看一下在btcwallet中用于连接btcd的客户端是如何启动的。RPC客户端的start函数定义如下所示:

// 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函数, 该函数真实建立了和btcdwebsocket连接,也即对RPC客户端中的wsConn变量进行了重新赋值。

// 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
...
}

在L1324行对wsConn变量重新进行了赋值。

5. btcd中的相关代码

同样地,为避免这一篇博客过长,第5节的内容将放在下一篇博客中。

 类似资料: