btcd源码解析——节点P2P连接建立的过程 (2)

4. 与其他peer建立P2P连接

我们接着上一篇博客,继续讲解P2P主动连接的管理被动接受连接的过程。

4.2 主动连接的管理

前面介绍到Start [connmanager.go]函数中调用的connHandler函数,主要用来管理主动连接。此外,在4.1节中我们提及requests管道的接收工作就是在connHandler函数中完成的。本小节我们就来看看connHandler函数的具体细节,先看看其代码:

// Start [connmanager.go] -> connHandler
func (cm *ConnManager) connHandler() {
    ...
    var (
        ...
        pending = make(map[uint64]*ConnReq)                		  // L233
        ...
        conns = make(map[uint64]*ConnReq, cm.cfg.TargetOutbound)                    // L236
    )
    
out:
    for {       
        select {
        case req := <.cm.requests:                                // L242
            switch msg :=req.(type) {
            
            case registerPending:                                 // L245
                connReq := msg.c
                connReq.updateState(ConnPending)
                pending[msg.c.id] = connReq
                close(msg.done)
                
            case handleConnected:                                 // L251
                connReq := msg.c

                connReq.updateState(ConnEstablished)              // L254
                connReq.conn = msg.conn
                conns[connReq.id] = connReq
                log.Debugf("Connected to %v", connReq)
                connReq.retryCount = 0
                cm.failedAttempts = 0

                delete(pending, connReq.id)                       // L270

                if cm.cfg.OnConnection != nil {                   // L272
                    go cm.cfg.OnConnection(connReq, msg.conn)     // L273
                }

            case handleDisconnected:
                ...
            case handleFailed:
                ...
            }
        case <-cm.quit: 
            break out
        }
    }
    ...
}

L242行代码接收requests管道中发过来的数据,判断数据类型后,交由不同的case处理。下面主要介绍registerPendinghandleConneted两种类型的数据

  • L245行代码处理registerPending类型的信息,其主要将conn变量的状态更新后加入到pending变量中
  • L251行代码处理handleConnected类型的信息,其首先也是对conn变量的状态进行了更新,将conn加入到conns变量中,并从pending中删除;然后利用OnConnection函数进行连接后的处理。OnConnection函数也是在server.go文件的newServer中赋值的,代码如下所示:
// Start [connmanager.go] -> connHandler -> newServer [server.go]
func newServer(...) (*server, error) {
    ...
    cmgr, err := connmgr.New(&connmgr.Config{   		       // L2818
        ... 
        OnConnection:   s.outboundPeerConnected,
        ...
        GetNewAddress:  newAddressFunc,
    })
    ...
    ...
}

outboundPeerConnected函数的定义如下所示:

//  Start [connmanager.go] -> connHandler -> newServer [server.go] -> outboundPeerConnected
func (s *server) outboundPeerConnected(c *connmgr.ConnReq, conn net.Conn) {
    sp := newServerPeer(s, c.Permanent)                          // L2024
    p, err := peer.NewOutboundPeer(newPeerConfig(sp), c.Addr.String())          
    ...
    sp.Peer = p
    sp.connReq = c
    sp.isWhitelisted = isWhitelisted(conn.RemoteAddr())          // L2032
    sp.AssociateConnection(conn)                                 // L2033
    go s.peerDoneHandler(sp)                                     // L2034
    s.addrManager.Attempt(sp.NA())                               // L2035
}

L2024-L2032代码创建了一个serverPeer变量,该变量包含了一个Peer变量;L2033行代码将sp变量与conn进行了绑定,并在AssociateConnection函数中启动了Peer变量,代码如下所示:

// Start [connmanager.go] -> connHandler -> newServer [server.go] -> outboundPeerConnected -> AssociateConnection
func (p *Peer) AssociateConnection(conn net.Conn) {
    ...
    p.conn = conn                   		                    // L2118
    ...
    go func() {       
        if err := p.start(); err != nil {                       // L2137
            ...
        }
    } ()
}

L2118行代码将conn赋值给Peer变量中相应的字段,L2137行代码的start函数用来处理P2P连接中的数据传输,start函数代码如下所示:

// Start [connmanager.go] -> connHandler -> newServer [server.go] -> outboundPeerConnected -> AssociateConnection -> start [peer.go]
func (p *Peer) start() error {
    ...
    negotiateErr := make(chan error, 1)                			// L2075
    go func() {                                                                         
        if p.inbound {             
            negotiateErr <- p.negotiateInboundProtocol()		// L2078
        } else {
            negotiateErr <- p.negotiateOutboundProtocol()       // L2080
        }
    }()                                                         // L2082
    ...
    go p.stallHandler()                                         // L2099
    go p.inHandler()
    go p.queueHandler()
    go p.outHandler()
    go p.pingHandler()                       		            // L2103
    ...
}

