作者:freewind
比原項(xiàng)目倉(cāng)庫(kù):https://github.com/Bytom/bytom
我們知道,在使用bytomd init --chain_id mainnet/testnet/solonet
初始化比原的時(shí)候,它會(huì)根據(jù)給定的chain_id
的不同,使用不同的端口(參看config/toml.go#L29):
寧強(qiáng)網(wǎng)站制作公司哪家好,找創(chuàng)新互聯(lián)公司!從網(wǎng)頁(yè)設(shè)計(jì)、網(wǎng)站建設(shè)、微信開發(fā)、APP開發(fā)、響應(yīng)式網(wǎng)站設(shè)計(jì)等網(wǎng)站項(xiàng)目制作,到程序開發(fā),運(yùn)營(yíng)維護(hù)。創(chuàng)新互聯(lián)公司從2013年成立到現(xiàn)在10年的時(shí)間,我們擁有了豐富的建站經(jīng)驗(yàn)和運(yùn)維經(jīng)驗(yàn),來保證我們的工作的順利進(jìn)行。專注于網(wǎng)站建設(shè)就選創(chuàng)新互聯(lián)公司。
mainnet
(連接到主網(wǎng)): 46657
testnet
(連接到測(cè)試網(wǎng)): 46656
solonet
(本地單獨(dú)節(jié)點(diǎn)): 46658
對(duì)于我來說,由于只需要對(duì)本地運(yùn)行的一個(gè)比原節(jié)點(diǎn)進(jìn)行分析,所以可以采用第3個(gè)chain_id
,即solonet
。這樣它啟動(dòng)之后,不會(huì)與其它的節(jié)點(diǎn)主動(dòng)連接,可以減少其它節(jié)點(diǎn)對(duì)于我們的干擾。
所以在啟動(dòng)的時(shí)候,我的命令是這樣的:
cd cmd/bytomd
./bytomd init --chain_id solonet
./bytomd node
它就會(huì)監(jiān)聽46658
端口,等待其它節(jié)點(diǎn)的連接。
如果這時(shí)我們使用telnet
來連接其46658
端口,成功連接上之后,可以看到它會(huì)發(fā)給我們一些亂碼,大概如下:
$ telnet localhost 46658
Trying 127.0.0.1...
Connected to localhost.
Escape character is '^]'.
??S??%?z???_?端??????U[e
我們也許會(huì)好奇,它發(fā)給我們的到底是什么?
但是這個(gè)問題留待下次回答,因?yàn)槭紫?,比原?jié)點(diǎn)必須能夠監(jiān)聽這個(gè)端口,我們才能連上。所以這次我們的問題是:
config.toml
中在前面,當(dāng)我們使用./bytomd init --chain_id solonet
初始化比原以后,比原會(huì)在本地的數(shù)據(jù)目錄中生成一個(gè)config.toml
的配置文件,內(nèi)容大約如下:
# 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
,就是該節(jié)點(diǎn)監(jiān)聽的地址和端口。
對(duì)于laddr = "tcp://0.0.0.0:46658"
,它是意思是:
tcp
協(xié)議0.0.0.0
,是指監(jiān)聽本機(jī)所有ip地址。這樣該節(jié)點(diǎn)既允許本地訪問,也允許外部主機(jī)訪問。如果你只想讓它監(jiān)聽某一個(gè)ip,手動(dòng)修改該配置文件即可46658
,就是我們?cè)谶@個(gè)問題中關(guān)注的端口了,它與該節(jié)點(diǎn)與其它節(jié)點(diǎn)交互數(shù)據(jù)使用的端口比原在監(jiān)聽這個(gè)端口的時(shí)候,并不是如我最開始預(yù)期的直接調(diào)用net.Listen
監(jiān)聽它。實(shí)際的過程要比這個(gè)復(fù)雜,因?yàn)楸仍O(shè)計(jì)了一個(gè)叫Switch
的對(duì)象,用來統(tǒng)一管理與外界相關(guān)的事件,包括監(jiān)聽、連接、發(fā)送消息等。而Switch
這個(gè)對(duì)象,又是在SyncManager
中創(chuàng)建的。
Switch
所以我們首先需要知道,比原在源代碼中是如何啟動(dòng),并且一步步走進(jìn)了Switch
的世界。
首先還是當(dāng)我們bytomd node
啟動(dòng)比原時(shí),對(duì)應(yīng)的入口函數(shù)如下:
cmd/bytomd/main.go#L54
func main() {
cmd := cli.PrepareBaseCmd(commands.RootCmd, "TM", os.ExpandEnv(config.DefaultDataDir()))
cmd.Execute()
}
它又會(huì)根據(jù)傳入的node
參數(shù),運(yùn)行下面的函數(shù):
cmd/bytomd/commands/run_node.go#L41
func runNode(cmd *cobra.Command, args []string) error {
// Create & start node
n := node.NewNode(config)
// ...
}
我們需要關(guān)注的是node.NewNode(config)
函數(shù),因?yàn)槭窃谒锩鎰?chuàng)建了SyncManager
:
node/node.go#L59
func NewNode(config *cfg.Config) *Node {
// ...
syncManager, _ := netsync.NewSyncManager(config, chain, txPool, newBlockCh)
// ...
}
在創(chuàng)建SyncManager
的時(shí)候,又創(chuàng)建了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)
// ...
}
這里需要注意一下,上面創(chuàng)建的protocolReactor
對(duì)象,是用來處理當(dāng)有節(jié)點(diǎn)連接上端口后,雙方如何交互的事情。跟這次問題“監(jiān)聽端口”沒有直接關(guān)系,但是這里也可以注意一下。
然后又創(chuàng)建了一個(gè)DefaultListener
對(duì)象,而監(jiān)聽端口的動(dòng)作,就是在它里面發(fā)生的。Listener創(chuàng)建之后,將會(huì)添加到manager.sw
(即Switch
)中,用于在那邊進(jìn)行外界數(shù)據(jù)與事件的交互。
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)
}
// ...
上面這部分就是真正監(jiān)聽的代碼了。通過Go語(yǔ)言提供的net.Listen
函數(shù),監(jiān)聽了指定的地址。另外,在監(jiān)聽的時(shí)候,進(jìn)行了多次嘗試,因?yàn)楫?dāng)一個(gè)剛剛被使用的端口被放開后,還需要一小段時(shí)間才能真正釋放,所以這里需要多嘗試幾次。
其中tryListenSeconds
是一個(gè)常量,值為5
,也就是說,大約會(huì)嘗試5秒鐘,要是都綁定不上,才會(huì)真正失敗,拋出錯(cuò)誤。
后面省略了一些代碼,主要是用來獲取當(dāng)前監(jiān)聽的實(shí)際ip以及外網(wǎng)ip,并記錄在日志中。本想在這里簡(jiǎn)單講講,但是發(fā)現(xiàn)還有點(diǎn)麻煩,所以打算放在后面專開一個(gè)問題。
其實(shí)本次問題到這里就已經(jīng)結(jié)束了,因?yàn)橐呀?jīng)完成了“監(jiān)聽”。但是后面還有一些初始化操作,是為了讓比原可以跟連接上該端口的節(jié)點(diǎn)進(jìn)行交互,也值得在這里講講。
接著剛才的方法,最后的部分是:
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
,它是一個(gè)帶有緩沖的channel(numBufferedConnections
值為10
),用來存放連接上該端口的連接對(duì)象。這些操作將在后面的dl.Start()
中執(zhí)行。
dl.Start()
將調(diào)用DefaultListener
對(duì)應(yīng)的OnStart
方法,如下:
p2p/listener.go#L114
func (l *DefaultListener) OnStart() error {
l.BaseService.OnStart()
go l.listenRoutine()
return nil
}
其中的l.listenRoutine
,就是執(zhí)行前面所說的向connections
channel里放入連接的函數(shù):
p2p/listener.go#L126
func (l *DefaultListener) listenRoutine() {
for {
conn, err := l.listener.Accept()
// ...
l.connections <- conn
}
// Cleanup
close(l.connections)
// ...
}
而Switch
在SyncManager
啟動(dòng)的時(shí)候會(huì)被啟動(dòng),在它的OnStart
方法中,會(huì)拿到所有Listener(即監(jiān)聽端口的對(duì)象)中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
就是與對(duì)應(yīng)節(jié)點(diǎn)進(jìn)行交互的邏輯所在,但是這已經(jīng)超出了本次問題的范疇,下次再講。
到此為止,本次的問題,應(yīng)該已經(jīng)講清楚了。