千家信息网

【比原链】比原是如何监听p2p端口的

发表于:2026-11-01 作者:千家信息网编辑
千家信息网最后更新 2026年11月01日,作者:freewind比原项目仓库:https://github.com/Bytom/bytom我们知道,在使用bytomd init --chain_id mainnet/testnet/solon
千家信息网最后更新 2026年11月01日【比原链】比原是如何监听p2p端口的

作者:freewind
比原项目仓库:https://github.com/Bytom/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。这样它启动之后,不会与其它的节点主动连接,可以减少其它节点对于我们的干扰。

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

cd cmd/bytomd./bytomd init --chain_id solonet./bytomd node

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

连上看看

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

$ telnet localhost 46658Trying 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/tomlfast_sync = truedb_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",它是意思是:

  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

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:

node/node.go#L59

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

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

netsync/handle.go#L42

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中做的事情比较多,所以我们把它分成几块说:

p2p/listener.go#L52

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方法,如下:

p2p/listener.go#L114

func (l *DefaultListener) OnStart() error {    l.BaseService.OnStart()    go l.listenRoutine()    return nil}

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

p2p/listener.go#L126

func (l *DefaultListener) listenRoutine() {    for {        conn, err := l.listener.Accept()        // ...        l.connections <- conn    }    // Cleanup    close(l.connections)    // ...}

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

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就是与对应节点进行交互的逻辑所在,但是这已经超出了本次问题的范畴,下次再讲。

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

监听 端口 节点 问题 对象 就是 时候 函数 代码 地址 数据 方法 面的 尝试 不同 事件 事情 外界 实际 文件 数据库的安全要保护哪些东西 数据库安全各自的含义是什么 生产安全数据库录入 数据库的安全性及管理 数据库安全策略包含哪些 海淀数据库安全审计系统 建立农村房屋安全信息数据库 易用的数据库客户端支持安全管理 连接数据库失败ssl安全错误 数据库的锁怎样保障安全 中智农互联网科技 朗新科技与工业互联网 互联网科技公司电话 互联网科技股有哪些 互联网公司会议桌创意科技软装饰 汉立互联网科技有限公司联系电话 卫星互联网科技股票 农村开互联网科技讲堂 创业天下互联网科技有限公司 科技互联网大厂 互联网科技的全部知识 国大互联网教育科技有限公司薪资 互联网科技快讯热点 35岁以上互联网科技行业 广州互联网高科技取餐柜 北京天晋互联网科技有限公司 唐山双旺合互联网科技产业园 恒生科技指数 中概互联网 互联网科技创新方面的资料 前海云享互联网科技 上海睿民互联网科技有限公司 云之行互联网科技 互联网金融科技公司干啥的 哈尔滨屹滨互联网科技有限公司 长沙茂晟互联网科技有限公司 山东有哪些互联网科技公司 启辰互联网科技 广州梦谷互联网科技有限公司领导 版纳互联网科技选哪家 求路客互联网科技电话
0