L2075-L2082行代码主要协商双方的协议版本,判断双方版本是否兼容。L2099-L2103行代码启动了五个协程,用于发送和接收数据 (包括区块的同步、交易的发送等),具体代码细节,我们将在下一篇博客中分析。这里需要特别提一下的是negotiateInboundProtocolnegotiateOutboundProtocol函数,该函数虽然名为“协商版本函数”,但该函数完成的工作却远远不止协商版本这么简单,以下以negotiateOutboundProtocol函数为例进行简单介绍。

4.2.1 negotiateOutboundProtocol函数

negotiateOutboundProtocol函数代码如下所示:

// negotiateOutboundProtocol [peer.go]
func (p *Peer) negotiateOutboundProtocol() error {
    if err := p.writeLocalVersionMsg(); err != nil {             // L2064
        return err
    }
    
    return p.readRemoteVersionMsg()                              // L2068
}

L2064行代码用于给对方peer放松版本信息,L2068接收对方peer发来的版本信息,readRemoteVersionMsg函数代码如下:

// negotiateOutboundProtocol [peer.go] -> readRemoteVersionMsg
func (p *Peer) readRemoteVersionMsg() error {
    ...
    p.versionKnown = true                               		 // L1911
    ...
    if p.cfg.Listeners.OnVersion != nil {
        rejectMsg := p.cfg.Listeners.OnVersion(p, msg)           // L1949
        ...
    }
    ...
}

L1911行代码对versionKnown进行了赋值,该值将在4.2.2小节使用到,后文再说。L1949行代码调用OnVersion函数,该函数是MessageListeners结构中的变量,其在server.gonewPeerConfig函数中被赋值为sp.OnVersionsp.OnVersion函数的代码如下:

// negotiateOutboundProtocol [peer.go] -> readRemoteVersionMsg -> OnVersion[server.go]
func (sp *serverPeer) OnVersion(_ *peer.Peer, msg *wire.MsgVersion) 
*wire.MsgReject {
    ...
    sp.server.AddPeer(sp)                                        // L501
    ...
}

L501行代码调用了AddPeer函数,该函数向管道newPeers中发送数据,代码如下所示:

// negotiateOutboundProtocol [peer.go] -> readRemoteVersionMsg -> OnVersion[server.go] -> AddPeer
func (s *server) AddPeer(sp *serverPeer) {                        // L2162
    s.newPeers <- sp
}

newPeers管道的另一端连接着server.peerHandler函数,代码如下所示:

// negotiateOutboundProtocol [peer.go] -> readRemoteVersionMsg -> OnVersion[server.go] -> AddPeer -> peerHandler
func (s *server) peerHandler() {
    ...
out:       
    for {              
        select {             
        // New peers connected to the server.              
        case p := <-s.newPeers:                     
            s.handleAddPeerMsg(state, p)                           // L2100  
        ...    
        case p := <-s.donePeers:                                   // L2103
            s.handleDonePeerMsg(state, p)
        ...
        case <-s.quit:
            ...
        }
    }
    ...
}

L2100调用handleAddPeerMsg函数,代码如下所示:

// negotiateOutboundProtocol [peer.go] -> readRemoteVersionMsg -> OnVersion[server.go] -> AddPeer -> peerHandler -> handleAddPeerMsg
func (s *server) handleAddPeerMsg(state *peerState, sp *serverPeer) bool {
    ...
    if sp.Inbound() {                                             // L1643
        state.inboundPeers[sp.ID()] = sp
    } else {       
        state.outboundGroups[addrmgr.GroupKey(sp.NA())]++       
        if sp.persistent {              
            state.persistentPeers[sp.ID()] = sp       
        } else {              
            state.outboundPeers[sp.ID()] = sp       
        }
    }                                                             // L1652
    ...
}

该函数在L1643-L1652行代码将peer变量(sp)登记到state状态中,便于后面的进一步管理,如在断开连接时清理peer相关数据。

4.2.2 peerDoneHandler函数

回到outboundPeerConnected函数中,L2034行代码调用了peerDoneHandler函数。该函数主要用于在peer断开连接时利用管道发出一些信号,代码如下所示:

// Start [connmanager.go] -> connHandler -> newServer [server.go] -> outboundPeerConnected -> AssociateConnection -> peerDoneHandler
func (s *server) peerDoneHandler(sp *serverPeer) {
    sp.WaitForDisconnect()                                       // L2041
    s.donePeers <- sp                                            // L2042

    ...
    if sp.VersionKnown() {                                       // L2045
        s.syncManager.DonePeer(sp.Peer)                          // L2046
        ...
    }
    close(sp.quit)                             	                 // L2056
}

