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>
<info-hash>:
Infohash的16进制编码,共40字符。为了与其它的编码兼容,客户端应当也支持32字符的infohash base32编码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);
}
[{
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);
}
static neighbor(target, id) {
return Buffer.concat([target.slice(0, 6), id.slice(6)]);
}