class DHT(object): def __init__(self, node, bootstrap=None, context=None): self.data = {} self.bootstrap = bootstrap self.node = node self.routing_table = RoutingTable(node, bootstrap) self.context = context if context != None else zmq.Context() self._thread_uid = None self._shutdown_flag = threading.Event() def shutdown(self): self._shutdown_flag.set() self._thread_uid.join() def get_value_handler(self, data_key, key, sock): try: sock.send(json.dumps({ 'value': self.data[data_key] })) except KeyError: print "%s: GET_VALUE for %s failed. Trying nearest" % (self.node, data_key) self.find_node_handler(key, sock) def store_value_handler(self, key, value, sock): self.data[key] = value print "Stored '%s' on %s" % (key, self.node) sock.send(json.dumps({ 'result': 'OK' })) def find_node_handler(self, key, sock): response = [] for _, node in self.routing_table.find_closest(Node(key)): response.append((tuple(node.data), node.host, node.port)) sock.send(json.dumps({ 'nodes': response })) def send_message_to_node(self, node, message_type, message_value=None): socket = self.context.socket(zmq.REQ) socket.connect("tcp://%s:%s" % (node.host, node.port)) socket.send(json.dumps({ 'from': { 'uid': tuple(self.node.data), 'host': self.node.host, 'port': self.node.port, }, 'type': message_type, 'value': message_value })) poller = zmq.Poller() poller.register(socket, zmq.POLLIN) if len(poller.poll(1000)) == 0: raise SocketTimedOutError() return json.loads(socket.recv()) def iterative_find_node(self, key, data_key=None): command = 'GET_VALUE' if data_key else 'FIND_NODE' # Build a set of seen items and a shortlist that # will be incremented seen = set() timed_out = set() shortlist = self.routing_table.find_closest(key) best_score_so_far = float('inf') while len(shortlist) > 0 and len(shortlist) <= 20: # For each iteration, pick alpha contacts: a set # of K contacts that have not been seen alpha_contacts = [ (score, node) for score, node in sorted(shortlist) if node not in seen ][:20] if len(alpha_contacts) == 0: break # Ensure that we are always improving our score over time best_score_currently = alpha_contacts[0][0] if best_score_currently >= best_score_so_far: break best_score_so_far = best_score_currently for _, node in alpha_contacts: seen.add(node) new_nodes = [] try: if data_key: result = self.send_message_to_node(node, 'GET_VALUE', { 'data_key': data_key, 'key': tuple(key.data) }) if 'value' in result: return result['value'], [] new_nodes = result['nodes'] else: new_nodes = self.send_message_to_node(node, 'FIND_NODE', tuple(key.data))['nodes'] except SocketTimedOutError: print "%s timed out. Removed from shortlist" % node timed_out.add(node) self.routing_table.mark_as_unavailable(node) for data, host, port in new_nodes: new_node = Node(bytearray(data), host, port) if new_node == self.node: continue # Update routing table and shortlist. self.routing_table.update(new_node) shortlist.append(((key ^ new_node).distance_key(), new_node)) result = [ node for _, node in sorted(shortlist) if node not in timed_out ][:20] return None, result def run(self): self._thread_uid = threading.Thread(target=self._run) self._thread_uid.daemon = True self._thread_uid.start() if self.bootstrap: self.iterative_find_node(self.node) return self._thread_uid def __setitem__(self, key, value): hashed_key = distributed_hash(key) search_node = Node(hashed_key) result = self.routing_table.find_closest(search_node) if len(result) == 0: self.data[key] = value else: for _, node in result: self.send_message_to_node(node, 'STORE_VALUE', { 'key': key, 'value': value }) def __getitem__(self, key): hashed_key = distributed_hash(key) try: return self.data[tuple(hashed_key)] except KeyError: search_node = Node(hashed_key) found, _ = self.iterative_find_node(search_node, key) if found: return found raise KeyError() def _run(self): socket = self.context.socket(zmq.REP) socket.bind("tcp://*:%s" % self.node.port) poller = zmq.Poller() poller.register(socket, zmq.POLLIN) while not self._shutdown_flag.is_set(): found = poller.poll(2000) if len(found) == 0: nodes_known = [len(b) for b in self.routing_table.buckets] #print "%s knows %s peers" % (self.node, sum(nodes_known)) continue message = json.loads(socket.recv()) # Update routing table if message is from a new # sender req_node = Node( bytearray(message['from']['uid']), message['from']['host'], message['from']['port'] ) #print "Received from %s" % req_node self.routing_table.update(req_node) if message['type'] == 'FIND_NODE': self.find_node_handler(message['value'], socket) elif message['type'] == 'STORE_VALUE': self.store_value_handler( message['value']['key'], message['value']['value'], socket ) elif message['type'] == 'GET_VALUE': self.get_value_handler( message['value']['data_key'], message['value']['key'], socket )