1、P2P网络节点发现流程
设定每次节点启动需要发现的节点数最小为N,最大值为M,节点ID为160位的二进制值。
根据Kademlia算法,假设启动的客户端(节点)为A,节点A的ID为“NodeIDA”,A启动后,发现其他节点的流程如下:
检测A的K桶下是否已经存在持久化的其他节点信息,如果存在,尝试能否ping-pong握手成功,如果可以,保留该节点。如果不可以,从K桶中删除该节点。如果此时节点A的K桶中没有节点,从种子节点列表中获取一个种子几点,将其节点ID写入K桶中。
如果K桶中节点数量小于N,启动节点发现。
节点A生成随机节点ID为“TargetNodeID”,该ID是一个160位的二进制值。
节点A的K桶中存在节点B,ID为“NodeIDB”。
记节点A与随机节点的距离DistanceTA= TargetNodeID ^ NodeIDA,^为异或运算符。
记节点B与随机节点的距离DistanceTB= TargetNodeID ^ NodeIDB,^为异或运算符。
如果DistanceTB<DistanceTA,说明节点B与随机几点的距离小于节点A与随机节点的距离。
节点B执行FindNode,比较节点B与B的K桶中的节点与随机节点的距离,查找比节点B距离随机节点更近的节点,将找到的节点发送给节点A,节点A按照与随机节点的距离依次写入K桶。
依次执行4-8的过程。
搜索时间超过600ms,停止搜索。
完成搜索后,使用距离随机节点最近的16个节点作为连接节点。
使用这种方式发现的节点会呈现一种随机性,使各个节点之间连接呈松散性分布。
2、代码实现
1、创建K桶对象,以太坊K桶按照与target节点距离进行排序,共20个Bucket桶,每个Bucket桶包含N个节点。K桶的结构如下图:
这里,K桶中Buckets数量为20。之所以这样设计是因为go语言包中sha1哈希算法生成的是长度为160位的二进制值,使用byte表示就是[20]byte,每个字节是8位二进制。
这样我们在计算过程中,不必要每个进制进行比较,只需要按照每个byte进行比较即可。
代码如下:
//Kad桶,存储所有bucket桶
type Kad struct{
mutex_n *sync.Mutex
mutex_a *sync.Mutex
nodes []*Node //连接的节点
currendNode *Node //当前节点
Buckets [HashBits/8]*bucket //K桶
asked []NodeID//存储Ping通的Nodeid
ticker *time.Ticker //定时器,用来定时刷新连接的节点
closed chan struct{}
tc chan Node
sc chan Node
}
//bucket桶,存储不同距离的节点
type bucket struct {
entries []*Node // 每个bucket中的节点
location int //bucket桶在K桶中的位置
}
//节点对象
type Node struct{
Addr string //节点Url地址,包括ip和端口号
Nodeid NodeID //节点编号
TargetID NodeID //随机节点编号
AddedAt time.Time //节点发现时间
index uint8 //节点在K桶中的位置
distance uint8 //记录每个节点距离目标节点的距离
}
这样的话,基本的K桶结构已经完成设计,下面我们逐步实现节点查找功能。
//创建K桶对象
func NewKad()*Kad{
kad:=Kad{
mutex_n:new(sync.Mutex),
mutex_a:new(sync.Mutex),
nodes:findNodesFromDB(),//持久化数据库中读取已经连接好的节点
currendNode:NewNode(Addr), //根据当前IP信息生成本地节点
ticker:time.NewTicker(refreshInterval),//节点刷新,这里设置30分钟执行一次
closed:make(chan struct{}),
tc:make(chan Node),
sc:make(chan Node),
}
log.Println("create new kad...")
go kad.checkNodes()
go kad.loop()
return &kad
}
如果该节点已经连接其他节点,为了防止重复读取,可以将已经连接的节点存入持久化数据中,方便下次使用。
节点刷新是为了防止连接过程有其他节点断开,所以需要定时刷新,如果连接的节点不足,需要重新发现其他新的节点。
K桶创建时会执行两个goruntime,checkNodes()用来检测当前可以正常连接的节点数量,如果数量不足,执行节点发现功能。
Loop函数执行刷新及其他goruntime功能。