🔥码云GVP开源项目 12k star Uniapp+ElementUI 功能强大 支持多语言、二开方便! 广告
# Host、Peer与Node host即主机,是p2p网络的参与对象,由于既处理请求和发送请求,所以一个host既是服务器也是客户端。host概念容易与peer概念混淆,其type是一个interface,包括一系列需要实现的方法,其中ID()方法返回的对象类型为peer.ID(在peer.go)中定义。 我们在启动一个节点的时候,必然要创建一个host:host是节点Node结构的一个变量。 peer对应于ID,其由创建host时的键值对的publickey生成而来,因此,我们可以根据peer.ID与host.ID()比较,判断本地主机与对等端是否是同一个主机。 加入网络后的host与peer是相对的,每一个peer都是host,每一个host都是peer。站在某个node的角度,该node即为host,其它node即为peer。我们可以通过PubSub服务的ListPeers,来获取全网的[]peer.ID。 可以理解,host或peer由ID唯一标识,ID在host创建时候根据同时创建的公钥生成。 node即节点,更准确的称呼为网络节点,我们在实现的时候,直接以network结构体来定义它,其结构包括host在内的多个变量,区块链运行从启动一个一个节点开始。 host是一个类型对象(type object),实现了作为一个既是服务器也是客户端的节点的P2P网络所需的处理方法和参数配置,node是一个结构体(struct),peer等同于ID。 每个peer在加入P2P网络之前也是一个host,host在创建完毕加入网络后成为一个peer。 网络中的每一个peer的功能可能不同(由加入网络之前创建host确定),host自身知道自己具备什么功能,虽然可以随时获取全网的peer,但并不知道其它peer所具备的功能及内容,也就是一个host只知道一个peer的ID,除此之外,一概不知,这也体现了P2P网络的匿名特性。一个host想要什么,通过通信通道向全网发出命令消息,或者在成功连接到一个peer后,发送命令消息给对方,由该peer给出响应,才能管窥一个peer的部分功能和内容。 我们实际进行编程交互的时候,是以node为对象进行处理的,一切从节点的startnode方法开始(返回一个node对象:*Network): ``` func StartNode(chain *blockchain.Blockchain, listenPort, minerAddress string, miner, fullNode bool, callback func(*Network)) ``` # Channel 通信通道 Channel结构体包含一个chan的通道Content(遵循先进先出的规则)。 ``` type Channel struct {     ctx   context.Context     pub   *pubsub.PubSub//发布     topic *pubsub.Topic     sub   *pubsub.Subscription//订阅     channelName string     self        peer.ID     Content     chan *ChannelContent//有缓冲的非阻塞通道,通道大小ChannelBufSize=128 } ``` 在创建host过程中,创建所有的通信通道,并将其加入到pubsub服务中(在join中启动无限循环,读取订阅的消息),当读到订阅的消息后,将消息体写入到自身的通道队列中。 在启动一个node的最后: ui.Run(network) 在Run中,开启一个协程: go ui.handleEvents(net) 在handleEvents中,使用一个无限循环,读取通道的队列消息进行处理: ``` case m := <-ui.GeneralChannel.Content: ui.HandleStream(net, m) casem := <-ui.MiningChannel.Content:             ui.HandleStream(net, m) casem := <-ui.FullNodesChannel.Content:             ui.HandleStream(net, m) ``` # pubsub 订阅发布服务系统 pubsub服务是P2P网络节点之间交互的关键。基于发布/订阅的模式,将消息的发送和处理分离,使得对等网络节点间的交互成为可能。 任何一个主机(节点或对端),同时都是一个订阅发布服务系统。但一个主机可能同时具备订阅和发布的功能,也可能只有发布的功能而没有订阅的功能,这在创建主机时候确定。 libp2p的pubsub是一个基于Gossip协议的消息路由器。 Gossip 过程是由种子节点发起,当一个种子节点有状态需要更新到网络中的其他节点时,它会随机的选择周围几个节点散播消息,收到消息的节点也会重复该过程,直至最终网络中所有的节点都收到了消息。这个过程可能需要一定的时间,由于不能保证某个时刻所有节点都收到消息,但是理论上最终所有节点都会收到消息,因此它是一个最终一致性协议。 Gossip protocol 也叫 Epidemic Protocol (流行病协议),实际上它还有很多别名,比如:“流言算法”、“疫情传播算法”等。基于六度分隔理论,任何信息的传播其实非常迅速,而且网络交互次数不会很多。比如Facebook在2016年2月4号做了一个实验:研究了当时已注册的15.9亿使用者资料,发现这个神奇数字的“网络直径”是4.57,翻成白话文意味着每个人与其他人间隔为4.57人。 **Gossip 中的通信模式** 在 Gossip 协议下,网络中两个节点之间有三种通信方式: * Push: 节点 A 将数据 (key,value,version) 及对应的版本号推送给 B 节点,B 节点更新 A 中比自己新的数据 * Pull:A 仅将数据 key, version 推送给 B,B 将本地比 A 新的数据(Key, value, version)推送给 A,A 更新本地 * Push/Pull:与 Pull 类似,只是多了一步,A 再将本地比 B 新的数据推送给 B,B 则更新本地 如果把两个节点数据同步一次定义为一个周期,则在一个周期内,Push 需通信 1 次,Pull 需 2 次,Push/Pull 则需 3 次。虽然消息数增加了,但从效果上来讲,Push/Pull 最好,理论上一个周期内可以使两个节点完全一致。直观上,Push/Pull 的收敛速度也是最快的。 **Gossip 安全增强** 对于大型区块链用户来说,重点是稳定性、可扩展性和安全加固。 为此,Gossipsub 协议设计并实现了新版本 v1.1 ([https://blog.ipfs.io/2020-05-20-gossipsub-v1.1/](https://blog.ipfs.io/2020-05-20-gossipsub-v1.1/))。这个新的 P2P PubSub 路由器包含了几个安全扩展,增加了对 Sybil、eclipse 和垃圾邮件攻击的保护。 这项工作极为重要,因为大型区块链的采用者(如 Filecoin 和 Ethereum 2.0)需要一个安全的消息层来分发他们的时间敏感和有价值的数据,而不容易受到恶意行为者的攻击。