近期在学习以太坊的源码,先是看p2p网络,因此开个大坑,来介绍一下学习所得。若是有什么问题,欢迎提出交流与指导。
0.索引
01.什么是p2p网络
02.以太坊的p2p网络结构
03.以太坊p2p网络的函数入口
1.什么是p2p网络
p2p,peer to peer,对等网络。即网络中的每个节点都是相同的。
C/S模式vsP2P
C/S模式
先来看一下非p2p的网络,即传统的C/S模式,也就是客户端发送请求与使用服务,服务器满足这些请求并提供服务。资源存储在中心的服务器上。
中间的节点为服务器节点,周围的节点为客户端节点,每个客户端都需要连接到服务器才能使用该服务器提供的服务。这种模式很方便,易于管理,只需要维护服务器节点即可。但是如果服务器出现一点问题,就会导致一段时间内,所有的客户端都无法使用服务,而且可能会随着客户端节点数量增大,而导致服务器的负担增大,降低效率。
p2p
在p2p网络中,如下图,两个节点相互连接的机会是一样的,任意两个节点都能相互连接,以及进行信息的传递,也就是,每个节点既可以是服务的发起方,也可以是服务的接收方。资源分布在各个节点之上。
需要查找资源的时候,只需要找到存放该资源的节点即可。由于资源分散的存放在节点上,所以资源的请求不会唯一指向某个节点,减缓了单个节点的负担。某个节点离开网络,并不会导致服务的停止,因为缓存的技术可以使其他的节点也拥有该节点相同的资源。不过p2p网络相对而言不好管理维护,因为所有的节点都是参与方。
在p2p网络的基础上,使得区块链有一个特性:去中心化。
p2p网络又可分为无结构化与结构化。
-
无结构化。
优点:无结构化的网络容易搭建,可扩展性较强,特别是在大量的节点经常加入和离开网络的时候,无结构化的p2p网络都能轻松应对。缺点:由于缺乏结构,节点在网络中查询数据的时候,需要通过泛洪的形式,找到拥有该数据的节点。泛洪指的是全网广播的方式,先是某个节点向它的邻居节点广播消息,邻居节点收到消息后再向自己的邻居节点广播,直到整个网络知晓这个消息,有点类似于石块落入湖泊中,一圈一圈荡漾开去的涟漪。泛洪会导致网络中的信息流量太大,而使用较多的计算机资源,并且,不能保证最终能查询到数据(如果一开始数据就不存在于网络中)。
-
结构化。
优点:节点以一定的规则相邻形成某种结构(树形或环形),使用基于DHT(分布式哈希表)的技术。结构化的网络一般节点和资源都有id的概念,节点id唯一指向该节点,资源id可由资源名的哈希得到,指向该资源。这样的做法在网络中定位了节点和资源。再将节点id与资源id进行一定关系的映射,(比如说两个id取一样的格式,一样的长度),就可以高效查询到所需要的资源。缺点:结构化需要维护满足特定条件的相邻节点列表(即形成某种结构),如果发生大量节点经常加入和离开网络的情况,可能会导致更多的节点也失去连接,需要再重新形成结构,增加网络通信开销。
2.以太坊的p2p网络结构
对于区块链的去中心化网络,一般来说,有结构的网络,相对效率较高,但是容易遭受攻击,或者某一主要节点出现问题而影响性能。无结构的网络,相对安全性高,但是效率低一些。
对此,以太坊做了折中的处理,底层的节点发现使用了结构化的kad算法,而上层的节点广播是以无结构的形式进行。(后面会做详细一点的介绍。)
以下是以太坊p2p网络的大致流程图:
由上图,以太坊p2p网络的源码分析大致分为:
- 底层网络:p2p网络是怎么构建的,即节点发现,建立连接的部分。
- 上层网络:区块和交易是怎么进行广播的,
peer
和peer
如何进行数据同步的。 - 底层网络与上层网络如何建立联系。
3.以太坊p2p网络的函数入口
(使用的版本是go-ethereum-1.8)
最开始的启动函数是node/node.go
里的Start
,如下
func (n *Node) Start() error {
// 将 Node.config 的p2p的配置置入n.serverConfig中
...
// 新建一个p2p模块里的Server对象running
running := &p2p.Server{Config: n.serverConfig}
n.log.Info("Starting peer-to-peer node", "instance", n.serverConfig.Name)
// 在running中加入协议
...
for _, service := range services {
running.Protocols = append(running.Protocols, service.Protocols()...)
}
// 启动p2p服务
if err := running.Start(); err != nil {
return convertFileLockError(err)
}
// 启动每一个服务
...
if err := service.Start(running);
...
// 最后启动RPC接口
if err := n.startRPC(services);
...
}
- 先加入p2p网络的配置信息。
- 新建一个
Server
对象,Server
是p2p模块最核心的数据结构,管理底层p2p网络的整个流程。 - 收集每一个服务的协议,并在
Server
对象里加入这些协议。 - 启动p2p,即开始构建底层网络。
- 启动服务。例如,
ethereum
服务,这是一个以太坊的全节点服务,管理上层peer
的ProtocolManager
就包含在这个ethereum
服务里。 - 最后启动RPC接口。
上述的Server
对应的是底层的网络,ProtocolManager
对应的是上层的网络。接下来先介绍底层网络,即从Server
开始。(Server
在p2p.server.go
中)
4.后续链接
后续完成的会在此处放上链接。
[p2p网络02]:启动底层网络以及监听TCP连接
[p2p网络03]:发起TCP连接请求
[p2p网络04]:基于UDP的节点发现
[p2p网络05]:底层节点如何与上层节点联系
[p2p网络06]:交易广播和区块广播
[p2p网络07]:同步区块和交易