p2p模块对外暴露了Server关键结构,帮助上层管理复杂的p2p网路,使其集中于Protocol的实现,只关注于数据的传输.
Server使用discover模块,在指定的UDP端口管理网络中结点的发现以及维护,discover模块能够直接和临近结点交换各自已知结点信息,从而不断的更新结点网络.
Server还是用nat模块来进行TCP端口映射,而nat主要是利用upnp和pmp两个协议,如果没有,那就只能认为指定的ip就是公网ip了.
Server要同时在udp和tcp的30303端口和p2p网络进行数据交换,前者是用来维护P2p网络,后者则是各种应用协议真正交换数据的地方.他们使用的格式是Msg,具体见 p2p_server.
关于Server以及Protocol如何使用可以见 Peer-to-Peer 虽然不是很详细,但是已经基本上说明了Server以及Protocol如何使用,感兴趣可以基于这个p2p网络实现自己的协议,可以完全与以太坊应用没关系.https://github.com/ethereum/go-ethereum/wiki/Peer-to-Peer
dial.go阅读手记dial.go是负责和peer建立连接关系的地方,主要是实现
type dialer interface {
/*
peers已经有的结点
*/
newTasks(running int, peers map[discover.NodeID]*Peer, now time.Time) []task
taskDone(task, time.Time)
addStatic(*discover.Node)
removeStatic(*discover.Node)
}
// dialstate schedules dials and discovery lookups.
// it get's a chance to compute new tasks on every iteration
// of the main loop in Server.run.
type dialstate struct {
maxDynDials int
ntab discoverTable
netrestrict *netutil.Netlist
lookupRunning bool
dialing map[discover.NodeID]connFlag //正在创建的连接
lookupBuf []*discover.Node // current discovery lookup results
randomNodes []*discover.Node // filled from Table
static map[discover.NodeID]*dialTask
hist *dialHistory
start time.Time // time when the dialer was first used
bootnodes []*discover.Node // default dials when there are no peers
}
其中最复杂的是newTasks,是建立新的连接,从test代码中可以看出,
要在指定的最大连接数(peers)基之上去创建新的连接
lookupBuf // current discovery lookup results
主要是在结束taskdone的时候添加已经发现的?
在不超过maxDynDials的情况下,首先减去peers已有的连接,然后是static中的任务,
如果过还有富余,富余空间中最多一半(根据实现,可能为0)用ReadRandomNodes填充,剩下的
就用lookupBuf来填充,如果lookbuf中没有有效的任务,那么就创建一个discoverTask,
如果还有空间,就创建一个waitExpireTask
总之newTasks就是在尽可能的情况下,多创建任务.为Server.run scheduleTasks时服务,
保证其能够连接到尽可能多的节点.如果节点数量不够的情况下,还没有dialTask就创建discoverTask,否则就创建一个WaitExpireTask
该模块相对比较简单,因为nat的真正实现并不在此模块,主要是使用了第三方的nat-upnp和nat-pmp来实现真正的穿透(端口映射).
对外公布的接口
```go
// An implementation of nat.Interface can map local ports to ports
// accessible from the Internet.
type Interface interface {
// These methods manage a mapping between a port on the local
// machine to a port that can be connected to from the internet.
//
// protocol is "UDP" or "TCP". Some implementations allow setting
// a display name for the mapping. The mapping may be removed by
// the gateway when its lifetime ends.
AddMapping(protocol string, extport, intport int, name string, lifetime time.Duration) error
DeleteMapping(protocol string, extport, intport int) error
// This method should return the external (Internet-facing)
// address of the gateway device.
ExternalIP() (net.IP, error)
// Should return name of the method. This is used for logging.
String() string
}
主要有三个关键函数一个是添加映射,一个是删除映射,另一个是获取外部IP.使用起来非常直观,我们这里看一个例子,nat.go中的Map函数中,
``m.AddMapping(protocol, extport, intport, name, mapTimeout);``
我看ethereum使用的时候,extport和intport都一样.
Interface总共有四个实现分别是upnp,pmp,extIP和startautodisc,从名字可以看出这四个实现都没有暴露给调用者,也就是内部使用,其中extIP是在本机IP就是公网IP的情况下使用,也就是无需端口映射的时候使用.upnp和pmp是对两种端口映射协议的使用封装,而startautodisc则是对这两者的再次封装统一.
那么nat模块怎么使用呢,用起来其实很简单.下面是示例代码,不完整.
```go
//获取nat实例,
var m nat.Interface=nat.Any()
//然后就可以使用AddMapping等操作了,
//nat还提供了更方便的函数Map来保持端口映射
//用法如下 来自server.go
nat.Map(srv.NAT, srv.quit, "tcp", laddr.Port, laddr.Port, "ethereum p2p")