class QuartzTorrent::PeerManager
This class is used internally by PeerClient
(The bittorrent protocol object) to choke, unchoke, and connect to peers for a specific torrent.
Public Class Methods
new()
click to toggle source
# File lib/quartz_torrent/peermanager.rb, line 29 def initialize @logger = LogManager.getLogger("peer_manager") @targetActivePeerCount = 50 @targetUnchokedPeerCount = 4 @cachedHandshakingAndEstablishedCount = 0 # An array of Peers that we are allowing to download. @downloaders = [] @optimisticUnchokePeer = nil # A peer is considered newly connected when the number of seconds it has had it's connection established # is below this number. @newlyConnectedDuration = 60 @optimisticPeerChangeDuration = 30 @lastOptimisticPeerChangeTime = nil end
Public Instance Methods
manageConnections(classifiedPeers)
click to toggle source
Determine if we need to connect to more peers. Returns a list of peers to connect to.
# File lib/quartz_torrent/peermanager.rb, line 46 def manageConnections(classifiedPeers) n = classifiedPeers.handshakingPeers.size + classifiedPeers.establishedPeers.size if n < @targetActivePeerCount result = classifiedPeers.disconnectedPeers.shuffle.first(@targetActivePeerCount - n) @logger.debug "There are #{n} peers connected or in handshaking. Will establish #{result.size} more connections to peers." result else [] end end
managePeers(classifiedPeers)
click to toggle source
Given a list of Peer
objects (torrent peers), calculate the actions to take.
# File lib/quartz_torrent/peermanager.rb, line 60 def managePeers(classifiedPeers) result = ManagePeersResult.new @logger.debug "Manage peers: #{classifiedPeers.disconnectedPeers.size} disconnected, #{classifiedPeers.handshakingPeers.size} handshaking, #{classifiedPeers.establishedPeers.size} established" @logger.debug "Manage peers: #{classifiedPeers}" # Unchoke some peers. According to the specification: # # "...unchoking the four peers which have the best upload rate and are interested. These four peers are referred to as downloaders, because they are interested in downloading from the client." # "Peers which have a better upload rate (as compared to the downloaders) but aren't interested get unchoked. If they become interested, the downloader with the worst upload rate gets choked. # If a client has a complete file, it uses its upload rate rather than its download rate to decide which peers to unchoke." # "at any one time there is a single peer which is unchoked regardless of its upload rate (if interested, it counts as one of the four allowed downloaders). Which peer is optimistically # unchoked rotates every 30 seconds. Newly connected peers are three times as likely to start as the current optimistic unchoke as anywhere else in the rotation. This gives them a decent chance # of getting a complete piece to upload." # # This doesn't define initial rampup; On rampup we have no peer upload rate information. # We want to end up with: # - At most 4 peers that are both interested and unchoked. These are Downloaders. They should be the ones with # the best upload rate. # - All peers that have a better upload rate than Downloaders and are not interested are unchoked. # - One random peer that is unchoked. If it is interested, it is one of the 4 downloaders. # When choosing this random peer, peers that have connected in the last N seconds should be 3 times more # likely to be chosen. This peer only changes every 30 seconds. # Step 1: Pick the optimistic unchoke peer selectOptimisticPeer(classifiedPeers) # Step 2: Update the downloaders to be the interested peers with the best upload rate. if classifiedPeers.interestedPeers.size > 0 bestUploadInterested = classifiedPeers.interestedPeers.sort{ |a,b| b.uploadRate.value <=> a.uploadRate.value}.first(@targetUnchokedPeerCount) # If the optimistic unchoke peer is interested, he counts as a downloader. if @optimisticUnchokePeer && @optimisticUnchokePeer.peerInterested peerAlreadyIsDownloader = false bestUploadInterested.each do |peer| if peer.eql?(@optimisticUnchokePeer) peerAlreadyIsDownloader = true break end end bestUploadInterested[bestUploadInterested.size-1] = @optimisticUnchokePeer if ! peerAlreadyIsDownloader end # If one of the downloaders has changed, choke the peer downloadersMap = {} @downloaders.each{ |d| downloadersMap[d.trackerPeer] = d } bestUploadInterested.each do |peer| if downloadersMap.delete peer.trackerPeer # This peer was already a downloader. No changes. else # This peer wasn't a downloader before. Now it is; unchoke it result.unchoke.push peer if peer.peerChoked end end # Any peers remaining in the map are no longer a downloader. Choke them. result.choke = result.choke.concat(downloadersMap.values) @downloaders = bestUploadInterested end # Step 3: Unchoke all peers that have a better upload rate but are not interested. # However, if we just started up, only unchoke targetUnchokedPeerCount peers. if @downloaders.size > 0 if classifiedPeers.uninterestedPeers.size > 0 classifiedPeers.uninterestedPeers.each do |peer| if peer.uploadRate.value > @downloaders[0].uploadRate.value && peer.peerChoked result.unchoke.push peer end if peer.uploadRate.value < @downloaders[0].uploadRate.value && ! peer.peerChoked && ! peer.eql?(@optimisticUnchokePeer) result.choke.push peer end end end else # No downloaders yet, so we can't tell who is fast or not. Unchoke some result.unchoke = result.unchoke.concat(classifiedPeers.uninterestedPeers.first(@targetUnchokedPeerCount)) end @logger.debug "Manage peers result: #{result}" result end
Private Instance Methods
selectOptimisticPeer(classifiedPeers)
click to toggle source
Choose a peer that we will optimistically unchoke.
# File lib/quartz_torrent/peermanager.rb, line 148 def selectOptimisticPeer(classifiedPeers) # "at any one time there is a single peer which is unchoked regardless of its upload rate (if interested, it counts as one of the four allowed downloaders). Which peer is optimistically # unchoked rotates every 30 seconds. Newly connected peers are three times as likely to start as the current optimistic unchoke as anywhere else in the rotation. This gives them a decent chance # of getting a complete piece to upload." if !@lastOptimisticPeerChangeTime || (Time.new - @lastOptimisticPeerChangeTime > @optimisticPeerChangeDuration) list = [] classifiedPeers.establishedPeers.each do |peer| if (Time.new - peer.firstEstablishTime) < @newlyConnectedDuration 3.times{ list.push peer } else list.push peer end end @optimisticUnchokePeer = list[rand(list.size)] if @optimisticUnchokePeer @logger.info "Optimistically unchoked peer set to #{@optimisticUnchokePeer.trackerPeer}" @lastOptimisticPeerChangeTime = Time.new end end end