剝開比原看代碼03:比原是如何監聽p2p連接埠的

來源:互聯網
上載者:User

比原是如何監聽p2p連接埠的

我們知道,在使用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)    // ...}

SwitchSyncManager啟動的時候會被啟動,在它的OnStart方法中,會拿到所有Listener(即監聽連接埠的對象)中connectionschannel中的串連,與它們互動。

https://github.com/freewind/b...

func (sw *Switch) listenerRoutine(l Listener) {    for {        inConn, ok := <-l.Connections()        if !ok {            break        }        // ...                err := sw.addPeerWithConnectionAndConfig(inConn, sw.peerConfig)        // ...    }

其中sw.addPeerWithConnectionAndConfig就是與對應節點進行互動的邏輯所在,但是這已經超出了本次問題的範疇,下次再講。

到此為止,本次的問題,應該已經講清楚了。

相關文章

聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在5個工作日內處理。

如果您發現本社區中有涉嫌抄襲的內容,歡迎發送郵件至: info-contact@alibabacloud.com 進行舉報並提供相關證據,工作人員會在 5 個工作天內聯絡您,一經查實,本站將立刻刪除涉嫌侵權內容。

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.