[TOC]
目录结构
pct@Chandler:~/go/src/github.com/ontio/ontology/net$ tree
.
├── actor
│ ├── consensus.go
│ ├── ledger.go
│ ├── net_server.go
│ └── txnpool.go
├── message
│ ├── address.go
│ ├── block.go
│ ├── blockHdr.go
│ ├── consensus.go
│ ├── inventory.go
│ ├── message.go
│ ├── not_found.go
│ ├── ping.go
│ ├── pong.go
│ ├── transaction.go
│ ├── verack.go
│ └── version.go
├── net.go
├── node
│ ├── event_notice.go
│ ├── id_cache.go
│ ├── info_update.go
│ ├── link.go
│ ├── node.go
│ └── node_map.go
└── protocol
└── protocol.go
4 directories, 24 files
net目录是实现网络通信的部分,它引用了本体的通信库: https://github.com/ontio/ontology-eventbus 所以我们需要先了解一下eventbus的实现机制。 —
eventbus
eventbus库是从 https://github.com/AsynkronIT/protoactor-go 库fork而来,它们都是使用actor并行通信的例子,所以我们先对Actor模型有个基础认知。
actor模型
Actor是计算机科学领域中的一个并行计算模型,它把actors当做通用的并行计算原语:一个actor对接收到的消息做出响应,进行本地决策,可以创建更多的actor,或者发送更多的消息;同时准备接收下一条消息。 在Actor理论中,一切都被认为是actor,这和面向对象语言里一切都被看成对象很类似。但包括面向对象语言在内的软件通常是顺序执行的,而Actor模型本质上则是并发的。 每个Actor都有一个(只有一个)Mailbox。Mailbox相当于是一个小型的队列,一旦Sender发送消息,就是将该消息入队到Mailbox中。入队的顺序按照消息发送的时间顺序。Mailbox有多种实现,默认为FIFO。但也可以根据优先级考虑出队顺序,实现算法则不相同。 actor的特点: actor的mailbox容量是无限的,不会造成写入时的阻塞 每个actor中所有消息共用一个mailbox(channel)。 actor并不关心消息的发送方(writer),可以对各模块间的逻辑进行解耦合。 actor可以部署在不同节点上。 因为Actor被设计为异步模型,同步调用的性能不高。
源码例程
我们找到Ledger中的actor创建过程来展示actor的使用: /home/pct/go/src/github.com/ontio/ontology/core/ledger/actor/actor.go
var DefLedgerPid *actor.PID //pid是每个Actor的唯一标识,通信时需要使用这个id作为参数
type LedgerActor struct { //定义一个数据类型,这个类型需要实现Receive方法
props *actor.Props //相当于线程的句柄,我们需要根据这个值生成pid
}
func (self *LedgerActor) Start() *actor.PID {
self.props = actor.FromProducer(func() actor.Actor { return self }) //通过FromProducer方法生成一个actor对象
DefLedgerPid, err = actor.SpawnNamed(self.props, "LedgerActor") //通过对象生成pid,用此pid通信
return DefLedgerPid
}
func (self *LedgerActor) Receive(ctx actor.Context) { //要实现actor就需要继承这个方法,通过断言判断收到的数据类型
switch msg := ctx.Message().(type) {
case *actor.Started:
如上代码所述,生成actor的过程很简单,两个方法即可生成,我们在Receive中实现自己想要实现的逻辑,它会在收到消息时被actor调用,pid是进行通信的钥匙。actor既可以进行同步通信也可以进行异步通信,既可以在进程内通信,也可以在进程间通信,进程间通信需要借助其他工具实现,在eventbus中实现了两种方式,protobuf和zeromq。 —
net的main函数调用
看完actor的机制后我们就能理解net模块在main中被调用的作用了:
net.SetLedgerPid(ledgerPID) //设置pid
net.SetTxnPoolPid(txPoolServer.GetPID(tc.TxActor)) //设置pid
noder = net.StartProtocol(acct.PublicKey) //启动net模块
p2pActor, err := net.InitNetServerActor(noder) //添加Actor
net.SetConsensusPid(consensusService.GetPID()) //设置pid
noder.SyncNodeHeight() //启动同步
noder.WaitForPeersStart() //等待节点启动,blf模式需要连接到四个节点才能启动
noder.WaitForSyncBlkFinish() //等待节点同步完成
net设置了账本的pid–ledgerPID,交易池的pid–TxnPoolPid,以及共识机制模块的pid,这就意味这三个模块会有和外部或者内部通信的需求。start和init这部分我们放后面再去读,首先梳理一下内部的数据结构。 —
net的逻辑分析
从梳理代码看出,net部分主要做的事情有这么几个:节点同步,数据同步,内部通信。 这几个功能都是通过actor机制来完成的,通过不同的消息类型完成多态的映射。 下面图表是net目录下数据结构和启动流程图:
发现相邻节点流程示例
下面示例说明了节点间是如何发现的,但是并没有actor的参与,这个和预想不符,并且需要在配置文件中配置它们的IP地址到”SeedList”字段,rpc这边有一个请求是可以获取节点的地址数据的,但是它是否可以通过节点间跨网络调用还需要后续确认。