ipfs pubsub代码解读

Pubsub: Publish-subscribe发布订阅模式

运行环境

版本:go-ipfs@v0.4.23 go-libp2p-pubsub@v0.0.3

本文运行两个节点,一个在ubuntu,另外一个在windows,下文用ipfs1代表ubuntu端的ipfs,用ipfs2代表windows端的ipfs。两个节点将彼此的地址添加到彼此的bootstrap中,形成由2个节点组成的测试网。

两者的peerID分别为:

ipfs1: QmUB36eFCLEN4PvwSQaJ2tsEBr9epTm5h1rATuY11baZ6o
ipfs2: Qmco9fPhEC9aYsFxY3ZekoUMZucmNa9soWDXh6xgr6FsJy

两个节点启动daemon的时候都添加参数:–enable-pubsub-experiment

$ ipfs daemon --enable-pubsub-experiment

两个节点都将bitswap/dht/namesys/pubsub的log等级设置为debug

$ ipfs log level bitswap debug & ipfs log level dht debug & ipfs log level namesys debug & ipfs log level pubsub debug

ipfs pubsub命令实操

$ ipfs pubsub
USAGE
  ipfs pubsub - An experimental publish-subscribe system on ipfs.

  ipfs pubsub

  ipfs pubsub allows you to publish messages to a given topic, and also to
  subscribe to new messages on a given topic.
  
  This is an experimental feature. It is not intended in its current state
  to be used in a production environment.
  
  To use, the daemon must be run with '--enable-pubsub-experiment'.

SUBCOMMANDS
  ipfs pubsub ls                    - List subscribed topics by name.
  ipfs pubsub peers [<topic>]       - List peers we are currently pubsubbing with.
  ipfs pubsub pub <topic> <data>... - Publish a message to a given pubsub topic.
  ipfs pubsub sub <topic>           - Subscribe to messages on a given topic.

  For more information about each command, use:
  'ipfs pubsub <subcmd> --help'

首先ipfs2订阅"phone"这个topic

$ ipfs pubsub sub phone

接着ipfs1 publish", topic为"phone",内容为"Moring"

$ ipfs pubsub pub phone "Moring"

最后看ipfs2的终端

$ ipfs pubsub sub phone
Moring

在ipfs2中另外开一个终端,查询节点当前订阅的topic

$ ipfs pubsub ls
phone

ipfs1查找订阅"phone"这个topic的peer,如果topic为空,默认是查找所有的topic

ipfs pubsub peers phone
QmUB36eFCLEN4PvwSQaJ2tsEBr9epTm5h1rATuY11baZ6o

代码讲解

ipfs config文件中关于pubsub的默认配置

"Pubsub": {
    "DisableSigning": false,
    "Router": "",
    "StrictSignatureVerification": false
  }

DisableSigning为false,表示本节点publish消息时需要签名,StrictSignatureVerification为false,表明接受不带签名的publish消息。

Router有3种,默认使用的是FloodSubRouter,RandomSubRouter目前没有被ipfs使用。

FloodSubRouter
GossipSubRouter
RandomSubRouter

顾名思义,Flood是洪泛的意思,即向所有订阅节点发布(publish);gossip是私语的意思,即向6个订阅节点发布;random是随机的意思,即随机向6个订阅节点发布。
值得注意的是,FloodSubRouter是基础协议,GossipSubRouter和RandomSubRouter都支持FloodSubRouter。

显然,GossipSubRouter最好用,也最复杂,接下来主要讲解GossipSubRouter

GossipSubRouter

GossipSubRouter结构:

type GossipSubRouter struct {
	p       *PubSub
	peers   map[peer.ID]protocol.ID         // peer protocols
	mesh    map[string]map[peer.ID]struct{} // topic meshes
	fanout  map[string]map[peer.ID]struct{} // topic fanout
	lastpub map[string]int64                // last publish time for fanout topics
	gossip  map[peer.ID][]*pb.ControlIHave  // pending gossip
	control map[peer.ID]*pb.ControlMessage  // pending control messages
	mcache  *MessageCache
}

GossipSub以topic为单位维护两个网络,分别为mesh和fanout,两者都包含订阅该topic的节点,如果自身订阅了一个topic,则mesh[topic]不为空,fanout为空;如果没有订阅,则mesh[topic]为空。

publish过程

publish的时候,首先会判断自己有没有订阅这个topic,即判断mesh[topic]是否为空,如果不为空,就向mesh[topic]中的节点发送publish消息;如果为空,就判断fanout[topic]是否为空,如果不为空,就向fanout[topic]中的节点发送publish消息;如果为空,就从pubsub.topic[topic]中向fanout[topic]补充节点至多6个节点,最后向这些节点发送publish消息。

其它节点收到publish消息后,会首先判断是否已经收到过这个publish消息,如果已经收到就将其丢弃;如果之前没收到,首先校验消息(如果publish消息附带签名,就检验签名),除了推送给本地的订阅了topic的对象,还会将该消息原封不动publish出去,过程同上,以此完成消息传递过程,这是一个典型的gossip过程。

为了回收空间,对于一个topic,如果1分钟不publish,就会delete fanout[topic]。

go-libp2p-pubsub/gossipsub.go

	// overlay parameters
	GossipSubD   = 6
	GossipSubDlo = 4
	GossipSubDhi = 12

	// gossip parameters
	GossipSubHistoryLength = 5
	GossipSubHistoryGossip = 3

	// heartbeat interval
	GossipSubHeartbeatInitialDelay = 100 * time.Millisecond
	GossipSubHeartbeatInterval     = 1 * time.Second

	// fanout ttl
	GossipSubFanoutTTL = 60 * time.Second

