bittorrent-dht模块
- BitTorrent DHT 通过 DHT 网络广播值,允许其他用户通过 DHT 来发现和获取这些数据。
1. 导入依赖
var DHT = require('bittorrent-dht')
2. 创建实例
var dht = new DHT({
bootstrap: config.dht.bootstrap
})
dht.listen(config.dht.listen)
-
new DHT({...})
:创建一个新的 DHT 实例,bootstrap
参数用于指定引导节点的地址,允许客户端加入 DHT 网络。 -
dht.listen(...)
:在指定的端口上开始监听来自其他 DHT 节点的请求。 -
处理 DHT 准备就绪事件
dht.on('ready', function () {
// 处理
})
- 示例代码
var DHT = require("bittorrent-dht");
var dht = new DHT();
var dht = new DHT();
var value = new Buffer(200).fill("abc");
dht.on("ready", function () {
dht.put({
v: value }, function (errors, hash) {
console.error("errors=", errors);
console.log("hash=", hash);
});
});
setTimeout(function () {
var arr = dht.toArray(); // 获取DHT节点数组
console.log("DHT节点数组:", arr); // 打印数组到控制台
dht.destroy();
}, 100000);
- 输出:
➜ workspace git:(master) ✗ node index.js
(node:17667) [DEP0005] DeprecationWarning: Buffer() is deprecated due to security and usability issues. Please use the Buffer.alloc(), Buffer.allocUnsafe(), or Buffer.from() methods instead.
(Use `node --trace-deprecation ...` to show where the warning was created)
errors= [
Error: 203 missing 'token' key
at DHT._onResponseOrError (/project/workspace/node_modules/bittorrent-dht/client.js:1077:11)
at DHT._onData (/project/workspace/node_modules/bittorrent-dht/client.js:1037:10)
at Socket.emit (node:events:518:28)
at UDP.onMessage [as onmessage] (node:dgram:942:8) {
address: '95.72.172.196:6881'
},
Error: 203 missing 'token' key
at DHT._onResponseOrError (/project/workspace/node_modules/bittorrent-dht/client.js:1077:11)
at DHT._onData (/project/workspace/node_modules/bittorrent-dht/client.js:1037:10)
at Socket.emit (node:events:518:28)
at UDP.onMessage [as onmessage] (node:dgram:942:8) {
address: '5.49.12.204:40333'
},
Error: 203 missing 'token' key
at DHT._onResponseOrError (/project/workspace/node_modules/bittorrent-dht/client.js:1077:11)
at DHT._onData (/project/workspace/node_modules/bittorrent-dht/client.js:1037:10)
at Socket.emit (node:events:518:28)
at UDP.onMessage [as onmessage] (node:dgram:942:8) {
address: '95.29.240.208:6881'
},
Error: 203 missing 'token' key
at DHT._onResponseOrError (/project/workspace/node_modules/bittorrent-dht/client.js:1077:11)
at DHT._onData (/project/workspace/node_modules/bittorrent-dht/client.js:1037:10)
at Socket.emit (node:events:518:28)
at UDP.onMessage [as onmessage] (node:dgram:942:8) {
address: '109.175.109.86:8893'
},
Error: 202 Server Error
at DHT._onResponseOrError (/project/workspace/node_modules/bittorrent-dht/client.js:1077:11)
at DHT._onData (/project/workspace/node_modules/bittorrent-dht/client.js:1037:10)
at Socket.emit (node:events:518:28)
at UDP.onMessage [as onmessage] (node:dgram:942:8) {
address: '146.19.24.245:23544'
},
Error: 203 missing 'token' key
at DHT._onResponseOrError (/project/workspace/node_modules/bittorrent-dht/client.js:1077:11)
at DHT._onData (/project/workspace/node_modules/bittorrent-dht/client.js:1037:10)
at Socket.emit (node:events:518:28)
at UDP.onMessage [as onmessage] (node:dgram:942:8) {
address: '81.101.23.157:56418'
},
Error: 203 missing 'token' key
at DHT._onResponseOrError (/project/workspace/node_modules/bittorrent-dht/client.js:1077:11)
at DHT._onData (/project/workspace/node_modules/bittorrent-dht/client.js:1037:10)
at Socket.emit (node:events:518:28)
at UDP.onMessage [as onmessage] (node:dgram:942:8) {
address: '159.146.86.15:42164'
},
Error: 203 missing 'token' key
at DHT._onResponseOrError (/project/workspace/node_modules/bittorrent-dht/client.js:1077:11)
at DHT._onData (/project/workspace/node_modules/bittorrent-dht/client.js:1037:10)
at Socket.emit (node:events:518:28)
at UDP.onMessage [as onmessage] (node:dgram:942:8) {
address: '195.8.44.196:20515'
},
Error: 203 missing 'token' key
at DHT._onResponseOrError (/project/workspace/node_modules/bittorrent-dht/client.js:1077:11)
at DHT._onData (/project/workspace/node_modules/bittorrent-dht/client.js:1037:10)
at Socket.emit (node:events:518:28)
at UDP.onMessage [as onmessage] (node:dgram:942:8) {
address: '5.79.173.86:26358'
},
Error: 202 Server Error
at DHT._onResponseOrError (/project/workspace/node_modules/bittorrent-dht/client.js:1077:11)
at DHT._onData (/project/workspace/node_modules/bittorrent-dht/client.js:1037:10)
at Socket.emit (node:events:518:28)
at UDP.onMessage [as onmessage] (node:dgram:942:8) {
address: '13.58.27.33:6881'
},
Error: 202 Server Error
at DHT._onResponseOrError (/project/workspace/node_modules/bittorrent-dht/client.js:1077:11)
at DHT._onData (/project/workspace/node_modules/bittorrent-dht/client.js:1037:10)
at Socket.emit (node:events:518:28)
at UDP.onMessage [as onmessage] (node:dgram:942:8) {
address: '18.223.137.220:6881'
},
Error: 203 missing 'token' key
at DHT._onResponseOrError (/project/workspace/node_modules/bittorrent-dht/client.js:1077:11)
at DHT._onData (/project/workspace/node_modules/bittorrent-dht/client.js:1037:10)
at Socket.emit (node:events:518:28)
at UDP.onMessage [as onmessage] (node:dgram:942:8) {
address: '103.15.254.90:18384'
},
Error: 203 missing 'token' key
at DHT._onResponseOrError (/project/workspace/node_modules/bittorrent-dht/client.js:1077:11)
at DHT._onData (/project/workspace/node_modules/bittorrent-dht/client.js:1037:10)
at Socket.emit (node:events:518:28)
at UDP.onMessage [as onmessage] (node:dgram:942:8) {
address: '189.147.128.165:6898'
},
Error: 203 missing 'token' key
at DHT._onResponseOrError (/project/workspace/node_modules/bittorrent-dht/client.js:1077:11)
at DHT._onData (/project/workspace/node_modules/bittorrent-dht/client.js:1037:10)
at Socket.emit (node:events:518:28)
at UDP.onMessage [as onmessage] (node:dgram:942:8) {
address: '49.187.241.178:6881'
},
Error: 203 missing 'token' key
at DHT._onResponseOrError (/project/workspace/node_modules/bittorrent-dht/client.js:1077:11)
at DHT._onData (/project/workspace/node_modules/bittorrent-dht/client.js:1037:10)
at Socket.emit (node:events:518:28)
at UDP.onMessage [as onmessage] (node:dgram:942:8) {
address: '195.211.192.65:43468'
},
Error: query timed out
at Timeout.onTimeout [as _onTimeout] (/project/workspace/node_modules/bittorrent-dht/client.js:1445:8)
at listOnTimeout (node:internal/timers:573:17)
at process.processTimers (node:internal/timers:514:7) {
address: '146.59.3.81:10240'
},
Error: query timed out
at Timeout.onTimeout [as _onTimeout] (/project/workspace/node_modules/bittorrent-dht/client.js:1445:8)
at listOnTimeout (node:internal/timers:573:17)
at process.processTimers (node:internal/timers:514:7) {
address: '195.8.44.196:41850'
},
Error: query timed out
at Timeout.onTimeout [as _onTimeout] (/project/workspace/node_modules/bittorrent-dht/client.js:1445:8)
at listOnTimeout (node:internal/timers:573:17)
at process.processTimers (node:internal/timers:514:7) {
address: '90.154.72.124:16183'
},
Error: query timed out
at Timeout.onTimeout [as _onTimeout] (/project/workspace/node_modules/bittorrent-dht/client.js:1445:8)
at listOnTimeout (node:internal/timers:573:17)
at process.processTimers (node:internal/timers:514:7) {
address: '193.233.120.141:16199'
}
]
hash= <Buffer ba 16 0d 84 85 59 c6 05 52 b7 83 f2 3c d3 55 5b 2a 32 c7 b8>
DHT节点数组: [
{
id: '6b5611b3bfb7b8c8372c069a1e22e540609bda5a',
addr: '87.98.162.88:6881'
},
{
id: 'a811480f80e15cc2da5de2efb637b55db52124a1',
addr: '178.32.217.211:6881'
},
{
id: 'af15df9751f12d69492050218fbad8ec1245bce3',
addr: '37.48.111.3:28010'
},
{
id: '89f562f9f211a457acfe33b20f629ccac543ab72',
addr: '51.38.34.56:51413'
},
{
id: 'a995297613a597c36b910494795510f888a39263',
addr: '89.149.202.214:21147'
},
{
id: '9b334e8f333ef9fa3f2a52934ec1fd771535e68e',
addr: '178.162.174.223:28009'
},
{
id: 'a555d8b451608de54ccad3db9a7349ec5096e407',
addr: '178.162.173.132:28015'
},
{
id: 'a5d1090886585bfbec069777aed63730feb71483',
addr: '188.34.190.92:6881'
},
{
id: '85f1caa50b72c07a86a9501968002b7d59501a95',
addr: '46.242.12.117:13066'
},
{
id: '997e40df66b6fb64577807c6c30802851843520b',
addr: '178.162.174.237:28001'
},
{
id: 'ae5a71d29d9b373b84ff5d397db31135caea6af8',
addr: '146.71.50.196:4000'
},
{
id: 'a3a090845890ffa9883b2f65e4cdd319c536b0af',
addr: '47.54.23.223:51413'
},
{
id: '9aa0c85e94e179e4692e3d86e707d5899062bed8',
addr: '178.162.173.166:28015'
},
{
id: '9f53fe555940af320f9c7540445589cc84369884',
addr: '23.158.56.119:10095'
},
{
id: '84fe47cc3c76ba793bc0f7f1ae6996431161ba60',
addr: '88.201.206.119:3334'
},
{
id: 'a3573c0b6206eaa41f2ce9d29dc9de5fef2865d1',
addr: '81.207.238.6:1024'
},
{
id: '9191c3ea0cf55e663d18fc552df2a580395f7a80',
addr: '82.65.220.127:6885'
},
{
id: 'b2498f2a643a78207bcb3dce4307f8e0a615297e',
addr: '24.20.153.108:6969'
},
{
id: 'b5a304f2843ba084416908fe7e7002fe9c5478ad',
addr: '14.48.222.76:7801'
},
{
id: 'a449d2b1d34c39c9ee4d917b24ce22af0db22248',
addr: '5.129.239.125:49001'
},
{
id: '9900258a640be2f0cd0c35a961a386c1cc9cf589',
addr: '103.140.3.17:15195'
},
{
id: 'e3d5f4b4aed69802dbb34b24f6fd358063dd1cf4',
addr: '14.104.200.224:10920'
},
{
id: 'e0f0da00d1bee565e0a21088787bdcbb4a2633e7',
addr: '125.77.176.152:37740'
},
{
id: 'e0dd29c1e17a46839ab2e54c32675e5463f9e03b',
addr: '113.0.38.69:32981'
},
{
id: 'e36ff0fa563342fd2fc905576a784e4277b9ee59',
addr: '124.115.94.28:64425'
},
{
id: 'e13b76f9a31ebe5a6d29b765b293143d461ca426',
addr: '122.192.133.176:6887'
},
{
id: 'e29a50d52c1c9029a9d3a7f9fe152e5803ae3344',
addr: '36.4.10.196:38999'
},
{
id: 'e5c14f6d2ee91906c9265afaab45482594f7467e',
addr: '221.157.193.11:6881'
},
{
id: 'e46507c044a8265a16360dc481a3c631dc79aff1',
addr: '61.147.218.204:56016'
},
{
id: 'e66a2f0d5056c00ba4425bab7ca0cfab4df7ddcf',
addr: '120.40.56.40:22840'
},
{
id: 'e632ccac3c4bc229581ea4d2039626cb12881e1c',
addr: '218.91.170.101:6891'
},
{
id: 'e6b14eb0462c8ac5e7553ec6b6003a65075c363e',
addr: '39.112.211.158:40954'
},
{
id: 'e71f1918956034be0a634b9cdbbec5665d2ac654',
addr: '183.134.38.41:6890'
},
{
id: 'e73c15b0beffd4373c145e00f07d4a450408dd52',
addr: '61.173.61.203:59951'
},
{
id: 'e711e8dda372363d608afa0a050d3fbdec8425db',
addr: '185.149.91.43:51079'
},
{
id: 'e71361d74d4799186ac0d62bccecbfe66c63b2cd',
addr: '169.150.223.225:6881'
},
{
id: 'e706b08a6b939e8d5e37ea2e4f9418c97cc1d66d',
addr: '94.75.194.118:28006'
},
{
id: 'e703e8c3a3c766f1ed6a365998f05ba84d81dd83',
addr: '77.82.189.152:49001'
},
{
id: 'e72eb05590c079a0acb407fdbb5204bc9b95d53b',
addr: '49.245.2.242:11706'
},
{
id: 'e725c22b82a5adebf493bd66ac859562b4f2b3ee',
addr: '14.103.76.31:60020'
},
{
id: 'e71f2ba6bec45aff6d35b3356e1ed3df4d5f15bb',
addr: '121.236.15.238:9888'
},
{
id: 'e751aa9bd98fbfc9b3aaa026443ebd8c429f5fcc',
addr: '195.154.171.138:5250'
},
{
id: 'e74e28a755e0b680d4197753c31e9ad081d8b8b4',
addr: '184.64.199.181:48670'
},
{
id: 'e74ca9d20211352647212795b0c4e761c067d874',
addr: '220.200.42.0:62559'
},
{
id: 'e74bbc1b073c79c49051b88c502986c8c21dd36f',
addr: '81.40.181.46:8621'
},
{
id: 'e7507c39a1cb087aa7ccc8f3adc3d04203d37f31',
addr: '185.203.56.10:60696'
},
{
id: 'e743d8ec35a30db1bb12137f67eae6e39b013508',
addr: '183.195.21.211:13284'
},
{
id: 'e74f8c5f47304ac841de681ec4b4cfbc134cbd7f',
addr: '112.24.228.151:27868'
},
{
id: 'e7503d0d29de824e4caae6017a4e6419d1a43082',
addr: '81.202.75.215:6889'
},
{
id: 'e753d24bdf60c8128dd48d15431093c20c34693d',
addr: '209.193.67.78:51413'
},
{
id: 'e755e97111a41814b466e4b7a5526a06f2cc534c',
addr: '186.57.159.109:6881'
},
{
id: 'e761c3d8d84b09395c0a2affd38e24c0c9418b4c',
addr: '42.193.175.166:18826'
},
{
id: 'e760ac2c0424839b85bf02179b32866ab297689b',
addr: '88.0.223.106:51413'
},
{
id: 'e767d67550eb627914a8d20b78728c9d59d95fc5',
addr: '195.230.109.6:43242'
},
{
id: 'e760a6ddaf17ee5725d15c21d9ed8186085ff04c',
addr: '59.5.76.86:41202'
},
{
id: 'e76579760dc1ad7e3d992d2bef992d6ea156bd89',
addr: '131.153.18.45:22574'
},
{
id: 'e762cffedfef8b697f64e88452b548e4ca052d0f',
addr: '217.225.89.57:6881'
},
{
id: 'e763d907a08cf6fcb07eecc265d595632377624c',
addr: '107.181.234.142:51413'
},
{
id: 'e766eb1feb72f2cb87aa747086e3faef8764f06e',
addr: '219.251.137.25:40972'
},
{
id: 'e760b549f1f1bbe9ebb3a6db3c870c3e99245e52',
addr: '59.6.5.153:64536'
},
{
id: 'e760afea3b0f54c53a75070241592c5dc6fd0c59',
addr: '27.125.248.23:15630'
},
{
id: 'e767644b57a9dbe2029ead2d395633301bfff07e',
addr: '59.3.119.55:40804'
},
{
id: 'e763054ba30fa0e3e767721c6a911ce9e7ac50fe',
addr: '60.163.61.30:17077'
},
{
id: 'e76595f27f020daa5d106a3d4e25bd24b1564b6a',
addr: '179.131.236.32:50321'
},
{
id: 'e7672ee9801a6f85413c6314f6305f7b94f93e37',
addr: '121.128.111.23:40811'
},
{
id: 'e760245fb585019447ba47e497a94d662e6349c8',
addr: '59.188.57.119:6889'
},
{
id: 'e7620cc83f2240a3b3d69ef294eece88c729f902',
addr: '72.21.17.91:61318'
},
{
id: 'e761f830ff2f48422470dabafe53ce7d08fb3cd2',
addr: '85.140.162.247:55414'
},
{
id: 'e7672049fe578e109d980c8eb64057cdb3454c02',
addr: '207.65.155.118:51413'
},
{
id: 'e76793864960d52d457ef2ebe2fcb40d02f57ca9',
addr: '157.157.193.103:5030'
},
{
id: 'e769bdb7be648414ae3cafb158fb4c18fa94826a',
addr: '72.21.17.3:12743'
},
{
id: 'e76a5736ae4740254fc6c57c04afe7bcd093c5fc',
addr: '222.113.206.3:32996'
},
{
id: 'e769bd26374a11ebcd96ac0021ea2bbe5aec00f2',
addr: '72.21.17.3:57563'
},
{
id: 'e76b41a618e160c3a5c837d5ecc70648305e58a4',
addr: '2.98.139.134:19856'
},
{
id: 'e768b641d52f2dfe3bf304d3ee8a8ce4dded3bf8',
addr: '119.197.185.173:60761'
},
{
id: 'e76bb57181f76165df4289688ea4e6730f3cf7ec',
addr: '188.21.206.210:29115'
},
{
id: 'e76b7ad6ae529049f1f1bbe9ebb3a6db3c870ce1',
addr: '84.9.37.4:35609'
},
{
id: 'e76bc6b2e88753a773e28c5c73ce4dd13178d9c6',
addr: '111.196.228.198:5774'
},
{
id: 'e769bf4d3366684d43e979222cd64fdb976a0142',
addr: '72.21.17.3:62436'
},
{
id: 'e7683854beae9b503af99d95f65dc8626d274b94',
addr: '178.162.174.237:28007'
},
{
id: 'e7686b42c80418f1dbaac7420de4e7b865eed412',
addr: '89.105.15.4:16345'
},
{
id: 'e769beafcbe43b71166602d4f70a330402c7b07a',
addr: '72.21.17.3:20057'
},
{
id: 'e7680d81a626489edee91255d9a08264a78cb8f7',
addr: '170.150.1.175:26340'
},
{
id: 'e76c07b8216f99cb4eee48795bc504be5e85ad92',
addr: '178.221.133.198:46873'
},
{
id: 'e76c04cffe2e06e0aa57a0edec0062b8f42c5803',
addr: '49.117.14.143:8392'
},
{
id: 'e76c0d1dc74193c186b35361733df3bb591d16ef',
addr: '46.197.49.135:26616'
},
{
id: 'e76c00c84cc41bd997f5c942055eed780a74189e',
addr: '185.251.151.248:51413'
},
{
id: 'e76c047b5dc0aaad6529b9e9487159c2c74fed01',
addr: '223.247.106.145:55291'
},
{
id: 'e76c00169995fb98f8c90f59817b3c989b5dfb3d',
addr: '82.132.246.194:54677'
},
{
id: 'e76c074d30740c88fcdac1f93ba8faa46fa572a5',
addr: '122.148.246.194:30754'
},
{
id: 'e76c0f976890ad222b72782990615e613e03d7e7',
addr: '212.35.183.80:3168'
},
{
id: 'e76c06ba160fdc583ed1969d1db70fae30a78541',
addr: '90.15.84.33:40622'
},
{
id: 'e76c06ba160fdc583ed1969d1db70f8dc9547307',
addr: '49.12.86.202:6883'
},
{
id: 'e76c06ba160fdc583ed152fbdd8d2da45f042476',
addr: '146.19.24.245:23544'
},
{
id: 'e76c0993a20cef635b7fad5e4dc95a8bcae8e107',
addr: '132.147.119.80:1041'
},
{
id: 'e76c0df644d2255ca4a4c8c8ee3029ecb2fd2d1b',
addr: '178.234.59.239:6881'
},
{
id: 'e76c1edc0b17287dbee23dbdc40875faab321fff',
addr: '93.156.208.226:48379'
},
{
id: 'e76c1fd6f2a275af36e9955fbcce091188938046',
addr: '200.121.141.155:31719'
},
{
id: 'e76c108b98fc86ac2e0d75ed5ff5c03e163dd9eb',
addr: '82.7.191.13:36957'
},
{
id: 'e76c1bde86a78072e5aafa52b4d87b270f1d571e',
addr: '70.175.203.76:6881'
},
... 100 more items
]
3. 发布announce
dht.announce(sha, config.dht.announce, function (err) {
if (err !== null) {
console.log('Announced ' + sha)
}
})
dht.announce(sha, ...)
:将特定的 SHA 值(GitTorrent项目即 Git 提交的哈希)及其相应的网络地址发布到 DHT 网络。其他节点可以通过这个信息来查找特定的 Git 数据。通过这个函数,节点通知 DHT 网络,它正在下载一个特定的种子。这使得其他节点能够发现这个下载中的节点,从而进行数据交换。具体实现如下:
/**
* Announce that the peer, controlling the querying node, is downloading a torrent on a port.
* 声明当前节点正在下载一个种子,告诉DHT网络此信息。
*
* @param {string|Buffer} infoHash - 种子的 infoHash,唯一标识符。
* @param {number} port - 当前节点下载种子的端口号。
* @param {function=} cb - 可选的回调函数,宣布完成时调用。
*/
DHT.prototype.announce = function (infoHash, port, cb) {
var self = this
// 如果回调函数没有提供,设置一个空函数
if (!cb) cb = function () {
}
// 如果DHT实例已经销毁,调用回调函数并传递错误信息
if (self.destroyed) return cb(new Error('dht is destroyed'))
// 将infoHash转换为Buffer格式
infoHash = idToBuffer(infoHash)
// 将infoHash转换为十六进制字符串,便于处理和输出
var infoHashHex = idToHexString(infoHash)
// 输出调试信息,表示正在宣布下载
self._debug('announce %s %s', infoHashHex, port)
// 遍历本地节点地址,为每个地址添加peer信息到DHT表中
self.localAddresses.forEach(function (address) {
self._addPeer(address + ':' + port, infoHashHex)
})
// 查找是否已经有对应 infoHash 的路由表存在
var table = self.tables[infoHashHex]
if (table) {
// 如果有对应的路由表,直接使用表中的最近节点进行处理
onClosest(null, table.closest({
id: infoHash }, K))
} else {
// 否则,发起一个lookup查询,查找最近的节点
self.lookup(infoHash, onClosest)
}
/**
* 当查找到最近的节点时调用的回调函数
* @param {Error|null} err - 错误信息,如果存在错误
* @param {Array} closest - 查找到的最近节点列表
*/
function onClosest (err, closest) {
if (err) return cb(err) // 如果有错误,调用回调并返回错误信息
// 遍历每个最近的节点,向他们发送 "announce_peer" 消息
closest.forEach(function (contact) {
self._sendAnnouncePeer(contact.addr, infoHash, port, contact.token)
})
// 输出调试信息,表示宣布操作结束
self._debug('announce end %s %s', infoHashHex, port)
// 调用回调函数,表示宣布完成
cb(null)
}
}
- 路由表的重用:
- 如果
dht.announce
在调用dht.lookup
之后的 5 分钟内被调用,并且路由表仍然有效,则可以重用最近的路由表。这意味着函数会更快地完成,因为不需要再次查询其他节点。 - 如果调用时没有缓存的路由表,
dht.announce
会首先执行dht.lookup
来发现相关节点。这将耗费更多时间,因为需要重新获取有效的 “tokens”。下面是一个官方提供的路由表示例:
- 如果
const dht1 = new DHT()
// some time passes ...
// destroy the dht
const nodes = dht1.toJSON().nodes
dht1.destroy()
// some time passes ...
// initialize a new dht with the same routing table as the first
const dht2 = new DHT()
nodes.forEach(function (node) {
dht2.addNode(node)
})
4. 增dht.put(opts, callback)
import DHT from 'bittorrent-dht'
const dht = new DHT()
const value = Buffer.alloc(200).fill('abc')// 可放入一个小于 1000 字节的值
dht.put({
v: value }, function (err, hash) {
console.error('error=', err)
console.log('hash=', hash)
})
/**
* Write arbitrary mutable and immutable data to the DHT.
* Specified in BEP44: http://bittorrent.org/beps/bep_0044.html
* @param {Object} opts
* @param {function=} cb
*/
DHT.prototype.put = function (opts, cb) {
var self = this
var isMutable = opts.k || opts.sig
if (opts.v === undefined) {
throw new Error('opts.v not given')
}
if (opts.v.length >= 1000) {
throw new Error('v must be less than 1000 bytes in put()')
}
if (isMutable && opts.cas && typeof opts.cas !== 'number') {
throw new Error('opts.cas must be an integer if provided')
}
if (isMutable && !opts.k) {
throw new Error('opts.k ed25519 public key required for mutable put')
}
if (isMutable && opts.k.length !== 32) {
throw new Error('opts.k ed25519 public key must be 32 bytes')
}
if (isMutable && !opts.sig) {
throw new Error('opts.sig signature required for mutable put')
}
if (isMutable && opts.sig.length !== 64) {
throw new Error('opts.sig signature must be 64 bytes')
}
if (isMutable && opts.salt && opts.salt.length > 64) {
throw new Error('opts.salt is > 64 bytes long')
}
if (isMutable && opts.seq === undefined) {
throw new Error('opts.seq not provided for a mutable update')
}
if (isMutable && typeof opts.seq !== 'number') {
throw new Error('opts.seq not an integer')
}
return self._put(opts, cb)
}
/**
* put() without type checks for internal use
* @param {Object} opts
* @param {function=} cb
*/
DHT.prototype._put = function (opts, cb) {
var self = this
var pending = 0
var errors = []
var isMutable = opts.k || opts.sig
var hash = isMutable
? sha1(opts.salt ? Buffer.concat([ opts.salt, opts.k ]) : opts.k)
: sha1(opts.v)
if (self.nodes.toArray().length === 0) {
process.nextTick(function () {
addLocal(null, [])
})
} else {
self.lookup(hash, onLookup)
}
function onLookup (err, nodes) {
if (err) return cb(err)
nodes.forEach(function (node) {
put(node)
})
addLocal()
}
function addLocal () {
var localData = {
id: self.nodeId,
v: opts.v
}
var localAddr = '127.0.0.1:' + self._port
if (isMutable) {
if (opts.cas) localData.cas = opts.cas
localData.sig = opts.sig
localData.k = opts.k
localData.seq = opts.seq
localData.token = opts.token || self._generateToken(localAddr)
}
self.nodes.add({
id: hash,
addr: localAddr,
data: localData
})
if (pending === 0) {
process.nextTick(function () {
cb(errors, hash) })
}
}
return hash
function put (node) {
if (node.data) return // skip data nodes
pending += 1
var t = self._getTransactionId(node.addr, next(node))
var data = {
a: {
id: opts.id || self.nodeId,
v: opts.v
},
t: transactionIdToBuffer(t),
y: 'q',
q: 'put'
}
if (isMutable) {
data.a.token = opts.token || self._generateToken(node.addr)
data.a.seq = Math.round(opts.seq)
data.a.sig = opts.sig
data.a.k = opts.k
if (opts.salt) data.a.salt = opts.salt
if (opts.cas) data.a.cas = Math.round(opts.cas)
}
self._send(node.addr, data)
}
function next (node) {
return function (err) {
if (err) {
err.address = node.addr
errors.push(err)
}
if (--pending === 0) cb(errors, hash)
}
}
}
5. 查dht.get(hash, opts, callback)
var val = new Buffer(key, 'hex')
dht.get(val, function (err, res) {
if (err) {
return console.error(err)
}
var json = res.v.toString()
var repos = JSON.parse(json)
}
GitTorrent的gittorrentd命令实现
- gittorrentd通过 BitTorrent DHT 和 GitTorrent 协议,将 Git 仓库数据通过 P2P 网络共享。
- 通过遍历本地的 Git 仓库目录,列出所有引用,并将其 SHA 通过 DHT 网络广播。
- 使用
net
模块创建 TCP 服务器,响应来自 P2P 节点的 Git 数据请求,并通过 WebTorrent 实现种子文件的共享。
创建 DHT 实例
#!/usr/bin/env node // 文件开头的这行叫做“shebang”,它告诉操作系统使用node来运行这个脚本。因此,当你在命令行中运行./gittorrentd时,它实际上是在用Node.js环境执行该脚本。
// 引入所需的模块
var DHT = require('bittorrent-dht'); // BitTorrent 的 DHT 模块,用于发现对等节点
var EC = require('elliptic').ec; // 椭圆曲线加密库,支持多种加密算法
var ed25519 = new EC('ed25519'); // 使用 Ed25519 椭圆曲线进行加密操作
var exec = require('child_process').exec; // 允许执行 shell 命令
var glob = require('glob'); // 用于文件路径匹配
var fs = require('fs'); // 文件系统模块
var hat = require('hat'); // 随机 ID 生成库
var net = require('net'); // 提供网络套接字操作
var Protocol = require('bittorrent-protocol'); // BitTorrent 协议模块
var spawn = require('child_process').spawn; // 生成子进程以运行外部命令
var ut_gittorrent = require('ut_gittorrent'); // 用于 BitTorrent 上的 Git 传输扩展
var ut_metadata = require('ut_metadata'); // BitTorrent 元数据传输扩展
var WebTorrent = require('webtorrent'); // WebTorrent 客户端库,用于 BitTorrent 下载和种子
var zeroFill = require('zero-fill'); // 补零函数,格式化数字
var config = require('./config'); // 加载用户自定义配置
var git = require('./git'); // 加载 Git 操作相关的自定义模块
// 生成 BitTorrent 客户端的版本号字符串(用于 Peer ID),根据 package.json 的版本生成
var VERSION = require('./package.json').version
.match(/([0-9]+)/g).slice(0, 2).map(zeroFill(2)).join('');
// 错误处理函数,打印错误并退出
function die(error) {
console.error(error);
process.exit(1);
}
// 创建 DHT 实例,使用配置文件中定义的引导节点
var dht = new DHT({
bootstrap: config.dht.bootstrap
});
// DHT 监听来自 Peers 的请求
dht.listen(config.dht.listen);
var announcedRefs = {
}; // 存储已经公布的 Git 引用 (refs)
var userProfile = {
repositories: {
} // 存储用户的 Git 仓库信息
};
// 生成或读取密钥文件
var key = create_or_read_keyfile();
function create_or_read_keyfile() {
// 如果密钥文件不存在,创建一个新的 Ed25519 密钥对
if (!fs.existsSync(config.key)) {
var keypair = new EC('ed25519').genKeyPair();
fs.writeFileSync(config.key, JSON.stringify({
pub: keypair.getPublic('hex'), // 公钥
priv: keypair.getPrivate('hex') // 私钥
}));
}
// 读取已存在的密钥文件
var key = JSON.parse(fs.readFileSync(config.key).toString());
return ed25519.keyPair({
priv: key.priv, // 私钥
privEnc: 'hex',
pub: key.pub, // 公钥
pubEnc: 'hex'
});
}
// 补零函数,确保缓冲区长度达到指定的字节数
function bpad(n, buf) {
if (buf.length === n) return buf;
if (buf.length < n) {
var b = new Buffer(n);
buf.copy(b, n - buf.length);
for (var i = 0; i < n - buf.length; i++) b[i] = 0;
return b;
}
}
当 DHT 就绪时,执行以下操作
var head = ''; // 当前仓库的 HEAD 引用
// 当 DHT 就绪时,执行以下操作
dht.on('ready', function () {
// 查找文件目录
// 使用 glob 搜索所有带有 git-daemon-export-ok 文件的目录(Git 可公开的仓库)
//,比如/projectA/git-daemon-export-ok和/projectB/.git/git-daemon-export-ok
var repos = glob.sync('*/{,.git/}git-daemon-export-ok', {
strict: false });
var count = repos.length;
repos.forEach(function (repo) {
console.log('in repo ' + repo);
repo = repo.replace(/git-daemon-export-ok$/, ''); // 文件名中移除 git-daemon-export-ok
console.log(repo);
var reponame = repo.replace(/\/.git\/$/, ''); // 提取仓库名或路径部分
userProfile.repositories[reponame] = {
}; // 初始化用户的仓库信息
// 使用自定义 Git 模块列出该仓库的所有引用 (refs)
// 第二个参数是一个回调函数,当 git ls-remote 获取到每一个引用时,都会调用该回调函数,并传递该引用的 sha(40 位提交哈希)和 ref(引用名,通常是分支或标签的全名)
var ls = git.ls(repo, function (sha, ref) {
// 只处理 HEAD 和 heads(分支)引用,忽略其他引用
if (ref !== 'HEAD' && !ref.match(/^refs\/heads\//)) {
return;
}
if (ref === 'refs/heads/master') {
head = sha; // 如果是 master 分支,将其 SHA 设为 HEAD
}
userProfile.repositories[reponame][ref] = sha; // 记录仓库引用
if (!announcedRefs[sha]) {
console.log('Announcing ' + sha + ' for ' + ref + ' on repo ' + repo);
announcedRefs[sha] = repo; // 标记已公布的引用
dht.announce(sha, config.dht.announce, function (err) {
if (err !== null) {
console.log('Announced ' + sha);
}
});
}
});
// 当列出引用结束时,减少计数器,最终发布用户的可变密钥
ls.stdout.on('end', function () {
count--;
if (count <= 0) {
publish_mutable_key(); // 当所有仓库处理完毕,发布密钥
}
});
ls.on('exit', function (err) {
if (err) {
die(err); // 如果 git.ls 失败,终止程序
}
});
});
// 定义publish_mutable_key函数发布用户的可变密钥到 DHT
function publish_mutable_key() {
var json = JSON.stringify(userProfile); // 将用户仓库信息序列化为 JSON
if (json.length > 950) {
console.error("Can't publish mutable key: doesn't fit in 950 bytes.");
return false; // 如果数据过大,无法发布
}
var value = new Buffer(json.length);
value.write(json);
var sig = key.sign(value); // 使用用户密钥对数据进行签名
var opts = {
k: bpad(32, Buffer(key.getPublic().x.toArray())), // 公钥
seq: 0,
v: value, // 用户数据
sig: Buffer.concat([
bpad(32, Buffer(sig.r.toArray())), // 签名的一部分
bpad(32, Buffer(sig.s.toArray())) // 签名的另一部分
])
};
console.log(json);
// 将可变密钥发布到 DHT
dht.put(opts, function (errors, hash) {
console.error('errors=', errors);
console.log('hash=', hash.toString('hex')); // 打印发布的哈希值
});
}
// 创建一个 TCP 服务器,传入一个回调函数,该函数在每次有新的连接时被调用
net.createServer(function (socket) {
var wire = new Protocol(); // 创建新的 BitTorrent 协议实例(bittorrent-protocol)
wire.use(ut_gittorrent()); // 使用 GitTorrent 扩展
wire.use(ut_metadata()); // 使用元数据传输扩展
// 将 socket 和协议 wire 连接起来
socket.pipe(wire).pipe(socket);
// 当收到握手时,处理对等节点
wire.on('handshake', function (infoHash, peerId) {
console.log('Received handshake for ' + infoHash.toString('hex'));
var myPeerId = new Buffer('-WW' + VERSION + '-' + hat(48), 'utf8');
wire.handshake(new Buffer(infoHash), new Buffer(myPeerId)); // 发送握手响应
});
// 当对等节点请求 Git 包时,生成相应的数据
wire.ut_gittorrent.on('generatePack', function (sha) {
console.error('calling git pack-objects for ' + sha);
if (!announcedRefs[sha]) {
console.error('Asked for an unknown sha: ' + sha);
return;
}
var directory = announcedRefs[sha];
var have = null;
if (sha !== head) {
have = head;
}
// 使用 Git 打包对象
var pack = git.upload_pack(directory, sha, have);
pack.stderr.pipe(process.stderr);
// 当打包完成时,写入到文件
pack.on('ready', function () {
var filename = sha + '.pack';
var stream = fs.createWriteStream(filename);
pack.stdout.pipe(stream);
stream.on('close', function () {
// 写入操作完成,流关闭时,执行匿名函数
console.error('Finished writing ' + filename);
// 使用 WebTorrent 将文件进行种子共享
var webtorrent = new WebTorrent({
dht: {
bootstrap: config.dht.bootstrap},
tracker: false
});
// 种子创建完成后发送给对等节点
webtorrent.seed(filename, function onTorrent (torrent) {
console.error(torrent.infoHash);
wire.ut_gittorrent.sendTorrent(torrent.infoHash);
});
});
});
// 打包进程结束时,检查退出码
pack.on('exit', function (code) {
if (code !== 0) {
console.error('git-upload-pack process exited with code ' + code);
}
});
});
}).listen(config.dht.announce); // 监听配置中定义的端口
});