剥开比原看代码03:比原是如何监听p2p端口的
作者:freewind
比原项目仓库:
Github地址:https://github.com/Bytom/bytom
Gitee地址:https://gitee.com/BytomBlockchain/bytom
我们知道,在使用bytomd init --chain_id mainnet/testnet/solonet
初始化比原的时候,它会根据给定的chain_id
的不同,使用不同的端口(参看config/toml.go#L29):
-
mainnet
(连接到主网):46657
-
testnet
(连接到测试网):46656
-
solonet
(本地单独节点):46658
对于我来说,由于只需要对本地运行的一个比原节点进行分析,所以可以采用第3个chain_id
,即solonet
。这样它启动之后,不会与其它的节点主动连接,可以减少其它节点对于我们的干扰。
所以在启动的时候,我的命令是这样的:
cd cmd/bytomd ./bytomd init --chain_id solonet ./bytomd node
它就会监听46658
端口,等待其它节点的连接。
连上看看
如果这时我们使用telnet
来连接其46658
端口,成功连接上之后,可以看到它会发给我们一些乱码,大概如下:
$ telnet localhost 46658 Trying 127.0.0.1... Connected to localhost. Escape character is '^]'. ט�S��%�z?��_�端��݂���U[e
我们也许会好奇,它发给我们的到底是什么?
但是这个问题留待下次回答,因为首先,比原节点必须能够监听这个端口,我们才能连上。所以这次我们的问题是:
比原在代码中是如何监听这个端口的?
端口已经写在config.toml
中
在前面,当我们使用./bytomd init --chain_id solonet
初始化比原以后,比原会在本地的数据目录中生成一个config.toml
的配置文件,内容大约如下:
# 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" chain_id = "solonet" [p2p] laddr = "tcp://0.0.0.0:46658" seeds = ""
其中[p2p]
下面的laddr
,就是该节点监听的地址和端口。
对于laddr = "tcp://0.0.0.0:46658"
,它是意思是:
- 使用的是
tcp
协议 - 监听的ip是
0.0.0.0
,是指监听本机所有ip地址。这样该节点既允许本地访问,也允许外部主机访问。如果你只想让它监听某一个ip,手动修改该配置文件即可 -
46658
,就是我们在这个问题中关注的端口了,它与该节点与其它节点交互数据使用的端口
比原在监听这个端口的时候,并不是如我最开始预期的直接调用net.Listen
监听它。实际的过程要比这个复杂,因为比原设计了一个叫Switch
的对象,用来统一管理与外界相关的事件,包括监听、连接、发送消息等。而Switch
这个对象,又是在SyncManager
中创建的。
启动直到进入Switch
所以我们首先需要知道,比原在源代码中是如何启动,并且一步步走进了Switch
的世界。
首先还是当我们bytomd node
启动比原时,对应的入口函数如下:
func main() { cmd := cli.PrepareBaseCmd(commands.RootCmd, "TM", os.ExpandEnv(config.DefaultDataDir())) cmd.Execute() }
它又会根据传入的node
参数,运行下面的函数:
cmd/bytomd/commands/run_node.go#L41
func runNode(cmd *cobra.Command, args []string) error { // Create & start node n := node.NewNode(config) // ... }
我们需要关注的是node.NewNode(config)
函数,因为是在它里面创建了SyncManager
:
func NewNode(config *cfg.Config) *Node { // ... syncManager, _ := netsync.NewSyncManager(config, chain, txPool, newBlockCh) // ... }
在创建SyncManager
的时候,又创建了Switch
:
func NewSyncManager(config *cfg.Config, chain *core.Chain, txPool *core.TxPool, newBlockCh chan *bc.Hash) (*SyncManager, error) { // ... manager.sw = p2p.NewSwitch(config.P2P, trustHistoryDB) // ... protocolReactor := NewProtocolReactor(chain, txPool, manager.sw, manager.blockKeeper, manager.fetcher, manager.peers, manager.newPeerCh, manager.txSyncCh, manager.dropPeerCh) manager.sw.AddReactor("PROTOCOL", protocolReactor) // Create & add listener p, address := protocolAndAddress(manager.config.P2P.ListenAddress) l := p2p.NewDefaultListener(p, address, manager.config.P2P.SkipUPNP, nil) manager.sw.AddListener(l) // ... }
这里需要注意一下,上面创建的protocolReactor
对象,是用来处理当有节点连接上端口后,双方如何交互的事情。跟这次问题“监听端口”没有直接关系,但是这里也可以注意一下。
然后又创建了一个DefaultListener
对象,而监听端口的动作,就是在它里面发生的。Listener创建之后,将会添加到manager.sw
(即Switch
)中,用于在那边进行外界数据与事件的交互。
监听端口
NewDefaultListener
中做的事情比较多,所以我们把它分成几块说:
func NewDefaultListener(protocol string, lAddr string, skipUPNP bool, logger tlog.Logger) Listener { // Local listen IP & port lAddrIP, lAddrPort := splitHostPort(lAddr) // Create listener var listener net.Listener var err error for i := 0; i < tryListenSeconds; i++ { listener, err = net.Listen(protocol, lAddr) if err == nil { break } else if i < tryListenSeconds-1 { time.Sleep(time.Second * 1) } } if err != nil { cmn.PanicCrisis(err) } // ...
上面这部分就是真正监听的代码了。通过Go语言提供的net.Listen
函数,监听了指定的地址。另外,在监听的时候,进行了多次尝试,因为当一个刚刚被使用的端口被放开后,还需要一小段时间才能真正释放,所以这里需要多尝试几次。
其中tryListenSeconds
是一个常量,值为5
,也就是说,大约会尝试5秒钟,要是都绑定不上,才会真正失败,抛出错误。
后面省略了一些代码,主要是用来获取当前监听的实际ip以及外网ip,并记录在日志中。本想在这里简单讲讲,但是发现还有点麻烦,所以打算放在后面专开一个问题。
其实本次问题到这里就已经结束了,因为已经完成了“监听”。但是后面还有一些初始化操作,是为了让比原可以跟连接上该端口的节点进行交互,也值得在这里讲讲。
接着刚才的方法,最后的部分是:
dl := &DefaultListener{ listener: listener, intAddr: intAddr, extAddr: extAddr, connections: make(chan net.Conn, numBufferedConnections), } dl.BaseService = *cmn.NewBaseService(logger, "DefaultListener", dl) dl.Start() // Started upon construction return dl }
需要注意的是connections
,它是一个带有缓冲的channel(numBufferedConnections
值为10
),用来存放连接上该端口的连接对象。这些操作将在后面的dl.Start()
中执行。
dl.Start()
将调用DefaultListener
对应的OnStart
方法,如下:
func (l *DefaultListener) OnStart() error { l.BaseService.OnStart() go l.listenRoutine() return nil }
其中的l.listenRoutine
,就是执行前面所说的向connections
channel里放入连接的函数:
func (l *DefaultListener) listenRoutine() { for { conn, err := l.listener.Accept() // ... l.connections <- conn } // Cleanup close(l.connections) // ... }
而Switch
在SyncManager
启动的时候会被启动,在它的OnStart
方法中,会拿到所有Listener(即监听端口的对象)中connections
channel中的连接,与它们交互。
https://github.com/freewind/bytom-v1.0.1/blob/master/p2p/switch.go#L498
func (sw *Switch) listenerRoutine(l Listener) { for { inConn, ok := <-l.Connections() if !ok { break } // ... err := sw.addPeerWithConnectionAndConfig(inConn, sw.peerConfig) // ... }
其中sw.addPeerWithConnectionAndConfig
就是与对应节点进行交互的逻辑所在,但是这已经超出了本次问题的范畴,下次再讲。
到此为止,本次的问题,应该已经讲清楚了。

低调大师中文资讯倾力打造互联网数据资讯、行业资源、电子商务、移动互联网、网络营销平台。
持续更新报道IT业界、互联网、市场资讯、驱动更新,是最及时权威的产业资讯及硬件资讯报道平台。
转载内容版权归作者及来源网站所有,本站原创内容转载请注明来源。
-
上一篇
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.一键...
-
下一篇
2017年 JavaScript 框架回顾 -- React生态系统
前一篇文章中,我们介绍了2017年 JavaScript 框架的整体情况。我们也了解到在众多的前端框架中,目前最为庞大又在快速增长的当属 React 了,本文就来重点介绍 React 的生态系统。 首先看看与 React 有关的软件包的生态系统。在 Facebook 构建 React 之初,就有许多来自于开源社区第三方库的软件包。这些软件包使用 React 补充其它功能,以便提供完整的应用程序解决方案。当然,安装包中也存在着提供相似功能的彼此竞争关系。 React Router 丰富的 Web 应用程序具有的一个共同特点就是:提供了多个“路由”。这些“路由”本质上是不同的功能块,在浏览器中表现为单独的 URL。React 不需要为简单的应用程序使用路由,同时在一些桌面和移动应用程序环境中,路由也不是必需的。因此,React Router 虽然是最受欢迎的 React 应用程序的路由解决方案,但 React Router 的相对流行程度却只有 React 的一半。 了解 React 的开发者都知道,React 应用于一个明确定义的问题领域并具有明确定义的接口,这也使其本身可以应用于比原...
相关文章
文章评论
共有0条评论来说两句吧...
文章二维码
点击排行
推荐阅读
最新文章
- SpringBoot2整合Redis,开启缓存,提高访问速度
- 设置Eclipse缩进为4个空格,增强代码规范
- Windows10,CentOS7,CentOS8安装MongoDB4.0.16
- SpringBoot2全家桶,快速入门学习开发网站教程
- CentOS7编译安装Cmake3.16.3,解决mysql等软件编译问题
- Docker快速安装Oracle11G,搭建oracle11g学习环境
- SpringBoot2整合MyBatis,连接MySql数据库做增删改查操作
- Springboot2将连接池hikari替换为druid,体验最强大的数据库连接池
- SpringBoot2配置默认Tomcat设置,开启更多高级功能
- SpringBoot2更换Tomcat为Jetty,小型站点的福音