当前位置: 首页 > 工具软件 > DHT > 使用案例 >

【dht】dht简介以及使用nodejs查找dht网络学习笔记

丌官和泰
2023-12-01

官网

概念

  • Kademlia算法
    Kademlia是DHT网络的一种实现。在Kademlia网络中,距离是通过异或(XOR)计算的,结果为无符号整数。distance(A, B) = |A xor B|,值越小表示越近。
    具体算法详细信息可以看此论文。或者去回形针视频里搜下就懂了。

  • KRPC协议
    KRPC 是节点之间的交互协议,是由 bencode 编码组成的一个简单的 RPC 结构,他使用 UDP 报文发送。一个独立的请求包被发出去然后一个独立的包被回复。这个协议没有重发。它包含 3 种消息:请求,回复和错误。对DHT协议而言,这里有 4 种请求:
    ping 检查一个节点是否有效
    find_node 向一个节点发送查找节点的请求,在初始路由表或验证桶是否存活时使用
    get_peers 向一个节点发送查找资源的请求
    announce_peer 向一个节点发送自己已经开始下载某个资源的通知

  • 磁力链接格式:

magnet:?xt=urn:btih:<info-hash>&dn=<name>&tr=<tracker-url>
  1. <info-hash>: Infohash的16进制编码,共40字符。为了与其它的编码兼容,客户端应当也支持32字符的infohash base32编码
  2. Xt是唯一强制的参数
  3. dn是在等待metadata时可能供客户端显示的名字
  4. 如果只有一个字段,Tr是tracker的url,如果有很多的tracker,那么多个tr字段会被包含进去
    dn和tr都是可选的
  • infohash nodejs制作:
const infoHash = crypto.createHash('sha1').update(bencode.decode('file.torrent').info).digest()
  • 传播使用:
const magnet = `magnet:?xt=urn:btih:${infoHash.toString('hex').toUpperCase()}`
  • dht网络格式:
    建议看一下文档

  • peer节点
    一个peer节点是一个实现了bt协议并且开启了tcp监听端口的bt客户端或者服务器

  • node节点
    一个node节点是一个实现了dht协议并且开启了udp监听端口的bt客户端或者服务器

  • dht协议并不能取代bt协议,它只是bt协议的一个强有力的补充,在一些禁止运行bt tracker服务器的国家,通过使用dht协议,用户照样可以下载想要的内容。tracker服务器本来也不保存真正的文件,只是保存和torren文件相关的peer的信息,
    dht协议通过从附近的node节点获取peer信息,而不是从tracker服务器获取peer信息,这就是所谓的trackerless。

  • dht发送请求格式主要为:

findNode(target, nid) {
	const id = nid != undefined ? utils.genNeighborId(nid, this.id) : this.id;
	const msg = {
		t: crypto.randomBytes(2),
		y: "q",
		q: "find_node",
		a: {
			id,
			target: utils.randomId(),
		},
	};
	this.request(msg, target);
}
request(msg, target) {
		const address = target.address;
		const port = target.port;
		const packet = bencode.encode(msg);
		const len = packet.length;
		this.socket.send(packet, 0, len, port, address);
	}
  • id为20为buffer。
  • 根节点一般为:
[{
   address: 'router.utorrent.com',
   	port: 6881
   }, {
   	address: 'router.bittorrent.com',	
   	port: 6881
   }, {
   	address: 'dht.transmissionbt.com',
   	port: 6881
   }]
  • 对于发送、响应、错误,这么定义的:
请求
请求,对应于KPRC消息字典中的“y”关键字的值是“q”,它包含2个附加的关键字“q”和“a”。关键字“q”是一个字符串类型,包含了请求的方法名字。关键字“a”一个字典类型包含了请求所附加的参数。

回复
回复,对应于KPRC消息字典中的“y”关键字的值是“r”,包含了一个附加的关键字r。关键字“r”是一个字典类型,包含了返回的值。发送回复消息是在正确解析了请求消息的基础上完成的。

