剥开比原看代码03:比原是如何监听p2p端口的
作者: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):
mainnet
(连接到主网):46657
testnet
(连接到测试网):46656
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"
,它是意思是:
- 使用的是
tcp
协议 - 监听的ip是
0.0.0.0
,是指监听本机所有ip地址。这样该节点既允许本地访问,也允许外部主机访问。如果你只想让它监听某一个ip,手动修改该配置文件即可 46658
,就是我们在这个问题中关注的端口了,它与该节点与其它节点交互数据使用的端口
比原在监听这个端口的时候,并不是如我最开始预期的直接调用net.Listen
监听它。实际的过程要比这个复杂,因为比原设计了一个叫Switch
的对象,用来统一管理与外界相关的事件,包括监听、连接、发送消息等。而Switch
这个对象,又是在SyncManager
中创建的。
启动直到进入Switch
所以我们首先需要知道,比原在源代码中是如何启动,并且一步步走进了Switch
的世界。
首先还是当我们bytomd node
启动比原时,对应的入口函数如下:
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
:
func NewNode(config *cfg.Config) *Node {
// ...
syncManager, _ := netsync.NewSyncManager(config, chain, txPool, newBlockCh)
// ...
}
在创建SyncManager
的时候,又创建了Switch
:
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
中做的事情比较多,所以我们把它分成几块说:
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
方法,如下:
func (l *DefaultListener) OnStart() error {
l.BaseService.OnStart()
go l.listenRoutine()
return nil
}
其中的l.listenRoutine
,就是执行前面所说的向connections
channel里放入连接的函数:
func (l *DefaultListener) listenRoutine() {
for {
conn, err := l.listener.Accept()
// ...
l.connections <- conn
} // Cleanup
close(l.connections) // ...
}
而Switch
在SyncManager
启动的时候会被启动,在它的OnStart
方法中,会拿到所有Listener(即监听端口的对象)中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
就是与对应节点进行交互的逻辑所在,但是这已经超出了本次问题的范畴,下次再讲。
到此为止,本次的问题,应该已经讲清楚了。
剥开比原看代码03:比原是如何监听p2p端口的的更多相关文章
- 剥开比原看代码16:比原是如何通过/list-transactions显示交易信息的
作者:freewind 比原项目仓库: Github地址:https://github.com/Bytom/bytom Gitee地址:https://gitee.com/BytomBlockchai ...
- 剥开比原看代码13:比原是如何通过/list-balances显示帐户余额的?
作者:freewind 比原项目仓库: Github地址:https://github.com/Bytom/bytom Gitee地址:https://gitee.com/BytomBlockchai ...
- 剥开比原看代码12:比原是如何通过/create-account-receiver创建地址的?
作者:freewind 比原项目仓库: Github地址:https://github.com/Bytom/bytom Gitee地址:https://gitee.com/BytomBlockchai ...
- 剥开比原看代码11:比原是如何通过接口/create-account创建帐户的
作者:freewind 比原项目仓库: Github地址:https://github.com/Bytom/bytom Gitee地址:https://gitee.com/BytomBlockchai ...
- 剥开比原看代码08:比原的Dashboard是怎么做出来的?
作者:freewind 比原项目仓库: Github地址:https://github.com/Bytom/bytom Gitee地址:https://gitee.com/BytomBlockchai ...
- [oracle原]访问局域网内出现“ORA-12541:TNS:无监听程序”
近日在服务器局域网内27电脑上安装了oracle11g,本机上访问此数据库正常.但在局域网内其它机器上访问27上的数据库时,出现“ORA-12541:TNS:无监听程序”错误. 查27上的配置:D:\ ...
- (原)android中的动画(三)之动画监听&页面切换动画
1.动画也可以设置监听事件,例如在动画结束时需要执行某操作 把要执行的代码写在onAnimationEnd()回调方法中即可: anim.setAnimationListener(new Animat ...
- 关于websocket的代码,实现发送信息和监听信息(前端 后端(node.js))
文件结构 node.js代码 // 需要HTTP 模块来启动服务器和Socket.IOvar http= require('http');var fs = require('fs');// 在8080 ...
- 剥开比原看代码10:比原是如何通过/create-key接口创建密钥的
作者:freewind 比原项目仓库: Github地址:https://github.com/Bytom/bytom Gitee地址:https://gitee.com/BytomBlockchai ...
随机推荐
- JAVA基础1---Object类解析
1.Object简介 众所周知,Object类是Java所有类的万类之源,所有Java类都是继承之Object类,而默认就直接忽略了extends Object这段代码. 2.Object类的源码 话 ...
- qt creator翻译流程
一,下载qt creator软件包,最好是5.3, 本人是Win7,64位电脑,下载的是qt-opensource-windows-x86-mingw482_opengl-5.3.0.ex ...
- mysql-day06
##视图 - 什么是视图:在数据库中存在多种对象,表和视图都是数据库中的对象,创建视图时名称不能和表重名,视图实际上就代表一段sql查询语句,也可以理解成视图是一张虚拟的表,此虚拟表中的数据会随着原表 ...
- 计蒜客---N的-2进制表示
对于十进制整数N,试求其-2进制表示. 例如,因为 1*1 + 1*-2 + 1*4 + 0*-8 +1*16 + 1*-32 = -13 ,所以(-13)_10 = ( ...
- 注意!list和array是不同的
python中的list是python的内置数据类型,list中的数据类型不必相同的,而array的中的数据类型必须全部相同. numpy中封装的array有很强大的功能,里面存放的都是相同的数据类 ...
- 案例:配置apache和nginx的SSL加密传输协议
一.SSI解释 SSI是一种类似于ASP的基于服务器的网页制作技术.将内容发送到浏览器之前,可以使用“服务器端包含 (SSI)”指令将文本.图形或应用程序信息包含到网页中.例如,可以使用 SSI 包含 ...
- memcache,redis对比
一.问题: 数据库表数据量极大(千万条),要求让服务器更加快速地响应用户的需求. 二.解决方案: 1.通过高速服务器Cache缓存数据库数据 2.内存数据库 ( ...
- glog日志库移植Android平台
1.在linux平台下使用ndk交叉编译链编译glog生成libglog.a静态库. 2.将生成的库文件与头文件放到Android项目中,使用JNI方法调用. 3.编译遇到错误“stderr.stdo ...
- IE haslayout的属性及其值
haslayout是IE 渲染引擎的一个内部组成部分.在IE 中,一个元素要么自己对自身的内容进行计算大小和组织,要么依赖于父元素来计算尺寸和组织内容.为了调节这两个不同的概念,渲染引擎采用了hasl ...
- javascript 的01
javaScript可以实现验证表单.制作特效等功能,JavaScript的目的主要是一下三点: 1 客户端表单验证2 页面动态效果3 jQuery的基础JavaScript是一种描述性语言,也是一种 ...