L2041行代码等待关闭连接的信号,L2042通过donePeers管道向server发送信号,L2056行通过quit管道向server发送信号,这两个管道的另一端也都连接着server.peerHandler函数,如4.2.1小节所示。
此外,L2045spversionKnown字段进行了判断,该字段在readRemoteVersionMsg函数的L1911行被赋值为true. L2046syncManager发送信号,通知syncManager停止数据的同步。

4.3 被动接受连接

下面我们介绍节点被动接受连接的过程。
我们再将Start函数中被动接收连接相关的代码贴在这儿:

// Start [connmanager.go]
func (cm *ConnManager) Start() {
    ...
    if cm.cfg.OnAccept != nil {                                  // L522
        for _, listener := range cm.cfg.Listeners {
            cm.wg.Add(1)
            go cm.listenerHandler(listener)                      // L525
        }
    }
   ...
}

L522首先判断OnAccept字段是否为nil. OnAccept字段也是在server.go文件的newServer中赋值的,代码如下所示:

// newServer [server.go]
func newServer(...) (*server, error) {
    ...
    cmgr, err := connmgr.New(&connmgr.Config{                    // L2818
        ... 
        OnAccept:       s.inboundPeerConnected,
        ...
        GetNewAddress:  newAddressFunc,
    })
    ...
}

回到Start函数中L525行的代码,继续看listenerHandler函数,其代码如下所示:

// Start [connmanager.go] -> listenHandler
func (cm *ConnManager) listenHandler(listener net.Listener) {       
    ...
    for atomic.LoadInt32(&cm.stop) == 0 {                                    
        conn, err := listener.Accept()                          // L494
        ...
        go cm.cfg.OnAccept(conn)                                // L502
    }       
    ...
}

L494行通过调用golangnet包的Accept方法接收连接请求,并将该请求作为参数传递给OnAccept函数。前面已经介绍过,OnAccept字段被赋值为inboundPeerConnected函数,后者的代码如下所示:

// inboundPeerConnected [server.go]
func (s *server) inboundPeerConnected(conn net.Conn) {      
    sp := newServerPeer(s, false)                              // L2011
    sp.isWhitelisted = isWhitelisted(conn.RemoteAddr())       
    sp.Peer = peer.NewInboundPeer(newPeerConfig(sp))           // L2013
    sp.AssociateConnection(conn)                               // L2014
    go s.peerDoneHandler(sp)        	                       // L2015
}

inboundPeerConnected函数的代码和outboundPeerConnected函数的代码大同小异,此处不再赘述。

5. 总结

至此,我们完成了P2P连接建立的源码解析。
其主要分为两个部分:peer地址的管理和peer连接的建立。
其中peer连接建立又可分为两种:主动发起连接和被动接收连接。
被动接受连接的代码比较简单。只需要listenerHandler一个协程就可以完成;主动发起连接的代码相对复杂,需要NewConnReqconnHandler两个协程来完成。
在此,我们没有介绍P2P连接的断开过程,这部分代码后面有机会再来分析。

  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
