Edit me

[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目录下数据结构和启动流程图: 07-1


发现相邻节点流程示例

下面示例说明了节点间是如何发现的,但是并没有actor的参与,这个和预想不符,并且需要在配置文件中配置它们的IP地址到”SeedList”字段,rpc这边有一个请求是可以获取节点的地址数据的,但是它是否可以通过节点间跨网络调用还需要后续确认。

07-2

Tags: