剥开比原看代码02:比原启动后去哪里连接别的节点
作者:freewind
比原项目仓库:
Github地址:https://github.com/Bytom/bytom
Gitee地址:https://gitee.com/BytomBlockchain/bytom
比原启动后去哪里连接别的节点
最开始我对于这个问题一直有个疑惑:区块链是一个分布式的网络,那么一个节点启动后,它怎么知道去哪里找别的节点从而加入网络呢?
看到代码之后,我才明白,原来在代码中硬编码了一些种子地址,这样在启动的时候,可以先通过种子地址加入网络。虽然整个网络是分布式的,但是最开始还是需要一定的中心化。
预编码内容
对于配置文件config.toml,比原的代码中硬编码了配置文件内容:
var defaultConfigTmpl = `# This is a TOML config file. # For more information, see https://github.com/toml-lang/toml fast_sync = true db_backend = "leveldb" api_addr = "0.0.0.0:9888" ` var mainNetConfigTmpl = `chain_id = "mainnet" [p2p] laddr = "tcp://0.0.0.0:46657" seeds = "45.79.213.28:46657,198.74.61.131:46657,212.111.41.245:46657,47.100.214.154:46657,47.100.109.199:46657,47.100.105.165:46657" ` var testNetConfigTmpl = `chain_id = "testnet" [p2p] laddr = "tcp://0.0.0.0:46656" seeds = "47.96.42.1:46656,172.104.224.219:46656,45.118.132.164:46656" ` var soloNetConfigTmpl = `chain_id = "solonet" [p2p] laddr = "tcp://0.0.0.0:46658" seeds = "" `
可以看出,对于不同的chain_id,预设的种子是不同的。
当然,如果我们自己知道某些节点的地址,也可以在初始化生成config.toml后,手动修改该文件添加进去。
启动syncManager
那么,比原在代码中是使用这些种子地址并连接它们的呢?关键在于,连接的代码位于SyncManager中,所以我们要找到启动syncManager的地方。
首先,当我们使用bytomd node启动后,下面的函数将被调用:
cmd/bytomd/commands/run_node.go#L41
func runNode(cmd *cobra.Command, args []string) error {
// Create & start node
n := node.NewNode(config)
if _, err := n.Start(); err != nil {
// ...
}
// ...
}
这里调用了n.Start,其中的Start方法,来自于Node所嵌入的cmn.BaseService:
type Node struct {
cmn.BaseService
// ...
}
所以n.Start对应的是下面这个方法:
vendor/github.com/tendermint/tmlibs/common/service.go#L97
func (bs *BaseService) Start() (bool, error) {
// ...
err := bs.impl.OnStart()
// ...
}
在这里,由于bs.impl对应于Node,所以将继续调用Node.OnStart():
func (n *Node) OnStart() error {
// ...
n.syncManager.Start()
// ...
}
可以看到,我们终于走到了调用了syncManager.Start()的地方。
syncManager中的处理
然后就是在syncManager内部的一些处理了。
它主要是除了从config.toml中取得种子节点外,还需要把以前连接过并保存在本地的AddressBook.json中的节点也拿出来连接,这样就算预设的种子节点失败了,也还是有可能连接上网络(部分解决了前面提到的中心化的担忧)。
syncManager.Start()对应于:
func (sm *SyncManager) Start() {
go sm.netStart()
// ...
}
其中sm.netStart(),对应于:
func (sm *SyncManager) netStart() error {
// ...
// If seeds exist, add them to the address book and dial out
if sm.config.P2P.Seeds != "" {
// dial out
seeds := strings.Split(sm.config.P2P.Seeds, ",")
if err := sm.DialSeeds(seeds); err != nil {
return err
}
}
// ...
}
其中的sm.config.P2P.Seeds就对应于config.toml中的seeds。关于这两者是怎么对应起来的,会在后面文章中详解。
紧接着,再通过sm.DialSeeds(seeds)去连接这些seed,这个方法对应的代码位于:
func (sm *SyncManager) DialSeeds(seeds []string) error {
return sm.sw.DialSeeds(sm.addrBook, seeds)
}
其实是是调用了sm.sw.DialSeeds,而sm.sw是指Switch。这时可以看到,有一个叫addrBook的东西参与了进来,它保存了该结点之前成功连接过的节点地址,我们这里暂不多做讨论。
Switch.DialSeeds对应于:
func (sw *Switch) DialSeeds(addrBook *AddrBook, seeds []string) error {
// ...
perm := rand.Perm(len(netAddrs))
for i := 0; i < len(perm)/2; i++ {
j := perm[i]
sw.dialSeed(netAddrs[j])
}
// ...
}
这里引入了随机数,是为了将发起连接的顺序打乱,这样可以让每个种子都获得公平的连接机会。
sw.dialSeed(netAddrs[j])对应于:
func (sw *Switch) dialSeed(addr *NetAddress) {
peer, err := sw.DialPeerWithAddress(addr, false)
// ...
}
sw.DialPeerWithAddress(addr, false)又对应于:
func (sw *Switch) DialPeerWithAddress(addr *NetAddress, persistent bool) (*Peer, error) {
// ...
log.WithField("address", addr).Info("Dialing peer")
peer, err := newOutboundPeerWithConfig(addr, sw.reactorsByCh, sw.chDescs, sw.StopPeerForError, sw.nodePrivKey, sw.peerConfig)
// ...
}
其中的persistent参数如果是true的话,表明这个peer比较重要,在某些情况下如果断开连接后,还会尝试重连。如果persistent为false的,就没有这个待遇。
newOutboundPeerWithConfig对应于:
func newOutboundPeerWithConfig(addr *NetAddress, reactorsByCh map[byte]Reactor, chDescs []*ChannelDescriptor, onPeerError func(*Peer, interface{}), ourNodePrivKey crypto.PrivKeyEd25519, config *PeerConfig) (*Peer, error) {
conn, err := dial(addr, config)
// ...
}
继续dial,加入了超时:
func dial(addr *NetAddress, config *PeerConfig) (net.Conn, error) {
conn, err := addr.DialTimeout(config.DialTimeout * time.Second)
if err != nil {
return nil, err
}
return conn, nil
}
addr.DialTimeout对应于:
func (na *NetAddress) DialTimeout(timeout time.Duration) (net.Conn, error) {
conn, err := net.DialTimeout("tcp", na.String(), timeout)
if err != nil {
return nil, err
}
return conn, nil
}
终于到了net包的调用,开始真正去连接这个种子节点了,到这里,我们可以认为这个问题解决了。
关注公众号
低调大师中文资讯倾力打造互联网数据资讯、行业资源、电子商务、移动互联网、网络营销平台。
持续更新报道IT业界、互联网、市场资讯、驱动更新,是最及时权威的产业资讯及硬件资讯报道平台。
转载内容版权归作者及来源网站所有,本站原创内容转载请注明来源。
-
上一篇
【程序媛晒83行代码】Java女工程师江小白,偷偷亮代码被老板发现
在中国程序媛中,他们的代码有什么样的魅力,Aone联合云栖社区、饿了么、钉钉、阿里云、天猫、口碑发起首届程序媛比码活动——不秀大长腿,秀高智商;不秀美图照,秀代码图,参与晒码互动游戏赢“83行代码”T恤! 我们来说说这群女工程师的第83行代码及代码背后的故事: 我是福州市五佰网络科技有限公司的Java女工程师江小白 我的第83行代码来自添加员工信息之前在Service中校验是否已经存在相同的手机号的一段代码 与江小白小姐姐互动,为她打call——>点击进去晒码 更多小姐姐,点击进入查看代码 有被代码耽误的钉钉吃货程序媛,写代码写到忘记吃饭的——采霜 她急需能把她从代码中叫醒去吃饭的小伙伴,赶紧勾搭;http://yq.aliyun.com/roundtable/126499/answer/170319#visit170319 有以代码为乐的饿了么大前端打(bei)杂(guo)工程师——敖天羽http://yq.aliyun.com/roundtable/126499/answer/170299#visit170299 还有全栈美女工程师——前端后端一锅端的——墨瑜女神http:/...
-
下一篇
Protobuf在微信小游戏开发中的使用技巧
微信小游戏发布后,许多 HTML5 游戏开发者希望把现有的 HTML5 游戏迁移到微信小游戏中,但由于一些技术上的问题导致进程卡壳。通过梳理Egret社区、白鹭小游戏开发技术讨论群等途径的反馈后发现,有不少开发者遇到的难题在于「如何在微信小游戏中使用Protobuf 」。 白鹭引擎架构师王泽在近期发布了一个开源项目protobuf-egret,提供了一个可以适配微信小游戏的Protobuf 类库以及对应的代码生成工具,并且这个工具不仅限于白鹭引擎,即使是使用其他 HTML5 游戏引擎的开发者也可以使用。 项目地址: https://github.com/WanderWang/protobuf-egret/ 开源项目 protobuf-egret主要包含了特性、原理、如何安装与使用、如何运行DEMO以及部分已知问题等内容,希望通过本篇内容能够更好的帮助大家开展微信小游戏工作。 特性 1.提供 protobuf.js 基础运行时库 2.提供命令行脚本,将 protofile 生成 JavaScript 代码 3.生成正确的 .d.ts 代码,以方便 TypeScript 项目使用 4.一键...
相关文章
文章评论
共有0条评论来说两句吧...
文章二维码
点击排行
推荐阅读
最新文章
- Springboot2将连接池hikari替换为druid,体验最强大的数据库连接池
- CentOS7编译安装Cmake3.16.3,解决mysql等软件编译问题
- SpringBoot2初体验,简单认识spring boot2并且搭建基础工程
- CentOS7,CentOS8安装Elasticsearch6.8.6
- CentOS关闭SELinux安全模块
- CentOS8安装MyCat,轻松搞定数据库的读写分离、垂直分库、水平分库
- SpringBoot2编写第一个Controller,响应你的http请求并返回结果
- Docker容器配置,解决镜像无法拉取问题
- Windows10,CentOS7,CentOS8安装MongoDB4.0.16
- SpringBoot2更换Tomcat为Jetty,小型站点的福音

微信收款码
支付宝收款码