Files
2019-04-14 10:48:38 +08:00

220 lines
6.3 KiB
Python

#!/usr/bin/env python3
# encoding: utf-8
import socket
from multiprocessing import Process
from threading import Thread
from concurrent.futures import ThreadPoolExecutor
from time import sleep
from collections import deque
from common.bencode import bencode, bdecode
from common.utils import *
from config import Config
from common.metadata import download_metadata
from common.database import RedisClient
logger = get_logger("logger_dht_main")
class KNode(object):
def __init__(self, nid, ip, port):
self.nid = nid
self.ip = ip
self.port = port
class DHT(Thread):
def __init__(self,bind_ip,bind_port):
Thread.__init__(self)
self.ufd = socket.socket(socket.AF_INET, socket.SOCK_DGRAM, socket.IPPROTO_UDP)
self.ufd.bind((bind_ip, bind_port))
def send_krpc(self, msg, address):
# noinspection PyBroadException
try:
self.ufd.sendto(bencode(msg), address)
except Exception:
pass
class DHTClient(DHT):
def __init__(self,bind_ip,bind_port):
DHT.__init__(self,bind_ip,bind_port)
self.bind_ip = bind_ip
self.bind_port = bind_port
self.setDaemon(True)
self.nid = random_id()
self.nodes = deque(maxlen=Config.MAX_NODE_SIZE)
def send_find_node(self, address, nid=None):
nid = get_neighbor(nid, self.nid) if nid else self.nid
tid = entropy(2) # TransactionID长度为2
msg = {
't': tid,
'y': 'q',
'q': 'find_node',
'a': {
'id': nid,
'target': random_id()
}
}
self.send_krpc(msg, address)
def join_dht(self):
for address in Config.BOOTSTRAP_NODES:
self.send_find_node(address)
def rejoin_dht(self):
if len(self.nodes) == 0:
self.join_dht()
timer(Config.REJOIN_DHT_INTERVAL, self.rejoin_dht)
def auto_send_find_node(self):
while True:
try:
node = self.nodes.popleft()
self.send_find_node((node.ip, node.port), node.nid)
except IndexError:
pass
sleep(1.0 / Config.MAX_NODE_SIZE)
def process_find_node_response(self, msg):
nodes = decode_nodes(msg['r']['nodes'])
for node in nodes:
(nid, ip, port) = node
if len(nid) != 20:
continue
if ip == self.bind_ip:
continue
n = KNode(nid, ip, port)
self.nodes.append(n)
class DHTServer(DHTClient):
def __init__(self,bind_ip, bind_port, process_id):
DHTClient.__init__(self,bind_ip,bind_port)
self.bind_ip = bind_ip
self.bind_port = bind_port
self.process_id = process_id
self.pool = ThreadPoolExecutor(Config.DOWNLOAD_THREAD)
self.process_request_actions = {
'get_peers': self.on_get_peers_request,
'announce_peer': self.on_announce_peer_request,
}
self.ufd = socket.socket(socket.AF_INET, socket.SOCK_DGRAM, socket.IPPROTO_UDP)
self.ufd.bind((self.bind_ip, self.bind_port))
timer(Config.REJOIN_DHT_INTERVAL, self.rejoin_dht)
def run(self):
self.rejoin_dht()
while True:
# noinspection PyBroadException
try:
(data, address) = self.ufd.recvfrom(65536)
msg = bdecode(data)
self.on_message(msg, address)
except Exception:
pass
def on_message(self, msg, address):
try:
if msg['y'] == 'r':
if 'nodes' in msg['r']:
self.process_find_node_response(msg)
elif msg['y'] == 'q':
try:
self.process_request_actions[msg['q']](msg, address)
except KeyError:
self.play_dead(msg, address)
except KeyError:
pass
def on_get_peers_request(self, msg, address):
try:
h = msg['a']['info_hash']
tid = msg['t']
token = h[:2]
msg = {
't': tid,
'y': 'r',
'r': {
'id': get_neighbor(h, self.nid),
'nodes': '',
'token': token
}
}
self.send_krpc(msg, address)
except KeyError:
pass
def on_announce_peer_request(self, msg, address):
# noinspection PyBroadException
try:
h = msg['a']['info_hash']
token = msg['a']['token']
if h[:2] == token:
if 'implied_port ' in msg['a'] and msg['a']['implied_port '] != 0:
port = address[1]
else:
port = msg['a']['port']
self.pool.submit(download_metadata, (address[0], port), h, self.process_id)
except Exception:
return
finally:
self.ok(msg, address)
def play_dead(self, msg, address):
try:
tid = msg['t']
msg = {
't': tid,
'y': 'e',
'e': [202, 'Server Error']
}
self.send_krpc(msg, address)
except KeyError:
pass
def ok(self, msg, address):
try:
tid = msg['t']
nid = msg['a']['id']
msg = {
't': tid,
'y': 'r',
'r': {
'id': get_neighbor(nid, self.nid)
}
}
self.send_krpc(msg, address)
except KeyError:
pass
def _start_thread(offset):
"""
启动线程
:param offset: 端口偏移值
"""
logger.info("dht running successful ! {0} ->>>> {1}:{2}".format(offset,Config.BIND_IP,Config.BIND_PORT + offset))
dht = DHTServer(Config.BIND_IP, Config.BIND_PORT + offset, offset)
dht.start()
dht.auto_send_find_node()
# t = Thread(target=dht.auto_send_find_node)
# t.start()
# t.join()
# dht.join()
def start_server():
"""
多进程启动服务
"""
processes = []
for i in range(Config.MAX_PROCESSES):
processes.append(Process(target=_start_thread, args=(i,)))
for p in processes:
p.start()
for p in processes:
p.join()