基础数据结构
1 | type IpfsDHT struct { |
这里简单的回顾一下host.Host与pstore.Peerstore提供的方法
这是host提供的方法,主要还是网络连接,协议处理相关的内容
1 |
|
peerstore提供的方法就简单很多,主要还是返回一些Peer相关的基础信息
1 | type Peerstore interface { |
基本方法
接下来我们从KAD网络的基础消息类型入手分析
PING
此处暂时没有发现libp2p中的实现
STORE
ipfs中的store不是简单的在一个节点中进行store操作,它会找到最临近的k个节点,然后执行这个操作
1 | func (dht *IpfsDHT) PutValue(ctx context.Context, key string, value []byte, opts ...ropts.Option) (err error) { |
这里可以看到libp2p在执行操作的时候会去调用
1 | notif.PublishQueryEvent(ctx, ¬if.QueryEvent{ |
目前暂时还没看出来这个QueryEvent的作用和目的。这个是接下来需要关注的问题和点了。
FIND_NODE
此处的目标是在当前p2p网络中找到指定id的peer
1 | func (dht *IpfsDHT) FindPeer(ctx context.Context, id peer.ID) (_ pstore.PeerInfo, err error) { |
这里的query.Run的迭代查找的实现非常有意思,最终的效果是能在迭代的peer桶中找到所需要的peer列表
FIND_VALUE
此处的目标是在p2p网络中找到指定的value
1 |
|
这里VALUE
的更新只会对于以前存储了这个VALUE
的节点。这个是挺有趣的故事,按照论文的说法会将其它临近的节点缓存上,这里有些存疑。
大部分的操作都是基于上述的查询过程实现的。为了存储一个<key, value>对,一个参与者定位出k个最接近key的节点,然后向它们发送STORERPC请求。另外,每个节点在需要保活的时候,都会重新发布<key, value>对,这个会在之后说。这种做法会保证<key, value>对有很大可能持久化。对于现在的Kademlia应用(比如文件共享),我们还会要求<key, value>对的原始发布者每隔24小时再重新发布一次。因为,为了限制系统中不可用的索引信息不至于太多,一个<key, value>对,在发布24小时后,默认就会被设为过期。对于其它的应用,比如数字证书或用于值映射的加密哈希,更长的过期时间也许会更合适。
要找到一个<key, value>对,一个节点先开始找到最接近这个key的k个节点。但是,值查找用的是FIND_VALUE而不是FIND_NODERPC。并且,只要有一个节点返回了value,查找过程就立即停止。为了缓存,一旦一个查找成功,请求节点将会在它发现的,那些最接近key的,但又没有存储这个<key, value>对的节点上存储这个键值对。
这里在关注完成kad网络的基本操作后,其他的操作暂时先不再赘述,我们开始考虑如何在使用kad-dht包来实现一个p2p发现网络。
实现思路
整体来说我们先考虑验证,再考虑实际的实现
demo模型
整体要实现的demo的场景很简单,就是一个3个节点的发现网络
验证
这里要解决的是如何kad网络中才能正常
这里我们需要参考以太坊的devp2p的使用方法来实现一个基础的kad发现网络
- 启动3个KAD节点
- 通过KAD节点来进行发现和通信
下面是我们最终实现的代码
1 |
|
如上的demo实际上实现的就是节点的发现与通信的功能
整体实验进行如下:
- 首先启动节点1
- 接下来启动节点2,设置节点2的bootnode节点为节点1
- 最后启动节点3,设置节点3的bootnode节点为节点1,并且在节点3上尝试findnode节点2
最终效果图如下
- 节点1输出
- 节点2输出
- 节点3输出
可以看到,节点3通过k桶中的节点1的地址最终成功的发现到了节点2
总结
整个libp2p的kad网络的使用是非常有趣的,kad包其实本质上只去维护了k桶的关系以及实现了kad网络的基本操作,真正网络层的消息处理与地址维护还是在host层中,在我们的demo中虽然实现了一个小的p2p网络,可以节点之间相互发现,但是这个还不能算是一个真正的p2p网络,因为这个发现还是手动的,缺少一个任务调度层去自发的调度各种类型的任务。再之后的文章中我们会去讨论如何来调度这些任务。