標籤:pre this vat 方法 pad com ict art lookup
dial.go在p2p裡面主要負責建立連結的部分工作。 比如發現建立連結的節點。 與節點建立連結。 通過discover來尋找指定節點的地址。等功能。
dial.go裡面利用一個dailstate的資料結構來儲存中間狀態,是dial功能裡面的核心資料結構。
// 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 //discoverTable 用來做節點查詢的
netrestrict *netutil.Netlist
lookupRunning bool dialing map[discover.NodeID]connFlag //正在連結的節點 lookupBuf []*discover.Node // current discovery lookup results //當前的discovery查詢結果 randomNodes []*discover.Node // filled from Table //從discoverTable隨機查詢的節點 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 //這個是內建的節點。 如果沒有找到其他節點。那麼使用連結這些節點。}
dailstate的建立過程。
func newDialState(static []*discover.Node, bootnodes []*discover.Node, ntab discoverTable, maxdyn int, netrestrict *netutil.Netlist) *dialstate { s := &dialstate{ maxDynDials: maxdyn, ntab: ntab, netrestrict: netrestrict, static: make(map[discover.NodeID]*dialTask), dialing: make(map[discover.NodeID]connFlag), bootnodes: make([]*discover.Node, len(bootnodes)), randomNodes: make([]*discover.Node, maxdyn/2), hist: new(dialHistory), } copy(s.bootnodes, bootnodes) for _, n := range static { s.addStatic(n) } return s}
dail最重要的方法是newTasks方法。這個方法用來產生task。 task是一個介面。有一個Do的方法。
type task interface { Do(*Server)}func (s *dialstate) newTasks(nRunning int, peers map[discover.NodeID]*Peer, now time.Time) []task { if s.start == (time.Time{}) { s.start = now } var newtasks []task //addDial是一個內部方法, 首先通過checkDial檢查節點。然後設定狀態,最後把節點增加到newtasks隊列裡面。 addDial := func(flag connFlag, n *discover.Node) bool { if err := s.checkDial(n, peers); err != nil { log.Trace("Skipping dial candidate", "id", n.ID, "addr", &net.TCPAddr{IP: n.IP, Port: int(n.TCP)}, "err", err) return false } s.dialing[n.ID] = flag newtasks = append(newtasks, &dialTask{flags: flag, dest: n}) return true } // Compute number of dynamic dials necessary at this point. needDynDials := s.maxDynDials //首先判斷已經建立的串連的類型。如果是動態類型。那麼需要建立動態連結數量減少。 for _, p := range peers { if p.rw.is(dynDialedConn) { needDynDials-- } } //然後再判斷正在建立的連結。如果是動態類型。那麼需要建立動態連結數量減少。 for _, flag := range s.dialing { if flag&dynDialedConn != 0 { needDynDials-- } } // Expire the dial history on every invocation. s.hist.expire(now) // Create dials for static nodes if they are not connected. //查看所有的靜態類型。如果可以那麼也建立連結。 for id, t := range s.static { err := s.checkDial(t.dest, peers) switch err { case errNotWhitelisted, errSelf: log.Warn("Removing static dial candidate", "id", t.dest.ID, "addr", &net.TCPAddr{IP: t.dest.IP, Port: int(t.dest.TCP)}, "err", err) delete(s.static, t.dest.ID) case nil: s.dialing[id] = t.flags newtasks = append(newtasks, t) } } // If we don‘t have any peers whatsoever, try to dial a random bootnode. This // scenario is useful for the testnet (and private networks) where the discovery // table might be full of mostly bad peers, making it hard to find good ones. //如果當前還沒有任何連結。 而且20秒(fallbackInterval)內沒有建立任何連結。 那麼就使用bootnode建立連結。 if len(peers) == 0 && len(s.bootnodes) > 0 && needDynDials > 0 && now.Sub(s.start) > fallbackInterval { bootnode := s.bootnodes[0] s.bootnodes = append(s.bootnodes[:0], s.bootnodes[1:]...) s.bootnodes = append(s.bootnodes, bootnode) if addDial(dynDialedConn, bootnode) { needDynDials-- } } // Use random nodes from the table for half of the necessary // dynamic dials. //否則使用1/2的隨機節點建立連結。 randomCandidates := needDynDials / 2 if randomCandidates > 0 { n := s.ntab.ReadRandomNodes(s.randomNodes) for i := 0; i < randomCandidates && i < n; i++ { if addDial(dynDialedConn, s.randomNodes[i]) { needDynDials-- } } } // Create dynamic dials from random lookup results, removing tried // items from the result buffer. i := 0 for ; i < len(s.lookupBuf) && needDynDials > 0; i++ { if addDial(dynDialedConn, s.lookupBuf[i]) { needDynDials-- } } s.lookupBuf = s.lookupBuf[:copy(s.lookupBuf, s.lookupBuf[i:])] // Launch a discovery lookup if more candidates are needed. // 如果就算這樣也不能建立足夠動態連結。 那麼建立一個discoverTask用來再網路上尋找其他的節點。放入lookupBuf if len(s.lookupBuf) < needDynDials && !s.lookupRunning { s.lookupRunning = true newtasks = append(newtasks, &discoverTask{}) } // Launch a timer to wait for the next node to expire if all // candidates have been tried and no task is currently active. // This should prevent cases where the dialer logic is not ticked // because there are no pending events. // 如果當前沒有任何任務需要做,那麼建立一個睡眠的任務返回。 if nRunning == 0 && len(newtasks) == 0 && s.hist.Len() > 0 { t := &waitExpireTask{s.hist.min().exp.Sub(now)} newtasks = append(newtasks, t) } return newtasks}
checkDial方法, 用來檢查任務是否需要建立連結。
func (s *dialstate) checkDial(n *discover.Node, peers map[discover.NodeID]*Peer) error { _, dialing := s.dialing[n.ID] switch { case dialing: //正在建立 return errAlreadyDialing case peers[n.ID] != nil: //已經連結了 return errAlreadyConnected case s.ntab != nil && n.ID == s.ntab.Self().ID: //建立的對象不是自己 return errSelf case s.netrestrict != nil && !s.netrestrict.Contains(n.IP): //網路限制。 對方的IP地址不在白名單裡面。 return errNotWhitelisted case s.hist.contains(n.ID): // 這個ID曾經連結過。 return errRecentlyDialed } return nil}
taskDone方法。 這個方法再task完成之後會被調用。 查看task的類型。如果是連結任務,那麼增加到hist裡面。 並從正在連結的隊列刪除。 如果是查詢任務。 把查詢的記過放在lookupBuf裡面。
func (s *dialstate) taskDone(t task, now time.Time) { switch t := t.(type) { case *dialTask: s.hist.add(t.dest.ID, now.Add(dialHistoryExpiration)) delete(s.dialing, t.dest.ID) case *discoverTask: s.lookupRunning = false s.lookupBuf = append(s.lookupBuf, t.results...) }}
dialTask.Do方法,不同的task有不同的Do方法。 dailTask主要負責建立連結。 如果t.dest是沒有ip地址的。 那麼嘗試通過resolve查詢ip地址。 然後調用dial方法建立連結。 對於靜態節點。如果第一次失敗,那麼會嘗試再次resolve靜態節點。然後再嘗試dial(因為靜態節點的ip是配置的。 如果靜態節點的ip地址變動。那麼我們嘗試resolve靜態節點的新地址,然後調用連結。)
func (t *dialTask) Do(srv *Server) { if t.dest.Incomplete() { if !t.resolve(srv) { return } } success := t.dial(srv, t.dest) // Try resolving the ID of static nodes if dialing failed. if !success && t.flags&staticDialedConn != 0 { if t.resolve(srv) { t.dial(srv, t.dest) } }}
resolve方法。這個方法主要調用了discover網路的Resolve方法。如果失敗,那麼逾時再試
// resolve attempts to find the current endpoint for the destination// using discovery.//// Resolve operations are throttled with backoff to avoid flooding the// discovery network with useless queries for nodes that don‘t exist.// The backoff delay resets when the node is found.func (t *dialTask) resolve(srv *Server) bool { if srv.ntab == nil { log.Debug("Can‘t resolve node", "id", t.dest.ID, "err", "discovery is disabled") return false } if t.resolveDelay == 0 { t.resolveDelay = initialResolveDelay } if time.Since(t.lastResolved) < t.resolveDelay { return false } resolved := srv.ntab.Resolve(t.dest.ID) t.lastResolved = time.Now() if resolved == nil { t.resolveDelay *= 2 if t.resolveDelay > maxResolveDelay { t.resolveDelay = maxResolveDelay } log.Debug("Resolving node failed", "id", t.dest.ID, "newdelay", t.resolveDelay) return false } // The node was found. t.resolveDelay = initialResolveDelay t.dest = resolved log.Debug("Resolved node", "id", t.dest.ID, "addr", &net.TCPAddr{IP: t.dest.IP, Port: int(t.dest.TCP)}) return true}
dial方法,這個方法進行了實際的網路連接操作。 主要通過srv.SetupConn方法來完成, 後續再分析Server.go的時候再分析這個方法。
// dial performs the actual connection attempt.func (t *dialTask) dial(srv *Server, dest *discover.Node) bool { fd, err := srv.Dialer.Dial(dest) if err != nil { log.Trace("Dial error", "task", t, "err", err) return false } mfd := newMeteredConn(fd, false) srv.SetupConn(mfd, t.flags, dest) return true}
discoverTask和waitExpireTask的Do方法,
func (t *discoverTask) Do(srv *Server) { // newTasks generates a lookup task whenever dynamic dials are // necessary. Lookups need to take some time, otherwise the // event loop spins too fast. next := srv.lastLookup.Add(lookupInterval) if now := time.Now(); now.Before(next) { time.Sleep(next.Sub(now)) } srv.lastLookup = time.Now() var target discover.NodeID rand.Read(target[:]) t.results = srv.ntab.Lookup(target)}func (t waitExpireTask) Do(*Server) { time.Sleep(t.Duration)}
區塊鏈入門教程以太坊源碼分析p2p-dial.go源碼分析