/** * Manages handshaking with the client. * * If seeders_stop_seeding config key is set to a number greater than 0, * we check if we have at least N seeders beyond ourselves for the requested * torrent and if so, stop seeding (to spare bandwith). * * @throws PHPTracker_Seeder_Error_CloseConnection In case when the reqeust is invalid or we don't want or cannot serve the requested torrent. * @param PHPTracker_Seeder_Client $client */ protected function shakeHand(PHPTracker_Seeder_Client $client) { $protocol_length = unpack('C', $client->socketRead(1)); $protocol_length = current($protocol_length); if (($protocol = $client->socketRead($protocol_length)) !== self::PROTOCOL_STRING) { $this->logger->logError("Client tries to connect with unsupported protocol: " . substr($protocol, 0, 100) . ". Closing connection."); throw new PHPTracker_Seeder_Error_CloseConnection('Unsupported protocol.'); } // 8 reserved void bytes. $client->socketRead(8); $info_hash = $client->socketRead(20); $client->peer_id = $client->socketRead(20); $info_hash_readable = unpack('H*', $info_hash); $info_hash_readable = reset($info_hash_readable); $torrent = $this->persistence->getTorrent($info_hash); if (!isset($torrent)) { throw new PHPTracker_Seeder_Error_CloseConnection('Unknown info hash.'); } $client->torrent = $torrent; // If we have X other seeders already, we stop seeding on our own. if (0 < ($seeders_stop_seeding = $this->config->get('seeders_stop_seeding', false, 0))) { $stats = $this->persistence->getPeerStats($info_hash, $this->peer_id); if ($stats['complete'] >= $seeders_stop_seeding) { $this->logger->logMessage("External seeder limit ({$seeders_stop_seeding}) reached for info hash {$info_hash_readable}, stopping seeding."); throw new PHPTracker_Seeder_Error_CloseConnection('Stop seeding, we have others to seed.'); } } // Our handshake signal. $client->socketWrite(pack('C', strlen(self::PROTOCOL_STRING)) . self::PROTOCOL_STRING . pack('a8', '') . $info_hash . pack('a20', $this->peer_id)); $this->logger->logMessage("Handshake completed with peer {$client->peer_id} with address {$client->address}:{$client->port}, info hash: {$info_hash_readable}."); }
/** * Save announce for all the torrents in the database so clients know where to connect. * * This method runs in infinite loop repeating announcing every self::ANNOUNCE_INTERVAL seconds. */ protected function announce() { $persistence = $this->config->get('persistence'); $iterations = 0; do { $all_torrents = $persistence->getAllInfoHash(); foreach ($all_torrents as $torrent_info) { $persistence->saveAnnounce($torrent_info['info_hash'], $this->peer->peer_id, $this->peer->external_address, $this->peer->port, $torrent_info['length'], 0, 0, 'complete', self::ANNOUNCE_INTERVAL); } $this->logger->logMessage('Seeder server announced itself for ' . count($all_torrents) . " torrents at address {$this->peer->external_address}:{$this->peer->port} (announces every " . self::ANNOUNCE_INTERVAL . 's).'); sleep(self::ANNOUNCE_INTERVAL); } while (++$iterations < self::STOP_AFTER_ITERATIONS); // Memory leak prevention, see self::STOP_AFTER_ITERATIONS. $this->logger->logMessage('Announce process restarts to prevent memory leaks.'); exit(0); }