错误
错误,对应于KPRC消息字典中的y关键字的值是“e”,包含一个附加的关键字e。关键字“e”是一个列表类型。第一个元素是一个数字类型,表明了错误码。第二个元素是一个字符串类型,表明了错误信息。当一个请求不能解析或出错时,错误包将被发送。
  • 对于数据包的响应格式处理:
	onMessage(packet, rinfo) {
		// decode后的msg内容为Buffer
		try {
			var msg = bencode.decode(packet);
		} catch (e) {
			console.error("decode error");
			return;
		}
		if (msg.y) {
			var y = msg.y.toString();
		}
		if (!msg.t) {
			return console.log("t is required!");
		}
		if (!y || y.length !== 1) {
			return console.log("y is required!");
		}
		if (y === "e") {
			return console.log("can not process e!");
		}
		if (y === "q") {
			if (!msg.a) {
				return console.log("a is required!");
			}
			if (!msg.a.id || msg.a.id.length !== 20) {
				return console.log("id is required!");
			}
			if (msg.q) {
				var q = msg.q.toString();
			} else {
				return;
			}
			switch (q) {
				case "ping":
					this.toPing(msg, rinfo);
					break;
				case "find_node":
					this.toFindNode(msg, rinfo);
					break;
				case "get_peers":
					this.toGetPeers(msg, rinfo);
					break;
				case "annouce_peer":
					this.toAnnouncePeer(msg, rinfo);
					break;
				default:
					console.log("q is unknown");
			}
		}
		//处理我方findNode后对方的回复
		if (y === "r") {
			if (msg.r.nodes) {
				var nodes = utils.decodeNodes(msg.r.nodes);
			} else {
				return;
			}
			const len = nodes.length;
			if (len !== 0) {
				for (let i = 0; i < len; i++) {
					//将node加入路由表
					let node = nodes[i];
					if (node.port < 1 || node.port > 65535) {
						console.log("port is invalid");
						continue;
					}
					this.nodesList.push({
						nid: node.nid,
						address: node.address,
						port: node.port,
					});
				}
			}
		}
	}
  • 对于发送响应的处理:
	//响应ping查询
	toPing(msg, rinfo) {
		const r = {
			id: this.id,
		};
		this.response(r, msg.t, rinfo);
	}
	//响应findNode查询
	toFindNode(msg, rinfo) {
		const r = {
			id: this.id,
			nodes: "",
		};
		this.response(r, msg.t, rinfo);
	}
	//响应getPeer查询
	toGetPeers(msg, rinfo) {
		if (msg.a && msg.a.info_hash && msg.a.info_hash.length === 20) {
			var infohash = msg.a.info_hash;
			model.saveInfoHash(infohash.toString("hex"));
		} else {
			return;
		}
		const r = {
			id: utils.genNeighborId(infohash, this.id),
			token: crypto.randomBytes(4),
			nodes: "",
		};
		this.response(r, msg.t, rinfo);
	}

	//响应annoucePeer查询
	toAnnouncePeer(msg, rinfo) {
		if (msg.a && msg.a.info_hash && msg.a.info_hash.length === 20) {
			model.saveInfoHash(msg.a.info_hash.toString("hex"));
		} else {
			return;
		}
		const r = {
			id: this.id,
		};
		this.response(r, msg.t, rinfo);
	}
  • 其中,getpeers可以拿到完全未验证infohash,这个是别人所提供的信息。
  • 响应announcePeer的为资源信息,也就是别人已在下载中的资源信息,通过记录可以获取infohash,需要通过第三方拿到meta信息之后去验证那个节点是否正常。
  • 生成邻近节点,普遍是保持高位一致,使用slice进行生成:
static neighbor(target, id) {
		return Buffer.concat([target.slice(0, 6), id.slice(6)]);
	}
 类似资料: