mirror of
https://github.com/rehe0x/bthello.git
synced 2026-10-16 21:01:25 +08:00
220 lines
6.3 KiB
Python
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()
|