Published: 2019-08-26

BTCD源码阅读笔记-connmgr

btcd 是一个golang实现的比特币全节点。

本文是阅读其connmgr包代码的笔记(有些潦草),分析其主要结构。

基于git代码版本: 16327141da8ce4b46b5bac57ba01352943465d9e

Table of Contents

1 connmgr模块

connmgr用来处理节点的连接,定义了一个 ConnManager ,用来管理request connect的连接、断开、失败、重试、限制最大连接数、重试次数等情况。

1.1 主要结构

// ConnManager provides a manager to handle network connections.
type ConnManager struct {
        // The following variables must only be used atomically.
        connReqCount uint64  // 管理的连接数
        start        int32  // 启动标识
        stop         int32 // 停止标志

        cfg            Config  // 配置
        wg             sync.WaitGroup  // 并发连接管理
        failedAttempts uint64  // 记录失败次数,如果成功连接则归0
        requests       chan interface{}  // 用于消息传递
        quit           chan struct{} // 用于退出
}


// ConnReq is the connection request to a network address. If permanent, the
// connection will be retried on disconnection.
type ConnReq struct {
        // The following variables must only be used atomically.
        id uint64  // 连接id,采用connReqCoount +1 的方式生成

        Addr      net.Addr // 连接地址
        Permanent bool  // 是否是持久连接

        conn       net.Conn
        state      ConnState // 记录该连接的状态
        stateMtx   sync.RWMutex
        retryCount uint32   // 记录持久连接的失败重试次数,如果成功连接则归0
}


// Config holds the configuration options related to the connection manager.
type Config struct {
        // Listeners defines a slice of listeners for which the connection
        // manager will take ownership of and accept connections.  When a
        // connection is accepted, the OnAccept handler will be invoked with the
        // connection.  Since the connection manager takes ownership of these
        // listeners, they will be closed when the connection manager is
        // stopped.
        //
        // This field will not have any effect if the OnAccept field is not
        // also specified.  It may be nil if the caller does not wish to listen
        // for incoming connections.
        Listeners []net.Listener

        // OnAccept is a callback that is fired when an inbound connection is
        // accepted.  It is the caller's responsibility to close the connection.
        // Failure to close the connection will result in the connection manager
        // believing the connection is still active and thus have undesirable
        // side effects such as still counting toward maximum connection limits.
        //
        // This field will not have any effect if the Listeners field is not
        // also specified since there couldn't possibly be any accepted
        // connections in that case.
        OnAccept func(net.Conn)

        // TargetOutbound is the number of outbound network connections to
        // maintain. Defaults to 8.
        TargetOutbound uint32

        // RetryDuration is the duration to wait before retrying connection
        // requests. Defaults to 5s.
        RetryDuration time.Duration

        // OnConnection is a callback that is fired when a new outbound
        // connection is established.
        OnConnection func(*ConnReq, net.Conn)

        // OnDisconnection is a callback that is fired when an outbound
        // connection is disconnected.
        OnDisconnection func(*ConnReq)

        // GetNewAddress is a way to get an address to make a network connection
        // to.  If nil, no new connections will be made automatically.
        GetNewAddress func() (net.Addr, error)

        // Dial connects to the address on the named network. It cannot be nil.
        Dial func(net.Addr) (net.Conn, error)
}

1.2 主要方法

1.2.1 start


1. go connHandler()
   启动处理连接的goroutine
2. go listenHandler()
   启动config里注册的listeners
3. go NewConnReq()

1.2.2 connHandler


pending map: 记录pending状态的连接信息
conns map : 记录连接状态的连接信息

for/select:
1. req := <-cm.requests:
   1. registerPending  表示 有待发生待连接请求
      如何处理:
      1.记录该请求到一个pending map中
   2. handleConnected  表示 连接建立
      如何处理:
      1.确保在pending map中添加过该连接
      2.pengding map删除条目, conns map中添加连接条目
      3.调用manager config配置的OnConnection方法

   3. handleDisconnected 表示 连接断开
      如何处理:
      1.确保在pending或者conns map中添加过该连接,并删除相应条目
      2.调用manager config配置的OnDisconnection方法
      3.根据 该断开的消息 是否需要重试分开处理
        1. 不需要的话,更新状态就结束
        2. 需要的话,在一定条件下将连接加到pending map里,然后调用handleFailedConn
   4. handleFailed 表示 失败情况
      1.确保在pending map中添加过该连接
      2.更新连接状态,然后调用handleFailedConn

2. <-cm.quit
   退出

1.2.3 handleFailedConn


该方法用来处理连接断开或者其他连接失败的情况
如果连接是持久连接,就在等待一段时间后尝试重连
如果不是,就建立一个新的连接,当重试的次数超过最大值后,那就等待一段时间再重新建立新连接。

1.2.4 listenHandler

该方法等待连接,然后调用config里的OnAccept来处理连接

1.2.5 NewConnReq


该方法创建一个新连接,连接到相应的地址。
具体逻辑:
连接数connReqCount+1
发送registerPending消息(由connHandler接收处理)
connHandler处理完register后,调用config的GetNewAddress方法获取新的地址addr
如果获取失败,发送handleFailed方法(由connHandler接收处理)
获取addr成功后,调用connect方法来获取连接

1.2.6 Connect


如果(根据连接id判断)该地址是个新连接,那么设置其id,然后注册到pending map连接里。
调用config里的Dial方法,连接到addr, 如果成功则发送handleConnected方法, 失败则发送handleFailed方法到cm.requests, 由connHandler处理

1.2.7 Disconnect

发送handleDisconnected消息给cm.requests

1.2.8 Stop

该方法先将config里的listeners调用close()
在调用close(cm.quit)发送退出消息告知各个goroutine

1.3 消息传递简图

只有一个cm.requests channel,通过传递不同的msg来让connhandler处理,整体还是比较简明清晰,故不作图了。

2 End

Author: Nisen

Email: imnisen@163.com