Subscribe过程

当Subscribe一个topic时,首先会判断自己有没有订阅这个topic,即判断pubsub.myTopics[topic]是否为空,如果不为空,表明自己已经订阅,直接return; 如果为空,则join gossip mesh,首先mesh[topic]是否为空,如果不为空,表明之前已经join了,直接return; 如果为空,就判断fanout[topic]是否为空,如果不为空,表明自己在1min内publish过这个topic,则将fanout[topic]移出到mesh[topic],并delete fanout[topic]。 如果为空,从pubsub.topics[topic]补充6个节点到mesh[topic],并且向这些补充进来的节点发送iGraft消息,告诉它我把它加入到mesh[topic],其它节点收到iGraft消息后,也会把我加入到mesh[topic]中。

取消订阅时,首先会判断自己有没有订阅这个topic,即判断mesh[topic]是否为空,如果为空,表明自己没有订阅,直接return;如果不为空,表明自己已经订阅,则删除mesh[topic],并且向那些被剔除的节点发送iPrune消息,告诉它我把它从mesh[topic]网络剔除了,其它节点收到iPrune消息后,也会把我从mesh[topic]剔除。

消息交换&网络控制

为了确保网络节点在线,gossip每1S就会发送一个心跳包,以维护网络。心跳包的另外一个功能是交换信息,如iHave、iGraft和iPrune。

其它节点收到心跳包后,会判断iHave是否有自己想要的消息,如果有,那么向其发送iWant消息。

gossipSub每1S都会检查mesh[topic]的节点个数,如果少于6个节点则从pubsub.topics[topic]中补充,并且向这些补充进来的节点发送iGraft消息,告诉它我把它加入到mesh[topic],其它节点收到iGraft消息后,也会把我加入到mesh[topic]中;

如果多于6个节点,就会随机剔除多余的节点,并且向那些被剔除的节点发送iPrune消息,告诉它我把它从mesh[topic]网络剔除了,其它节点收到iPrune消息后,也会把我从mesh[topic]剔除。

RPC消息

pubsub使用RPC通信

// pubsub.go
type RPC struct {
	pb.RPC

	// unexported on purpose, not sending this over the wire
	from peer.ID
}

// rpc.pb.go
type RPC struct {
	Subscriptions        []*RPC_SubOpts  `protobuf:"bytes,1,rep,name=subscriptions" json:"subscriptions,omitempty"`
	Publish              []*Message      `protobuf:"bytes,2,rep,name=publish" json:"publish,omitempty"`
	Control              *ControlMessage `protobuf:"bytes,3,opt,name=control" json:"control,omitempty"`
}

type RPC_SubOpts struct {
	Subscribe            *bool    `protobuf:"varint,1,opt,name=subscribe" json:"subscribe,omitempty"`
	Topicid              *string  `protobuf:"bytes,2,opt,name=topicid" json:"topicid,omitempty"`
}

type Message struct {
	From                 []byte   `protobuf:"bytes,1,opt,name=from" json:"from,omitempty"`
	Data                 []byte   `protobuf:"bytes,2,opt,name=data" json:"data,omitempty"`
	Seqno                []byte   `protobuf:"bytes,3,opt,name=seqno" json:"seqno,omitempty"`
	TopicIDs             []string `protobuf:"bytes,4,rep,name=topicIDs" json:"topicIDs,omitempty"`
	Signature            []byte   `protobuf:"bytes,5,opt,name=signature" json:"signature,omitempty"`
}

type ControlMessage struct {
	Ihave                []*ControlIHave `protobuf:"bytes,1,rep,name=ihave" json:"ihave,omitempty"`
	Iwant                []*ControlIWant `protobuf:"bytes,2,rep,name=iwant" json:"iwant,omitempty"`
	Graft                []*ControlGraft `protobuf:"bytes,3,rep,name=graft" json:"graft,omitempty"`
	Prune                []*ControlPrune `protobuf:"bytes,4,rep,name=prune" json:"prune,omitempty"`
}

type ControlIHave struct {
	TopicID              *string  `protobuf:"bytes,1,opt,name=topicID" json:"topicID,omitempty"`
	MessageIDs           []string `protobuf:"bytes,2,rep,name=messageIDs" json:"messageIDs,omitempty"`
}

type ControlIWant struct {
	MessageIDs           []string `protobuf:"bytes,1,rep,name=messageIDs" json:"messageIDs,omitempty"`
}

type ControlGraft struct {
	TopicID              *string  `protobuf:"bytes,1,opt,name=topicID" json:"topicID,omitempty"`
}

type ControlPrune struct {
	TopicID              *string  `protobuf:"bytes,1,opt,name=topicID" json:"topicID,omitempty"`
}

RPC_SubOpts封装的是订阅信息,即是否订阅某一topic。当有新peer连接的时候,ipfs1会和新peer握手(Helo),握手内容是Subscriptions,即当前订阅的topic。其它节点收到ipfs1发过来的hello(rpc)后,会把其peerID加入到pubsub.topics[topic]中,以便后续publish或者Subscribe该topic的时候,找得到订阅的peer。

Message封装的是publish消息,其中From是publisher;Data是内容数据;Seqno是序列号,用于标识版本,每次publish,Seqno加1(pubsub.counter++);TopicIDs是topic集;Signature是签名。

ControlMessage封装的是控制消息,分别是IWant, IHave、IGraft和IPrune, 其中后3者以topic为单位。

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值