Choker.cpp

Go to the documentation of this file.
00001 /*
00002 
00003 Copyright (C) 2006-2007 by Peter Dimov.
00004 
00005 This file is part of Calitko (http://www.calitko.org).
00006 
00007 Calitko is free software; you can redistribute it and/or modify
00008 it under the terms of the GNU General Public License as published by
00009 the Free Software Foundation; either version 2 of the License, or
00010 (at your option) any later version.
00011 
00012 Calitko is distributed in the hope that it will be useful,
00013 but WITHOUT ANY WARRANTY; without even the implied warranty of
00014 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
00015 GNU General Public License for more details.
00016 
00017 You should have received a copy of the GNU General Public License
00018 along with Calitko; if not, write to the Free Software
00019 Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA
00020 
00021 */
00022 
00023 #include "Qt.h"
00024 #include "Choker.h"
00025 #include "TransferSession.h"
00026 #include "Imports.cpp"
00027 
00029 
00033 Choker::Choker (Timer *timer,
00034                 RandomNumberGenerator *randomNumberGenerator,
00035                 TransferManagerState *transferManagerState)
00036  :  timer (timer), randomNumberGenerator (randomNumberGenerator),
00037     transferManagerState (transferManagerState),
00038     sessions(), newSessions(), optimisticlyUnchokedSessions(),
00039     optimisticChokingCounter (0)
00040 {
00041 }
00042 
00044 Choker::~Choker()
00045 {
00046 }
00047 
00049 void Choker::start()
00050 {
00051     // \todo assert if already started??
00052     timer->start (Callable <void()> (this, &Choker::chokingTime),
00053                   ChokingTimeout);
00054 }
00055 
00057 void Choker::stop()
00058 {
00059     timer->stop();
00060 }
00061 
00063 void Choker::openedSession (TransferSession *session)
00064 {
00065     sessions += session;
00066     newSessions += session;
00067 }
00068 
00070 void Choker::incomingPacket (TransferSession *, const Packet &)
00071 {
00072     // Nothing to do here.
00073 }
00074 
00076 void Choker::outgoingPacket (TransferSession *, const Packet &)
00077 {
00078     // Nothing to do here.
00079 }
00080 
00082 
00087 void Choker::closedSession (TransferSession *session)
00088 {
00089     sessions.removeAll (session);
00090     newSessions.removeAll (session);
00091 }
00092 
00094 void Choker::chokingTime()
00095 {
00096     doOptimisticUnchokingIfTimeHasCome();
00097     sortSessionsForSelection();
00098     reallocateUploadSlotsAndChokeUnchokeSessions();
00099 }
00100 
00102 
00110 void Choker::doOptimisticUnchokingIfTimeHasCome()
00111 {
00112     ++optimisticChokingCounter;
00113     if (optimisticChokingCounter == OptimisticChokingRate) {
00114         optimisticChokingCounter = 0;
00115         optimisticlyUnchokedSessions = choseOptimisticlyUnchokedSessions();
00116         foreach (TransferSession *session, optimisticlyUnchokedSessions) {
00117             // The state of session selected for optimistic unchoke is only set
00118             // here and may not be modified (i.e. no choking/unchoking).
00119             Q_ASSERT (session->isPeerChoked());
00120             session->sendPacket (unchoke);
00121         }
00122     }
00123 }
00124 
00126 
00141 Choker::Sessions Choker::choseOptimisticlyUnchokedSessions()
00142 {
00143     Sessions chokedSessions;
00144     int numberOfSessionsSnubbed = 0;
00145     foreach (TransferSession *session, sessions) {
00146         // No seeders are optimistic unchoke candidates:
00147         if (session->isPeerChoked() && !session->isPeerSeeder()) {
00148             chokedSessions += session;
00149             // "Newly connected peers are three times as likely to start as the
00150             // current optimistic unchoke as anywhere else in the rotation."
00151             if (newSessions.contains (session)) {
00152                 chokedSessions += session; // two times more likely
00153                 chokedSessions += session; // three times more likely
00154             }
00155         }
00156         // Only do an optimistic unchoke for a snubbed session if downloading:
00157         if (session->areWeSnubbed() && transferManagerState->isDownloading())
00158             ++numberOfSessionsSnubbed;
00159     }
00160     newSessions.clear();
00161 
00162     // Add an extra optimistic unchoke for each unchoked peer that snubbed us:
00163     Sessions optimisticUnchokes;
00164     for (int i = numberOfSessionsSnubbed + 1;
00165          i > 0 && !chokedSessions.isEmpty();
00166          --i) {
00167         int index = randomNumberGenerator->random() % chokedSessions.size();
00168         TransferSession *luckySession = chokedSessions.at (index);
00169         optimisticUnchokes += luckySession;
00170         // New connections are added multiple times, make sure all are removed:
00171         chokedSessions.removeAll (luckySession);
00172     }
00173     return optimisticUnchokes;
00174 }
00175 
00177 
00185 void Choker::sortSessionsForSelection()
00186 {
00187     if (transferManagerState->isDownloading())
00188         qStableSort (sessions.begin(), sessions.end(),
00189                      moreDownloadedAndMoreInterested);
00190     else
00191         qStableSort (sessions.begin(), sessions.end(),
00192                      moreUploadedAndMoreInterested);
00193 }
00194 
00196 
00207 void Choker::reallocateUploadSlotsAndChokeUnchokeSessions()
00208 {
00209     int uploadSlotsLeft = numberOfUploadSlots();
00210 
00211     foreach (TransferSession *session, sessions) {
00212         // We don't change the status of an optimisticly unchoked session:
00213         if (optimisticlyUnchokedSessions.contains (session))
00214             continue;
00215 
00216         if (uploadSlotsLeft > 0 && !session->isPeerSeeder())
00217             unchokeSessionIfNeeded (session);
00218         else
00219             chokeSessionIfNeeded (session);
00220         // Decrease uploadSlotsLeft only if the other peer is interested, which
00221         // means it would use the slot we are offering to it.
00222         if (session->isPeerInterested())
00223             --uploadSlotsLeft;
00224     }
00225 }
00226 
00228 
00232 int Choker::numberOfUploadSlots() const
00233 {
00234     int uploadSlots = UploadSlotsCount;
00235 
00236     foreach (TransferSession *session, optimisticlyUnchokedSessions)
00237         if (session->isPeerInterested())
00238             --uploadSlots;
00239 
00240     return uploadSlots;
00241 }
00242 
00244 void Choker::chokeSessionIfNeeded (TransferSession *session)
00245 {
00246     if (!session->isPeerChoked())
00247         session->sendPacket (choke);
00248 }
00249 
00251 void Choker::unchokeSessionIfNeeded (TransferSession *session)
00252 {
00253     if (session->isPeerChoked())
00254         session->sendPacket (unchoke);
00255 }
00256 
00258 
00267 bool Choker::moreDownloadedAndMoreInterested (const TransferSession *a,
00268                                               const TransferSession *b)
00269 {
00270     qint64 aSpeed = a->currentDownloadSpeed();
00271     qint64 bSpeed = b->currentDownloadSpeed();
00272 
00273     if (aSpeed > bSpeed)
00274         return true;
00275     else if (aSpeed == bSpeed)
00276         return a->isPeerInterested() > b->isPeerInterested();
00277     else
00278         return false;
00279 }
00280 
00282 
00291 bool Choker::moreUploadedAndMoreInterested (const TransferSession *a,
00292                                             const TransferSession *b)
00293 {
00294     qint64 aSpeed = a->currentUploadSpeed();
00295     qint64 bSpeed = b->currentUploadSpeed();
00296 
00297     if (aSpeed > bSpeed)
00298         return true;
00299     else if (aSpeed == bSpeed)
00300         return a->isPeerInterested() > b->isPeerInterested();
00301     else
00302         return false;
00303 }
00304 
00305 const Choke Choker::choke; // \todo use some kind of factory? the first choke packet will only need to be rebuilt once!
00306 const Unchoke Choker::unchoke; // \todo see above todo