📄 torrentclient.cpp
字号:
}
int TorrentClient::seedCount() const
{
int tmp = 0;
foreach (PeerWireClient *client, d->connections) {
if (client->availablePieces().count(true) == d->pieceCount)
++tmp;
}
return tmp;
}
TorrentClient::State TorrentClient::state() const
{
return d->state;
}
QString TorrentClient::stateString() const
{
return d->stateString;
}
TorrentClient::Error TorrentClient::error() const
{
return d->error;
}
QString TorrentClient::errorString() const
{
return d->errorString;
}
QByteArray TorrentClient::peerId() const
{
return d->peerId;
}
QByteArray TorrentClient::infoHash() const
{
return d->infoHash;
}
void TorrentClient::start()
{
if (d->state != Idle)
return;
TorrentServer::instance()->addClient(this);
// Initialize the file manager
d->setState(Preparing);
d->fileManager.setMetaInfo(d->metaInfo);
d->fileManager.setDestinationFolder(d->destinationFolder);
d->fileManager.setCompletedPieces(d->completedPieces);
d->fileManager.start(QThread::LowestPriority);
d->fileManager.startDataVerification();
}
void TorrentClient::stop()
{
if (d->state == Stopping)
return;
TorrentServer::instance()->removeClient(this);
// Update the state
State oldState = d->state;
d->setState(Stopping);
// Stop the timer
if (d->transferRateTimer) {
killTimer(d->transferRateTimer);
d->transferRateTimer = 0;
}
// Abort all existing connections
foreach (PeerWireClient *client, d->connections)
client->abort();
d->connections.clear();
// Perhaps stop the tracker
if (oldState > Preparing) {
d->trackerClient.stop();
} else {
d->setState(Idle);
emit stopped();
}
}
void TorrentClient::setPaused(bool paused)
{
if (paused) {
// Abort all connections, and set the max number of
// connections to 0. Keep the list of peers, so we can quickly
// resume later.
d->setState(Paused);
foreach (PeerWireClient *client, d->connections)
client->abort();
d->connections.clear();
TorrentServer::instance()->removeClient(this);
} else {
// Restore the max number of connections, and start the peer
// connector. We should also quickly start receiving incoming
// connections.
d->setState(d->completedPieces.count(true) == d->fileManager.pieceCount()
? Seeding : Searching);
connectToPeers();
TorrentServer::instance()->addClient(this);
}
}
void TorrentClient::timerEvent(QTimerEvent *event)
{
if (event->timerId() == d->uploadScheduleTimer) {
// Update the state of who's choked and who's not
scheduleUploads();
return;
}
if (event->timerId() != d->transferRateTimer) {
QObject::timerEvent(event);
return;
}
// Calculate average upload/download rate
qint64 uploadBytesPerSecond = 0;
qint64 downloadBytesPerSecond = 0;
for (int i = 0; i < RateControlWindowLength; ++i) {
uploadBytesPerSecond += d->uploadRate[i];
downloadBytesPerSecond += d->downloadRate[i];
}
uploadBytesPerSecond /= qint64(RateControlWindowLength);
downloadBytesPerSecond /= qint64(RateControlWindowLength);
for (int i = RateControlWindowLength - 2; i >= 0; --i) {
d->uploadRate[i + 1] = d->uploadRate[i];
d->downloadRate[i + 1] = d->downloadRate[i];
}
d->uploadRate[0] = 0;
d->downloadRate[0] = 0;
emit uploadRateUpdated(int(uploadBytesPerSecond));
emit downloadRateUpdated(int(downloadBytesPerSecond));
// Stop the timer if there is no activity.
if (downloadBytesPerSecond == 0 && uploadBytesPerSecond == 0) {
killTimer(d->transferRateTimer);
d->transferRateTimer = 0;
}
}
void TorrentClient::sendToPeer(int readId, int pieceIndex, int begin, const QByteArray &data)
{
// Send the requested block to the peer if the client connection
// still exists; otherwise do nothing. This slot is called by the
// file manager after it has read a block of data.
PeerWireClient *client = d->readIds.value(readId);
if (client) {
if ((client->peerWireState() & PeerWireClient::ChokingPeer) == 0)
client->sendBlock(pieceIndex, begin, data);
}
d->readIds.remove(readId);
}
void TorrentClient::fullVerificationDone()
{
// Update our list of completed and incomplete pieces.
d->completedPieces = d->fileManager.completedPieces();
d->incompletePieces.resize(d->completedPieces.size());
d->pieceCount = d->completedPieces.size();
for (int i = 0; i < d->fileManager.pieceCount(); ++i) {
if (!d->completedPieces.testBit(i))
d->incompletePieces.setBit(i);
}
updateProgress();
// If the checksums show that what the dumped state thought was
// partial was in fact complete, then we trust the checksums.
QMap<int, TorrentPiece *>::Iterator it = d->pendingPieces.begin();
while (it != d->pendingPieces.end()) {
if (d->completedPieces.testBit(it.key()))
it = d->pendingPieces.erase(it);
else
++it;
}
d->uploadScheduleTimer = startTimer(UploadScheduleInterval);
// Start the server
TorrentServer *server = TorrentServer::instance();
if (!server->isListening()) {
// Set up the peer wire server
for (int i = ServerMinPort; i <= ServerMaxPort; ++i) {
if (server->listen(QHostAddress::Any, i))
break;
}
if (!server->isListening()) {
d->setError(ServerError);
return;
}
}
d->setState(d->completedPieces.count(true) == d->pieceCount ? Seeding : Searching);
// Start the tracker client
d->trackerClient.setUploadCount(d->uploadedBytes);
d->trackerClient.setDownloadCount(d->downloadedBytes);
d->trackerClient.start(d->metaInfo);
}
void TorrentClient::pieceVerified(int pieceIndex, bool ok)
{
TorrentPiece *piece = d->pendingPieces.value(pieceIndex);
// Remove this piece from all payloads
QMultiMap<PeerWireClient *, TorrentPiece *>::Iterator it = d->payloads.begin();
while (it != d->payloads.end()) {
if (it.value()->index == pieceIndex)
it = d->payloads.erase(it);
else
++it;
}
if (!ok) {
// If a piece did not pass the SHA1 check, we'll simply clear
// its state, and the scheduler will re-request it
piece->inProgress = false;
piece->completedBlocks.fill(false);
piece->requestedBlocks.fill(false);
d->callScheduler();
return;
}
// Update the peer list so we know who's still interesting.
foreach (TorrentPeer *peer, d->peers) {
if (!peer->interesting)
continue;
bool interesting = false;
for (int i = 0; i < d->pieceCount; ++i) {
if (peer->pieces.testBit(i) && d->incompletePieces.testBit(i)) {
interesting = true;
break;
}
}
peer->interesting = interesting;
}
// Delete the piece and update our structures.
delete piece;
d->pendingPieces.remove(pieceIndex);
d->completedPieces.setBit(pieceIndex);
d->incompletePieces.clearBit(pieceIndex);
// Notify connected peers.
foreach (PeerWireClient *client, d->connections) {
if (client->state() == QAbstractSocket::ConnectedState
&& !client->availablePieces().testBit(pieceIndex)) {
client->sendPieceNotification(pieceIndex);
}
}
// Notify the tracker if we've entered Seeding status; otherwise
// call the scheduler.
int completed = d->completedPieces.count(true);
if (completed == d->pieceCount) {
if (d->state != Seeding) {
d->setState(Seeding);
d->trackerClient.start(d->metaInfo);
}
} else {
if (completed == 1)
d->setState(Downloading);
else if (d->incompletePieces.count(true) < 5 && d->pendingPieces.size() > d->incompletePieces.count(true))
d->setState(Endgame);
d->callScheduler();
}
updateProgress();
}
void TorrentClient::handleFileError()
{
if (d->state == Paused)
return;
setPaused(true);
emit error(FileError);
}
void TorrentClient::connectToPeers()
{
d->connectingToClients = false;
if (d->state == Stopping || d->state == Idle || d->state == Paused)
return;
if (d->state == Searching)
d->setState(Connecting);
// Find the list of peers we are not currently connected to, where
// the more interesting peers are listed more than once.
QList<TorrentPeer *> weighedPeers = weighedFreePeers();
// Start as many connections as we can
while (!weighedPeers.isEmpty() && ConnectionManager::instance()->canAddConnection()
&& (qrand() % (ConnectionManager::instance()->maxConnections() / 2))) {
PeerWireClient *client = new PeerWireClient(ConnectionManager::instance()->clientId(), this);
RateController::instance()->addSocket(client);
ConnectionManager::instance()->addConnection(client);
initializeConnection(client);
d->connections << client;
// Pick a random peer from the list of weighed peers.
TorrentPeer *peer = weighedPeers.takeAt(qrand() % weighedPeers.size());
weighedPeers.removeAll(peer);
peer->connectStart = QDateTime::currentDateTime().toTime_t();
peer->lastVisited = peer->connectStart;
// Connect to the peer.
client->setPeer(peer);
client->connectToHost(peer->address, peer->port);
}
}
QList<TorrentPeer *> TorrentClient::weighedFreePeers() const
{
QList<TorrentPeer *> weighedPeers;
// Generate a list of peers that we want to connect to.
uint now = QDateTime::currentDateTime().toTime_t();
QList<TorrentPeer *> freePeers;
QMap<QString, int> connectionsPerPeer;
foreach (TorrentPeer *peer, d->peers) {
bool busy = false;
foreach (PeerWireClient *client, d->connections) {
if (client->state() == PeerWireClient::ConnectedState
&& client->peerAddress() == peer->address
&& client->peerPort() == peer->port) {
if (++connectionsPerPeer[peer->address.toString()] >= MaxConnectionPerPeer) {
busy = true;
break;
}
}
}
if (!busy && (now - peer->lastVisited) > uint(MinimumTimeBeforeRevisit))
freePeers << peer;
}
// Nothing to connect to
if (freePeers.isEmpty())
return weighedPeers;
// Assign points based on connection speed and pieces available.
QList<QPair<int, TorrentPeer *> > points;
foreach (TorrentPeer *peer, freePeers) {
int tmp = 0;
if (peer->interesting) {
tmp += peer->numCompletedPieces;
if (d->state == Seeding)
tmp = d->pieceCount - tmp;
if (!peer->connectStart) // An unknown peer is as interesting as a seed
tmp += d->pieceCount;
// 1/5 of the total score for each second below 5 it takes to
// connect.
if (peer->connectTime < 5)
tmp += (d->pieceCount / 10) * (5 - peer->connectTime);
}
points << QPair<int, TorrentPeer *>(tmp, peer);
}
qSort(points);
// Minimize the list so the point difference is never more than 1.
typedef QPair<int,TorrentPeer*> PointPair;
QMultiMap<int, TorrentPeer *> pointMap;
int lowestScore = 0;
int lastIndex = 0;
foreach (PointPair point, points) {
if (point.first > lowestScore) {
lowestScore = point.first;
++lastIndex;
}
pointMap.insert(lastIndex, point.second);
⌨️ 快捷键说明
复制代码
Ctrl + C
搜索代码
Ctrl + F
全屏模式
F11
切换主题
Ctrl + Shift + D
显示快捷键
?
增大字号
Ctrl + =
减小字号
Ctrl + -