作者: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):

  1. mainnet(连接到主网): 46657
  2. testnet(连接到测试网): 46656
  3. solonet(本地单独节点): 46658

对于我来说,由于只需要对本地运行的一个比原节点进行分析,所以可以采用第3个chain_id,即solonet。这样它启动之后,不会与其它的节点主动连接,可以减少其它节点对于我们的干扰。

所以在启动的时候,我的命令是这样的:

  1. cd cmd/bytomd
  2. ./bytomd init --chain_id solonet
  3. ./bytomd node

它就会监听46658端口,等待其它节点的连接。

连上看看

如果这时我们使用telnet来连接其46658端口,成功连接上之后,可以看到它会发给我们一些乱码,大概如下:

  1. $ telnet localhost 46658
  2. Trying 127.0.0.1...
  3. Connected to localhost.
  4. Escape character is '^]'.
  5. ט�S��%�z?��_�端��݂���U[e

我们也许会好奇,它发给我们的到底是什么?

但是这个问题留待下次回答,因为首先,比原节点必须能够监听这个端口,我们才能连上。所以这次我们的问题是:

比原在代码中是如何监听这个端口的?

端口已经写在config.toml

在前面,当我们使用./bytomd init --chain_id solonet初始化比原以后,比原会在本地的数据目录中生成一个config.toml的配置文件,内容大约如下:

  1. # This is a TOML config file.
  2. # For more information, see https://github.com/toml-lang/toml
  3. fast_sync = true
  4. db_backend = "leveldb"
  5. api_addr = "0.0.0.0:9888"
  6. chain_id = "solonet"
  7. [p2p]
  8. laddr = "tcp://0.0.0.0:46658"
  9. seeds = ""

其中[p2p]下面的laddr,就是该节点监听的地址和端口。

对于laddr = "tcp://0.0.0.0:46658",它是意思是:

  1. 使用的是tcp协议
  2. 监听的ip是0.0.0.0,是指监听本机所有ip地址。这样该节点既允许本地访问,也允许外部主机访问。如果你只想让它监听某一个ip,手动修改该配置文件即可
  3. 46658,就是我们在这个问题中关注的端口了,它与该节点与其它节点交互数据使用的端口

比原在监听这个端口的时候,并不是如我最开始预期的直接调用net.Listen监听它。实际的过程要比这个复杂,因为比原设计了一个叫Switch的对象,用来统一管理与外界相关的事件,包括监听、连接、发送消息等。而Switch这个对象,又是在SyncManager中创建的。

启动直到进入Switch

所以我们首先需要知道,比原在源代码中是如何启动,并且一步步走进了Switch的世界。

首先还是当我们bytomd node启动比原时,对应的入口函数如下:

cmd/bytomd/main.go#L54

  1. func main() {
  2. cmd := cli.PrepareBaseCmd(commands.RootCmd, "TM", os.ExpandEnv(config.DefaultDataDir()))
  3. cmd.Execute()
  4. }

它又会根据传入的node参数,运行下面的函数:

cmd/bytomd/commands/run_node.go#L41

  1. func runNode(cmd *cobra.Command, args []string) error {
  2. // Create & start node
  3. n := node.NewNode(config)
  4. // ...
  5. }

我们需要关注的是node.NewNode(config)函数,因为是在它里面创建了SyncManager

node/node.go#L59

  1. func NewNode(config *cfg.Config) *Node {
  2. // ...
  3. syncManager, _ := netsync.NewSyncManager(config, chain, txPool, newBlockCh)
  4. // ...
  5. }

在创建SyncManager的时候,又创建了Switch:

netsync/handle.go#L42

  1. func NewSyncManager(config *cfg.Config, chain *core.Chain, txPool *core.TxPool, newBlockCh chan *bc.Hash) (*SyncManager, error) {
  2. // ...
  3. manager.sw = p2p.NewSwitch(config.P2P, trustHistoryDB)
  4.  
  5. // ...
  6. protocolReactor := NewProtocolReactor(chain, txPool, manager.sw, manager.blockKeeper, manager.fetcher, manager.peers, manager.newPeerCh, manager.txSyncCh, manager.dropPeerCh)
  7. manager.sw.AddReactor("PROTOCOL", protocolReactor)
  8.  
  9. // Create & add listener
  10. p, address := protocolAndAddress(manager.config.P2P.ListenAddress)
  11. l := p2p.NewDefaultListener(p, address, manager.config.P2P.SkipUPNP, nil)
  12. manager.sw.AddListener(l)
  13.  
  14. // ...
  15. }

这里需要注意一下,上面创建的protocolReactor对象,是用来处理当有节点连接上端口后,双方如何交互的事情。跟这次问题“监听端口”没有直接关系,但是这里也可以注意一下。

然后又创建了一个DefaultListener对象,而监听端口的动作,就是在它里面发生的。Listener创建之后,将会添加到manager.sw(即Switch)中,用于在那边进行外界数据与事件的交互。

监听端口

NewDefaultListener中做的事情比较多,所以我们把它分成几块说:

p2p/listener.go#L52

  1. func NewDefaultListener(protocol string, lAddr string, skipUPNP bool, logger tlog.Logger) Listener {
  2. // Local listen IP & port
  3. lAddrIP, lAddrPort := splitHostPort(lAddr)
  4.  
  5. // Create listener
  6. var listener net.Listener
  7. var err error
  8. for i := 0; i < tryListenSeconds; i++ {
  9. listener, err = net.Listen(protocol, lAddr)
  10. if err == nil {
  11. break
  12. } else if i < tryListenSeconds-1 {
  13. time.Sleep(time.Second * 1)
  14. }
  15. }
  16. if err != nil {
  17. cmn.PanicCrisis(err)
  18. }
  19.  
  20. // ...

上面这部分就是真正监听的代码了。通过Go语言提供的net.Listen函数,监听了指定的地址。另外,在监听的时候,进行了多次尝试,因为当一个刚刚被使用的端口被放开后,还需要一小段时间才能真正释放,所以这里需要多尝试几次。

其中tryListenSeconds是一个常量,值为5,也就是说,大约会尝试5秒钟,要是都绑定不上,才会真正失败,抛出错误。

后面省略了一些代码,主要是用来获取当前监听的实际ip以及外网ip,并记录在日志中。本想在这里简单讲讲,但是发现还有点麻烦,所以打算放在后面专开一个问题。

其实本次问题到这里就已经结束了,因为已经完成了“监听”。但是后面还有一些初始化操作,是为了让比原可以跟连接上该端口的节点进行交互,也值得在这里讲讲。

接着刚才的方法,最后的部分是:

  1. dl := &DefaultListener{
  2. listener: listener,
  3. intAddr: intAddr,
  4. extAddr: extAddr,
  5. connections: make(chan net.Conn, numBufferedConnections),
  6. }
  7. dl.BaseService = *cmn.NewBaseService(logger, "DefaultListener", dl)
  8. dl.Start() // Started upon construction
  9. return dl
  10. }

需要注意的是connections,它是一个带有缓冲的channel(numBufferedConnections值为10),用来存放连接上该端口的连接对象。这些操作将在后面的dl.Start()中执行。

dl.Start()将调用DefaultListener对应的OnStart方法,如下:

p2p/listener.go#L114

  1. func (l *DefaultListener) OnStart() error {
  2. l.BaseService.OnStart()
  3. go l.listenRoutine()
  4. return nil
  5. }

其中的l.listenRoutine,就是执行前面所说的向connections channel里放入连接的函数:

p2p/listener.go#L126

  1. func (l *DefaultListener) listenRoutine() {
  2. for {
  3. conn, err := l.listener.Accept()
  4. // ...
  5. l.connections <- conn
  6. }
  7.  
  8. // Cleanup
  9. close(l.connections)
  10.  
  11. // ...
  12. }

SwitchSyncManager启动的时候会被启动,在它的OnStart方法中,会拿到所有Listener(即监听端口的对象)中connectionschannel中的连接,与它们交互。

https://github.com/freewind/bytom-v1.0.1/blob/master/p2p/switch.go#L498

  1. func (sw *Switch) listenerRoutine(l Listener) {
  2. for {
  3. inConn, ok := <-l.Connections()
  4. if !ok {
  5. break
  6. }
  7. // ...
  8.  
  9. err := sw.addPeerWithConnectionAndConfig(inConn, sw.peerConfig)
  10. // ...
  11. }

其中sw.addPeerWithConnectionAndConfig就是与对应节点进行交互的逻辑所在,但是这已经超出了本次问题的范畴,下次再讲。

到此为止,本次的问题,应该已经讲清楚了。

剥开比原看代码03:比原是如何监听p2p端口的的更多相关文章

  1. 剥开比原看代码16:比原是如何通过/list-transactions显示交易信息的

    作者:freewind 比原项目仓库: Github地址:https://github.com/Bytom/bytom Gitee地址:https://gitee.com/BytomBlockchai ...

  2. 剥开比原看代码13:比原是如何通过/list-balances显示帐户余额的?

    作者:freewind 比原项目仓库: Github地址:https://github.com/Bytom/bytom Gitee地址:https://gitee.com/BytomBlockchai ...

  3. 剥开比原看代码12:比原是如何通过/create-account-receiver创建地址的?

    作者:freewind 比原项目仓库: Github地址:https://github.com/Bytom/bytom Gitee地址:https://gitee.com/BytomBlockchai ...

  4. 剥开比原看代码11:比原是如何通过接口/create-account创建帐户的

    作者:freewind 比原项目仓库: Github地址:https://github.com/Bytom/bytom Gitee地址:https://gitee.com/BytomBlockchai ...

  5. 剥开比原看代码08:比原的Dashboard是怎么做出来的?

    作者:freewind 比原项目仓库: Github地址:https://github.com/Bytom/bytom Gitee地址:https://gitee.com/BytomBlockchai ...

  6. [oracle原]访问局域网内出现“ORA-12541:TNS:无监听程序”

    近日在服务器局域网内27电脑上安装了oracle11g,本机上访问此数据库正常.但在局域网内其它机器上访问27上的数据库时,出现“ORA-12541:TNS:无监听程序”错误. 查27上的配置:D:\ ...

  7. (原)android中的动画(三)之动画监听&页面切换动画

    1.动画也可以设置监听事件,例如在动画结束时需要执行某操作 把要执行的代码写在onAnimationEnd()回调方法中即可: anim.setAnimationListener(new Animat ...

  8. 关于websocket的代码,实现发送信息和监听信息(前端 后端(node.js))

    文件结构 node.js代码 // 需要HTTP 模块来启动服务器和Socket.IOvar http= require('http');var fs = require('fs');// 在8080 ...

  9. 剥开比原看代码10:比原是如何通过/create-key接口创建密钥的

    作者:freewind 比原项目仓库: Github地址:https://github.com/Bytom/bytom Gitee地址:https://gitee.com/BytomBlockchai ...

随机推荐

  1. 了解一下UTF-16

    1)先啰嗦一下 UTF-16是一种编码格式.啥是编码格式?就是怎么存储,也就是存储的方式. 存储啥?存二进制数字.为啥要存二进制数字? 因为Unicode字符集里面把二进制数字和字符一一对应了,存二进 ...

  2. byte以及UTF-8的转码规则

    https://www.cnblogs.com/hell8088/p/9184336.html 多年来闲麻烦,只记录笔记,不曾编写BLOG,本文为原创,如需转载请标明出处 废话不说,直奔主题 asci ...

  3. jquery easyui datagrid 空白条处理 自适应宽高 格式化函数formmater 初始化时会报错 cannot read property 'width'||'length' of null|undefined

    1---表格定义好之后右侧可能会有一个空白条 这个空白条是留给滚动条的,当表格中的一页的数据在页面中不能全显示时会自动出现滚动条,网上有很多事要改源码才可以修改这个,但是当项目中多处用到时,有的需要滚 ...

  4. GoldenGate实时投递数据到大数据平台(5) - Kafka

    Oracle GoldenGate是Oracle公司的实时数据复制软件,支持关系型数据库和多种大数据平台.从GoldenGate 12.2开始,GoldenGate支持直接投递数据到Kafka等平台, ...

  5. PS火焰文字制作

    火焰文字制作: 最终效果 第一步: 新建图层,并输入文字(这里不做详细解说)

  6. ltp-ddt的makefile结构

    顶层makefile COMMON_TARGETS        := pan utils COMMON_TARGETS    += tools testcases/ddt COMMON_TARGET ...

  7. Python笔记 #21# DHNN

    离散型hopfield神经网络.参考自http://web.cs.ucla.edu/~rosen/161/notes/hopfield.html实现的草稿版本: # http://web.cs.ucl ...

  8. LNMP 添加 memcached服务

    LNMP 添加 memcached服务   由于memcached具有更多的功能和服务,已经不推荐使用memcache了.(缺少个字母d) 1. 首先安装memcached服务端. 这里使用yum源安 ...

  9. django 存在则忽略, 不存在则创 TagSheet.objects.get_or_create(tag='test')

    django 存在则忽略, 不存在则创 TagSheet.objects.get_or_create(tag='test')

  10. Fiddler(二)Fiddler操作界面[工具条]和[底部状态面板]说明

    工具条说明 下面从左往右介绍一下每个按钮在工作中能起到什么作用,我这里是汉化版,所以顾名思义大家也能大概明白,并且鼠标在对应的图标上停留会有官方解释.下面再稍微补充一下 从第二个按钮说起,也就是小气泡 ...