2008年爆发全球金融危机,同年11月1日,一个自称中本聪(Satoshi Nakamoto)的人在P2P foundation网站上发布了比特币白皮书《比特币:一种点对点的电子现金系统》 [6]  ,陈述了他对电子货币的新设想——比特币就此面世。2009年1月3日,比特币创世区块诞生。 和法定货币相比,比特币没有一个集中的发行方,而是由网络节点的计算生成,谁都有可能参与制造比特币,而且可以全世界流通,可以在任意一台接入互联网的电脑上买卖,不管身处何方,任何人都可以挖掘、购买、出售或收取比特币,并且在交易过程中外人无法辨认用户身份信息。2009年1月5日,不受央行和任何金融机构控制的比特币诞生。比特币是一种数字货币,由计算机生成的一串串复杂代码组成,新比特币通过预设的程序制造。 每当比特币进入主流媒体的视野时,主流媒体总会请一些主流经济学家分析一下比特币。早先,这些分析总是集中在比特币是不是骗局。而现如今的分析总是集中在比特币能否成为未来的主流货币。而这其中争论的焦点又往往集中在比特币的通缩特性上。 [7]  不少比特币玩家是被比特币的不能随意增发所吸引的。和比特币玩家的态度截然相反,经济学家们对比特币2100万固定总量的态度两极分化。 凯恩斯学派的经济学家们认为政府应该积极调控货币总量,用货币政策的松紧来为经济适时的加油或者刹车。因此,他们认为比特币固定总量货币牺牲了可调控性,而且更糟糕的是将不可避免地导致通货紧缩,进而伤害整体经济。奥地利学派经济学家们的观点却截然相反,他们认为政府对货币的干预越少越好,货币总量的固定导致的通缩并没什么大不了的,甚至是社会进步的标志。 比特币网络通过“挖矿”来生成新的比特币。所谓“挖矿”实质上是用计算机解决一项复杂的数学问题,来保证比特币网络分布式记账系统的一致性。比特币网络会自动调整数学问题的难度,让整个网络约每10分钟得到一个合格答案。随后比特币网络会新生成一定量的比特币作为区块奖励,奖励获得答案的人。 [6]  2009年,比特币诞生的时候,区块奖励是50个比特币。诞生10分钟后,第一批50个比特币生成了,而此时的货币总量就是50。随后比特币就以约每10分钟50个的速度增长。当总量达到1050万时(2100万的50%),区块奖励减半为25个。当总量达到1575万(新产出525万,即1050的50%)时,区块奖励再减半为12.5个。该货币系统曾在4年内只有不超过1050万个,之后的总数量将被永久限制在约2100万个。 [3]  [8]  比特币是一种虚拟货币,数量有限,但是可以用来套现:可以兑换成大多数国家的货币。你可以使用比特币购买一些虚拟的物品,比如网络游戏当中的衣服、帽子、装备等,只要有人接受,你也可以使用比特币购买现实生活当中的物品。 2014年2月25日,“比特币中国”的比特币开盘价格为3562.41元,截至下午4点40分,价格已下跌至3185元,跌幅逾10%。根据该平台的历史行情数据显示,在2014年1月27日,1比特币还能兑换5032元人民币。这意味着,该平台上不到一个月,比特币价格已下跌了36.7%。 同年9月9日,美国电商巨头eBay宣布,该公司旗下支付处理子公司Braintree将开始接受比特币支付。该公司已与比特币交易平台Coinbase达成合作,开始接受这种相对较新的支付手段。 虽然eBay市场交易平台和PayPal业务还不接受比特币支付,但旅行房屋租赁社区Airbnb和租车服务Uber等Braintree客户将可开始接受这种虚拟货币。Braintree的主要业务是面向企业提供支付处理软件,该公司在2013年被eBay以大约8亿美元的价格收购。 2017年1月22日晚间,火币网、比特币中国与OKCoin币行相继在各自官网发布公告称,为进一步抑制投机,防止价格剧烈波动,各平台将于1月24日中午12:00起开始收取交易服务费,服务费按成交金额的0.2%固定费率收取,且主动成交和被动成交费率一致。 [9]  5月5日,OKCoin币行网的新数据显示,比特币的价格刚刚再度刷新历史,截止发稿前高触及9222元人民币高位。1月24日中午12:00起,中国三大比特币平台正式开始收取交易费。9月4日,央行等七部委发公告称中国禁止虚拟货币交易。同年12月17日,比特币达到历史高价19850美元。 2018年11月25日,比特币跌破4000美元大关,后稳定在3000多美元。 [10]  11月19日,加密货币恢复跌势,比特币自2017年10月以来首次下探5000美元大关,原因是之前BCH出现硬分叉,且监管部门对首次代币发行(ICO)加强了审查。 [10]  11月21日凌晨4点半,coinbase平台比特币报价跌破4100美元,创下了13个月以来的新低。 2019年4月,比特币再次突破5000美元大关,创年内新高。 [11]  5月12日,比特币近八个月来首次突破7000美元。 [12]  5月14日,据coinmarketcap报价显示,比特币站上8000美元,24小时内上涨14.68%。 [13]  6月22日 ,比特币价格突破10000美元大关。比特币价格在10200左右震荡,24小时涨幅近7%。 [14]  6月26日,比特币价格一举突破12000美元,创下自去年1月来近17个月高点。 [15]  6月27日早间,比特币价格一度接近14000美元,再创年内新高。 [16]  2020年2月10日,比特币突破了一万美元。据交易数据,比特币的价格涨幅突破3% [17]  。3月12日,据加密货币交易平台Bitstamp数据显示,19点44分,比特币低价格已跌至5731美元 [18]  。5月8日,比特币突破10000美元关口,创下2月份以来的新高 [19]  。5月10日早上8点开始,比特币单价在半小时内从9500美元价位瞬间下跌了上千美元,低价格跌破8200美元,高价差超1400美元 [20]  。7月26日下午6点,比特币短时极速拉升,高触及10150.15USDT,日内大涨幅超过4%,这是2020年6月2日以来首次突破1万美元关口 [21]  。11月4日,比特币价格正式突破14000美元 [22]  。11月12日晚,比特币价格突破16000美元,刷新2018年1月以来新高,一周涨超8.6%。比特币总市值突破2915亿美元 [23]  。11月18日,比特币价格突破17000美元 [24]  。12月1日,比特币价格报19455.31美元,24小时涨幅为5.05%。 [25]  12月17日,比特币价格突破23000美元整数关口,刷新历史新高,日内涨幅超7.5%。 [26]  截至12月27日19时20分,比特币报价28273.06美元。 [27]  2021年1月8日,比特币涨至4万美元关口上方,高至40402美元

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值