剥开比原看代码03:比原是如何监听p2p端口的

简介: 作者:freewind比原项目仓库:Github地址:https://github.com/Bytom/bytomGitee地址:https://gitee.com/BytomBlockchain/bytom我们知道,在使用bytomd init --chain_id mainnet/tes...

作者: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。这样它启动之后,不会与其它的节点主动连接,可以减少其它节点对于我们的干扰。

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

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",它是意思是:

  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)

    // ...
}

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

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

相关文章
|
6月前
|
负载均衡 网络协议 算法
slb监听协议与端口
SLB是云服务商提供的负载均衡服务,用于分发客户端请求到多台后端服务器,提升服务可用性和响应速度。关键概念包括监听协议(TCP、UDP、HTTP、HTTPS、TCPSSL)和监听端口。监听协议决定了SLB处理请求的方式,而监听端口则是SLB接收请求的入口。配置时需根据应用选择合适协议和端口,并可设置负载均衡算法(如轮询、最少连接等)。客户端应通过SLB统一入口访问后端服务,避免绕过SLB导致的问题。
529 2
|
6月前
|
负载均衡 网络协议 安全
slb选择监听协议和端口
阿里云SLB中,监听协议(TCP、HTTP、HTTPS)与端口(80、443等)决定客户端请求的处理方式。TCP适用于纯TCP或自处理HTTP的场景,HTTP用于智能调度Web服务,HTTPS提供安全的HTTP传输。监听端口通常匹配应用标准,如80 for HTTP,443 for HTTPS。配置时,可考虑HTTPS重定向和传递`X-Forwarded-Proto`头以识别请求来源。选择应基于业务需求和安全考虑。
281 3
|
3月前
|
网络协议 Linux
Linux查看端口监听情况,以及Linux查看某个端口对应的进程号和程序
Linux查看端口监听情况,以及Linux查看某个端口对应的进程号和程序
633 2
|
3月前
|
开发框架 .NET Linux
【Azure 应用服务】 部署到App Service for Linux 服务的Docker 镜像,如何配置监听端口呢?
【Azure 应用服务】 部署到App Service for Linux 服务的Docker 镜像,如何配置监听端口呢?
|
4月前
|
人工智能 Serverless API
函数计算产品使用问题之如何在一个Docker容器内运行一个持续监听特定端口的应用程序
函数计算产品作为一种事件驱动的全托管计算服务,让用户能够专注于业务逻辑的编写,而无需关心底层服务器的管理与运维。你可以有效地利用函数计算产品来支撑各类应用场景,从简单的数据处理到复杂的业务逻辑,实现快速、高效、低成本的云上部署与运维。以下是一些关于使用函数计算产品的合集和要点,帮助你更好地理解和应用这一服务。
|
3月前
|
网络协议
【qt】TCP的监听 (设置服务器IP地址和端口号)
【qt】TCP的监听 (设置服务器IP地址和端口号)
204 0
|
3月前
|
Linux Windows
Windows查找监听端口对应的进程及其路径
Windows查找监听端口对应的进程及其路径
111 0
|
5月前
|
Java Android开发
Java Socket编程示例:服务器开启在8080端口监听,接收客户端连接并打印消息。
【6月更文挑战第23天】 Java Socket编程示例:服务器开启在8080端口监听,接收客户端连接并打印消息。客户端连接服务器,发送&quot;Hello, Server!&quot;后关闭。注意Android中需避免主线程进行网络操作。
100 4
|
6月前
|
Linux Apache
CentOS 7 源码安装LAMP环境源 和apache监听别的端口
CentOS 7 源码安装LAMP环境源 和apache监听别的端口
63 0
|
6月前
|
运维 安全 数据库
cmd中使用telnet检测远程的ip及端口是否处于监听状态
cmd中使用telnet检测远程的ip及端口是否处于监听状态
124 1

热门文章

最新文章