def runOU(self, nSlots, TTL): Log.pLD(self, "Executing OU Algorithm for {0}".format(nSlots) ) #print("[{0}] peers [ {1} ]".format(self.pid, self._peersConn)) #t = SSimulator().tick chosen = list() #Calculate the number of current OU Slots and possible candidates that are interested but not in OU or TFT candidates = self.getOUCandidates() if(len(candidates) < nSlots): self._getMorePeersFlag = True nSlots = len(candidates) if(nSlots == 0): return chosen candidates = random.sample(candidates, len(candidates)) #Array of peerId while( len(chosen) < nSlots ): p = candidates.pop(0) #Only unchok peers that are not already unchoked! if( (p[4] != self.OU_SLOT) ): self._peersConn[p[1]][2].unchock() self._peersConn[p[1]] = ( p[0], p[1], p[2], TTL, self.OU_SLOT ) chosen.append(p[1]) #Do chocking of all OU peers that currently are unchoked but not have been chosen in this round for p in self._peersConn.values(): if( (p[4] == self.OU_SLOT) and (chosen.count(p[1]) == 0) ): self._peersConn[p[1]] = ( p[0], p[1], p[2], -1, self.NO_SLOT ) self._peersConn[p[1]][2].chock() self._nOUSlots = len(chosen) return chosen
def updateLocalConnectionState(self): self._activeDownloadBandwidthUsage = 0 if(self._leaveNextRound == True): self._torrent.tracker.remPeer(self) self._disconnectConnections() self.removeSimElement() Log.pLI(self, "Leaving torrent ...") if self._torrent.isFinished() == True : if( self._downloadEnd == -1): self._downloadEnd = SSimulator().tick #So check if we want to leave or stay as a seeder if( self._leaveTorrent() == True ): self._leaveNextRound = True #Leave next round, if not, the peer wont be seen by the observer as finished! else: if( (SSimulator().tick >= self._nextPieceQueueUpdate) or (len(self.piecesQueue) == 0) ): Log.pLD(self, "Getting new piece list for downloading ..." ) (self.piecesQueue, self._pieceAvailability) = self.pieceSelection(self._pieceRandomizeCount) #Simulates the queuing of piece requests to other peers self._nextPieceQueueUpdate = SSimulator().tick + 5 #Shedule next update #Call updateLocalState() on each connection and update their averageDownloadRate finishedPieces = self._torrent.getFinishedPieces() for i in self._peersConn.values() : i[2].updateLocalState(finishedPieces)
def getNewPeerList(self): #Dont create too many connections to other peers ( because of performance problems ) if(len(self._peersConn) >= self._maxPeerListSize): return t = SSimulator().tick if( (t - self._lastPeerListUpdate) < self._timeBetweenPeerListUpdates): return else: self._lastPeerListUpdate = t Log.pLD(self, "Ask Tracker for new peers ...".format()) newPeers = self._torrent.tracker.getPeerList() #Filter unwanted peers , for example itself def f(x): return ( x.pid != self.pid ) newPeers = filter( f, newPeers ) for i in newPeers : if (i.pid in self._peersConn) == False: newConnection = Connection(self, i) self._peersConn[i.pid] = ( 0, i.pid, newConnection, -1 , self.NO_SLOT ) newConnection.setUploadLimit( self._calculateUploadRate(newConnection) ) #newConnection.setDownloadLimit( self._calculateDownloadRate(newConnection) ) newConnection.connect() Log.pLD(self, "adding Peer [{0}]".format(i.pid))
def _dropPeers(self, maxPeers): p = list(self._peersConn) nDrop = len(p) - maxPeers if(nDrop < 0): return #Nothing to do while(nDrop > 0 and len(p) > 0): pID = random.sample(p, 1)[0] #print("Dropping peer {} from {}".format(pID, p)) p.remove(pID) c = self._peersConn[pID] if(c[2].interested == True): continue #Do not drop peers we are interested in if(c[2].peerIsInterested() == True): continue #Do not drop peers that have interest in our data if(c[4] != Peer.NO_SLOT): continue #Do not drop active connection of any kind if(c[2].getDownloadRate() > 0): continue #Do not drop peers we are downloading from Log.pLD(self, "Dropping peer [{}]".format( c[1]) ) self._peersConn[pID][2].disconnect() nDrop-=1
def unchock(self, uploadRate=-1): if(uploadRate > -1): self._maxUploadRate = int(uploadRate) #if(downloadRate > -1): # self._maxDownloadRate = int(downloadRate) self.chocking = False Log.pLD(self._srcPeer, "unchocking [{0}@{1}]".format(self._destPeer.pid, self._maxUploadRate) ) self.__calculateUploadRate()
def runSeederOU(self, nRepeatingSlots, nRandomSlots, TTL): Log.pLD(self, "Executing Seeder OU Algorithm for {} reapting, and {} ramdon Slots".format(nRepeatingSlots, nRandomSlots) ) #t = SSimulator().tick chosen = list() #Calculate the number of current OU Slots and possible candidates that are interested but not in OU or TFT candidates = self.getOUCandidates() if(len(candidates) == 0): return chosen #Shuffle candidates to select nRandomSlots really random random.shuffle(candidates) candidates2 = list(candidates) #Get nRepeatingSlots; Only peers that we are already uploading data to! while( (len(chosen) < nRepeatingSlots) and (len(candidates) > 0) ): p = candidates.pop(0) #Only get already OU allocated ones if( (p[4] != self.OU_SLOT) ): continue #Skip un interested ones if( p[2].peerIsInterested() == False): continue self._peersConn[p[1]] = ( p[0], p[1], p[2], TTL, self.OU_SLOT ) chosen.append(p[1]) #No randomly select nRandomSlots more ( or if we could not select enough nRepeatingSlots, select a random one too ) candidates = candidates2 while( (len(chosen) < nRepeatingSlots+nRandomSlots) and (len(candidates) > 0) ): p = candidates.pop(0) if(p in chosen): continue #Only unchok peers that are not already unchoked! if( (p[4] != self.OU_SLOT) ): self._peersConn[p[1]][2].unchock() self._peersConn[p[1]] = ( p[0], p[1], p[2], TTL, self.OU_SLOT ) chosen.append(p[1]) #Do chocking of all OU peers that currently are unchoked but not have been chosen in this round for p in self._peersConn.values(): if( (p[4] == self.OU_SLOT) and (chosen.count(p[1]) == 0) ): self._peersConn[p[1]] = ( p[0], p[1], p[2], -1, self.NO_SLOT ) self._peersConn[p[1]][2].chock() self._nOUSlots = len(chosen) return chosen
def connectToPeer(self, peer): #logging.log(Simulator.DEBUG, "[{0}] External peer is connecting! [{1}]".format(self.pid, peer.pid)) Log.pLD(self, "External peer is connecting! [{0}]".format(peer.pid) ) newConnection = None #If we already now this peer, return current connection if peer.pid in self._peersConn : newConnection = self._peersConn[peer.pid][2] else: Log.pLD(self, "adding Peer [{0}]".format(peer.pid)) newConnection = Connection(self, peer) self._peersConn[peer.pid] = ( 0, peer.pid, newConnection, -1 , self.NO_SLOT) newConnection.setUploadLimit( self._calculateUploadRate(newConnection) ) #newConnection.setDownloadLimit( self._calculateDownloadRate(newConnection) ) self._peersConn[peer.pid][2].connect() #Return the connection reference return newConnection
def runTFT(self, nSlots, TTL): Log.pLD(self, " Executing TFT Algorithm for {0}".format(nSlots) ) #self._peersConn.sort() #Peer list in the form ( <UploadRate>, <PeerID>, <Connection> ) , sort will on the first field and on collision continue with the others #t = SSimulator().tick chosen = list() #Now everyone is a candidate, take even current OU Slots, this makes sense to step up a peer from OU to TFT is they are good candidates = self.getTFTCandidates() if(len(candidates) == 0): self._getMorePeersFlag = True #return chosen #DO NOT RETURN HERE OR THE CHOCKING AT THE END OF THE FUNCTION WONT BE APPLIED candidates.sort(reverse=True) #Sort candidates based on their uploadRate, (highest uploadRate first) shuffledFlag = False while( (len(chosen) < nSlots) and (len(candidates) > 0) ): p = candidates.pop(0) #If all the rest of the peers have zero upload, shuffle them if((p[0] == 0) and (shuffledFlag == False)): shuffledFlag = True #Only shuffle once candidates.append(p) candidates = random.sample(candidates, len(candidates)) #Array of peerId p = candidates.pop(0) #Only unchok peers that are not already unchoked! if( (p[4] != self.TFT_SLOT) ): self._peersConn[p[1]][2].unchock() self._peersConn[p[1]] = ( p[0], p[1], p[2], TTL, self.TFT_SLOT ) chosen.append(p[1]) #Do chocking of all TFT peers that currently are unchocked but not have been chosen in this round for p in self._peersConn.values(): if( (p[4] == self.TFT_SLOT) and (chosen.count(p[1]) == 0) ): self._peersConn[p[1]] = ( p[0], p[1], p[2], -1, self.NO_SLOT ) self._peersConn[p[1]][2].chock() self._nTFTSlots = len(chosen) return chosen
def updateGlobalState(self): if( self.disconnected == True ): #Connection was disconnected, tell peer self._srcPeer.peerDisconnect(self) return #Check if we are seeding , if so, we are __NEVER__ interested if(self._srcPeer.getTorrent().isFinished() == False): #Check what the other peer has to offer self._downloadablePieces = self.__calcDownloadablePieceSet() if(len(self._downloadablePieces) > 0): Log.pLD(self._srcPeer, "Having interest in {0} from {1}".format( len(self._downloadablePieces), self._destPeer.pid) ) self.interested = True self.__setCurrentPiece() #if( self.chocking == True ): # self.unchock(self._uploadRate, 0) else: Log.pLD(self._srcPeer, "Loosing interest in peer {0}".format(self._destPeer.pid) ) #self.chocking = True self.interested = False else: self.interested = False
def _wakeUpPeer(self): if( self._sleepTime > 0): self._sleepTime -= 1 return Log.pLD(self, "Node waking up ...".format()) #Unregister sleep and setup peer for normal operation self.unregisterSimFunction(Simulator.ST_INIT, self._wakeUpPeer ) self.registerSimFunction(Simulator.ST_UPDATE_LOCAL, self.updateLocalConnectionState ) self.registerSimFunction(Simulator.ST_UPDATE_GLOBAL, self.updateGlobalConnectionState ) self.registerSimFunction(Simulator.ST_LOGIC, self.peerLogic ) self.registerSimFunction(Simulator.ST_FILETRANSFER, self._runDownloads ) self.registerSimFunction(Simulator.ST_CONCLUTION, self._conclusionState ) #Decide when to run tft and ou algorithm next time self._nextTFTPhaseStart = SSimulator().tick self._nextOUPhaseStart = SSimulator().tick self._nextPieceQueueUpdate = SSimulator().tick #Register to tracker self._torrent.tracker.connect(self)
def runDownload(self): #If nothing happens, nothing is downloaded self._downloadRate = 0 #Downloading happens if we are interested and the other peer is not chocking us if( (self.interested == True) and (self.remoteConnection.chocking == False) ): #Limit maximum download rate currentDownloadRate = self.remoteConnection.getUploadRate() currentDownloadRate = self._srcPeer.requestDownloadBandwidth(currentDownloadRate) #if(currentDownloadRate > self._maxDownloadRate): # currentDownloadRate = self._maxDownloadRate #Check if we finished a complete piece and if so mark it as finished and get the next one self._acumulatedData += currentDownloadRate self._downloadRate = currentDownloadRate #Make this int so we dont get too long floating number in output log self._downloadRate = int(self._downloadRate) Log.pLD(self._srcPeer, "Downloaded {0}/{1} of piece {2}".format(self._acumulatedData, self._srcPeer.getTorrent().pieceSizeBytes, self._currentPiece) ) while(self._acumulatedData > self._srcPeer.getTorrent().pieceSizeBytes): Log.pLD(self._srcPeer, "Finished downloading piece {0}".format(self._currentPiece) ) self._srcPeer.finishedDownloadingPiece(self, self._currentPiece ) self._acumulatedData -= self._srcPeer.getTorrent().pieceSizeBytes self._currentPiece = -1 #Set a next piece and utilize the data downloaded for this one. If there are no more pieces discard the downloaded data if(self.__setCurrentPiece() == False): self._acumulatedData = 0 #This is simulating lost bandwidth due to running out of piece requests Log.pLD(self._srcPeer, "Piece request queue empty!" ) #On start of next round we will loose interest #self.interested = False if(self._currentPiece == -1): self.__setCurrentPiece()
def chock(self): self.chocking = True self._uploadRate = 0 Log.pLD(self._srcPeer, "chocking [{0}]".format(self._destPeer.pid) )
def __del__(self): Log.pLD("Peer is being destroyed")
def runTFT(self, nSlots, TTL): candidates = self.getTFTCandidates() if len(candidates) == 0: self._getMorePeersFlag = True #return list() #DO NOT RETURN HERE OR THE CHOCKING AT THE END OF THE FUNCTION WONT BE APPLIED #Create a list of tuples, or peer rating and peer id #Rate peers depending on their download/upload ration. New peers get a ration of zero (worst) , maybe change this to one? rated = [] for (idx,i) in enumerate(candidates) : if( i[2].getUploadLimit() == 0): rated.append( (0, idx) ) else: rated.append( (i[0] / i[2].getUploadLimit() , idx) ) rated.sort(reverse=True) rated2 = list(rated) #Copy the rated list for later chosen = [] #This will be set earlier by self._calculateSlotCountAndUploadRate() maxUpload = self._maxTFTUploadRate acUploadRate = 0 #nSlots = min(nSlots, len(rated)) while( (acUploadRate < maxUpload ) and (len(chosen) < nSlots) and (len(rated) > 0) ): idx = rated.pop(0)[1] #Index of the elements in candidates p = candidates[idx] #Skip peers that would take too much upload if( (acUploadRate + p[2].getUploadLimit()) > maxUpload ): continue #Only unchok peers that are not already unchocked! if(p[4] != self.TFT_SLOT): self._peersConn[p[1]][2].unchock() self._peersConn[p[1]] = ( p[0],p[1],p[2], TTL , self.TFT_SLOT ) chosen.append(p[1]) acUploadRate += p[2].getUploadLimit() #If we are not able to use all our upload capacity something is wrong. Do more peer discovery -> get more peers restOfUploadBandwidth = maxUpload - acUploadRate if(restOfUploadBandwidth>100): self._getMorePeersFlag = True #Take another peer and limit the upload to the available bandwidth rated = rated2 while( (acUploadRate < maxUpload ) and (len(chosen) < nSlots) and (len(rated) > 0) ): idx = rated.pop()[1] #Index of the elements in candidates p = candidates[idx] #Skip peers that would take too much upload if( p[1] in chosen ): continue #Only unchok peers that are not already unchocked! if(p[4] != self.TFT_SLOT): self._peersConn[p[1]][2].unchock() p[2].setUploadLimit(restOfUploadBandwidth)#Important self._peersConn[p[1]] = ( p[0],p[1],p[2], TTL , self.TFT_SLOT ) chosen.append(p[1]) acUploadRate += p[2].getUploadLimit() #Do chocking of all TFT peers that currently are unchocked but not have been chosen in this round for i in self._peersConn.values(): if( (i[4] == self.TFT_SLOT) and (chosen.count(i[1]) == 0) ): self._peersConn[i[1]] = ( i[0], i[1], i[2], -1, self.NO_SLOT ) self._peersConn[i[1]][2].chock() Log.pLD(self, " Executed modified TFT Algorithm and selected {0} peers".format( len(chosen) ) ) self._nTFTSlots = len(chosen) return chosen
def _runOU(self, nSlots, TTL): Log.pLD(self, "Executing OU Algorithm for {0}".format(nSlots) ) #print("[{0}] peers [ {1} ]".format(self.pid, self._peersConn)) #t = SSimulator().tick chosen = list() #Calculate the number of current OU Slots and possible candidates that are interested but not in OU or TFT candidates = self.getOUCandidates() if(len(candidates) < nSlots): self._getMorePeersFlag = True nSlots = len(candidates) #if(nSlots == 0): # return chosen #DO NOT RETURN HERE OR THE CHOCKING AT THE END OF THE FUNCTION WONT BE APPLIED random.shuffle(candidates) candidates2 = list(candidates) while( (len(chosen) < nSlots) and (len(candidates) > 0) ): p = candidates.pop(0) #Prefer interested peers if( p[2].peerIsInterested() == False): continue #Only unchok peers that are not already unchoked! if( (p[4] != self.OU_SLOT) ): self._peersConn[p[1]][2].unchock() self._peersConn[p[1]] = ( p[0], p[1], p[2], TTL, self.OU_SLOT ) chosen.append(p[1]) #If there are not enough interested peers, take some random peers if(len(chosen) < nSlots): self._getMorePeersFlag = True #Do more/better peer discovery candidates = candidates2 while( (len(chosen) < nSlots) and (len(candidates) > 0) ): p = candidates.pop(0) #Dont add peers twice if( (p[1] in chosen) == True): continue #Only unchok peers that are not already unchoked! if( (p[4] != self.OU_SLOT) ): self._peersConn[p[1]][2].unchock() self._peersConn[p[1]] = ( p[0], p[1], p[2], TTL, self.OU_SLOT ) chosen.append(p[1]) #Do chocking of all OU peers that currently are unchoked but not have been chosen in this round for p in self._peersConn.values(): if( (p[4] == self.OU_SLOT) and (chosen.count(p[1]) == 0) ): self._peersConn[p[1]] = ( p[0], p[1], p[2], -1, self.NO_SLOT ) self._peersConn[p[1]][2].chock() self._nOUSlots = len(chosen) if(self._nOUSlots < nSlots): pass return chosen