├── .idea ├── PhpSpider_Magnet-BitTorrent.iml ├── copyright │ └── profiles_settings.xml ├── dataSources.local.xml ├── dataSources.xml ├── dataSources │ └── bf1d3435-ff6b-4307-a34e-19f091aafb3a.xml ├── misc.xml ├── modules.xml ├── php.xml ├── sqldialects.xml ├── vcs.xml └── workspace.xml ├── README.md ├── dht ├── README.md ├── config.php ├── dht.php ├── header.php ├── inc │ ├── Base.class.php │ ├── Bencode.class.php │ └── Node.class.php ├── infohash.log ├── lib │ └── smtp.class.php ├── sendEmail.php └── sqlUtil.php ├── dht_readme.md └── sql └── sql_infohash.sql /.idea/PhpSpider_Magnet-BitTorrent.iml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /.idea/copyright/profiles_settings.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | -------------------------------------------------------------------------------- /.idea/dataSources.local.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | #@ 7 | ` 8 | 9 | 10 | master_key 11 | root 12 | PhpSpider_Magnet2BitTorrent: 13 | PhpSpider_Magnet2BitTorrent: 14 | 15 | 16 | -------------------------------------------------------------------------------- /.idea/dataSources.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | mysql 6 | true 7 | com.mysql.jdbc.Driver 8 | jdbc:mysql://localhost:3306/PhpSpider_Magnet2BitTorrent 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | -------------------------------------------------------------------------------- /.idea/dataSources/bf1d3435-ff6b-4307-a34e-19f091aafb3a.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 1 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 1 25 | int(10) unsigned|0 26 | 1 27 | 28 | 29 | 1 30 | char(40)|0 31 | 32 | 33 | 1 34 | timestamp|0 35 | CURRENT_TIMESTAMP 36 | 37 | 38 | infohash 39 | 40 | 41 | 42 | info_id 43 | 1 44 | 45 | 46 | -------------------------------------------------------------------------------- /.idea/misc.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | -------------------------------------------------------------------------------- /.idea/modules.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /.idea/php.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /.idea/sqldialects.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /.idea/vcs.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /.idea/workspace.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 18 | 19 | 21 | 22 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | 81 | 82 | 83 | 84 | 85 | 86 | 87 | 88 | 89 | 90 | 91 | 92 | 93 | 94 | 95 | 96 | 97 | 98 | 99 | 100 | 101 | 102 | 103 | 104 | 105 | 106 | 107 | 109 | 110 | 120 | 121 | 122 | 123 | 124 | true 125 | DEFINITION_ORDER 126 | 127 | 128 | 129 | 133 | 134 | 135 | 136 | 137 | 138 | 139 | 140 | 141 | 142 | 143 | 144 | 145 | 146 | 147 | 148 | 149 | 150 | 151 | 152 | 153 | 154 | 155 | 156 | 157 | 158 | 159 | 160 | 161 | 162 | 165 | 166 | 167 | 168 | 171 | 172 | 175 | 176 | 177 | 178 | 181 | 182 | 185 | 186 | 189 | 190 | 191 | 192 | 193 | 194 | 195 | 196 | 197 | 198 | 199 | 200 | 201 | 202 | 203 | 204 | 205 | 206 | 207 | 208 | 209 | 210 | 211 | 212 | 213 | 214 | 215 | 216 | 217 | 218 | 219 | 220 | 221 | 222 | 223 | 224 | 225 | 226 | 227 | 228 | 229 | 230 | 231 | 232 | 233 | 234 | 235 | 236 | 237 | 238 | 239 | 240 | project 241 | 242 | 243 | 244 | 245 | 246 | 247 | 248 | 249 | 250 | 251 | 252 | 253 | 254 | 255 | 256 | project 257 | 258 | 259 | true 260 | 261 | 262 | 263 | DIRECTORY 264 | 265 | false 266 | 267 | 268 | 269 | 270 | 271 | 272 | 273 | 274 | 275 | 276 | 277 | 278 | 279 | 280 | 282 | 283 | 284 | 285 | 1493442842756 286 | 293 | 294 | 295 | 296 | 298 | 299 | 300 | 301 | 302 | 303 | 304 | 305 | 306 | 307 | 308 | 309 | 310 | 311 | 312 | 313 | 314 | 315 | 316 | 317 | 318 | 319 | 320 | 321 | 322 | 323 | 326 | 329 | 330 | 331 | 333 | 334 | 335 | 336 | 337 | 338 | 339 | 340 | 341 | 342 | 343 | 344 | 345 | 346 | 347 | 348 | 349 | 350 | 351 | 352 | 353 | 354 | 355 | 356 | 357 | 358 | 359 | 360 | 361 | 362 | 363 | 364 | 365 | 366 | 367 | 368 | 369 | 370 | 371 | 372 | 373 | 374 | 375 | 376 | 377 | 378 | 379 | 380 | 381 | 382 | 383 | 384 | 385 | 386 | 387 | 388 | 389 | 390 | 391 | 392 | 393 | 394 | 395 | 396 | 397 | 398 | 399 | 400 | 401 | 402 | 403 | 404 | 405 | 406 | 407 | 408 | 409 | 410 | 411 | 412 | 413 | 414 | 415 | 416 | 417 | 418 | 419 | 420 | 421 | 422 | 423 | 424 | 425 | 426 | 427 | 428 | 429 | 430 | 431 | 432 | 433 | 434 | 435 | 436 | 437 | 438 | 439 | 440 | 441 | 442 | 443 | 444 | 445 | 446 | 447 | 448 | 449 | 450 | 451 | 452 | 453 | 454 | 455 | 456 | 457 | 458 | 459 | 460 | 461 | 462 | 463 | 464 | 465 | 466 | 467 | 468 | 469 | 470 | 471 | 472 | 473 | 474 | 475 | 476 | 477 | 478 | 479 | 480 | 481 | 482 | 483 | 484 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # PHP编写的DHT网络爬虫 2 | 3 | 以分析当前热门、用户喜好为目的,收集和查看磁力链接和种子,当然少不了证明PHP是世界上最好的语言 ^_^ 4 | 5 | 磁力搜索站点:www.imdalai.com (目前在收集分析磁力和种子阶段,尚未提供搜索) 6 | 7 | ## 1.开发环境介绍 8 | 9 | ### 1.环境搭建过程 10 | 11 | [centos7+php7.1.4+mysql5.7+swoole搭建记录](https://dalaizhao.github.io/2017/05/23/centos7-php7-1-4-mysql5-7-swoole%E6%90%AD%E5%BB%BA%E8%AE%B0%E5%BD%95/) 12 | 13 | ### 2.介绍开发环境 14 | 15 | linux系统:centos7.2 16 | php版本:php7.1.4 17 | 数据库:mysql5.7 18 | 当然要用高性能、高并发通信引擎:[swoole1.9.9](https://github.com/swoole/swoole-src) 19 | 20 | ### 3.协议介绍 21 | 22 | [DHT协议解析](https://github.com/dalaizhao/PhpSpider_Magnet-BitTorrent/blob/master/dht_readme.md) 23 | 24 | 目前忙于实习,本系统也是处于未完善状态,不过收集hashInfo倒是完善的(见dht文件夹),其它异步数据库、和bt下载都出与未完善阶段。那天抽空再完善整个系统。 谢谢,鼓励!   25 | 26 | 27 | # 重构中查看分支: feature_refactoring 28 | 29 | -------------------------------------------------------------------------------- /dht/README.md: -------------------------------------------------------------------------------- 1 | PHP编写的DHT爬虫 2 | 3 | 可自动加入DHT网络并获取到infohash信息,刚开始在建立连接收集infohash比较慢。 4 | 5 | 使用方法: 6 | 7 | 1. 安装PHP,请安装新版本 8 | 2. 安装swoole扩展 9 | 3. 运行命令启动 10 | 11 | ``` 12 | php dht.php 13 | ``` 14 | linux环境下后台运行命令:

nohup php dht.php &

15 | 16 | -------------------------------------------------------------------------------- /dht/config.php: -------------------------------------------------------------------------------- 1 | set(array( 47 | 'worker_num' => WORKER_NUM, 48 | 'daemonize' => FALSE, 49 | 'max_request' => MAX_REQUEST, 50 | 'dispatch_mode' => 2 51 | )); 52 | 53 | 54 | $serv->on('WorkerStart', function ($serv, $worker_id) { 55 | // 添加一个定时器, 使服务器定时寻找节点 56 | swoole_timer_tick(AUTO_FIND_TIME, function () { 57 | auto_find_node(); 58 | }); 59 | }); 60 | $serv->on('Receive', function ($serv, $fd, $from_id, $data) { 61 | // 检查数据长度 62 | if (strlen($data) == 0) 63 | return false; 64 | 65 | // 对数据进行解码 66 | $msg = Base::decode($data); 67 | 68 | // 获取对端链接信息, udp链接需要加上$from_id参数 69 | $fdinfo = $serv->connection_info($fd, $from_id); 70 | 71 | // 对接收到的数据进行类型判断 72 | if (!isset($msg['y'])) { 73 | /// 数据格式不合法 74 | /// 什么都不做 75 | } else if ($msg['y'] == 'r') { 76 | // 如果是回复, 且包含nodes信息 77 | if (array_key_exists('nodes', $msg['r'])) 78 | // 对nodes进行操作 79 | response_action($msg, array($fdinfo['remote_ip'], $fdinfo['remote_port'])); 80 | } elseif ($msg['y'] == 'q') { 81 | // 如果是请求, 则执行请求判断 82 | request_action($msg, array($fdinfo['remote_ip'], $fdinfo['remote_port'])); 83 | } else { 84 | return false; 85 | } 86 | }); 87 | 88 | 89 | //启动服务器 90 | $foo = $serv->start(); 91 | 92 | swoole_timer_tick(AUTO_FIND_TIME, function ($interval) { 93 | for ($i = 0; $i < MAX_PROCESS; $i++) { 94 | $process = new swoole_process(function () { 95 | auto_find_node(); 96 | }); 97 | $pid = $process->start(); 98 | $threads[$pid] = $process; 99 | swoole_process::wait(); 100 | } 101 | }); 102 | 103 | 104 | /** 105 | * 自动查找节点方法, 将在DHT网络中自动搜寻节点信息 106 | * 107 | * @return void 108 | */ 109 | function auto_find_node() 110 | { 111 | global $table; 112 | 113 | // 如果路由表中没有数据则先加入DHT网络 114 | if (count($table) == 0) 115 | return join_dht(); 116 | 117 | // 循环处理路由表 118 | while (count($table)) { 119 | // 从路由表中删除第一个node并返回被删除的node 120 | $node = array_shift($table); 121 | // 发送查找find_node到node中 122 | find_node(array($node->ip, $node->port), $node->nid); 123 | } 124 | } 125 | 126 | /** 127 | * 加入dht网络 128 | * 129 | * @return void 130 | */ 131 | function join_dht() 132 | { 133 | global $table, $bootstrap_nodes; 134 | 135 | // 循环操作 136 | foreach ($bootstrap_nodes as $node) { 137 | // 将node域名解析为IP地址, 并发送find_node请求 138 | find_node(array(gethostbyname($node[0]), $node[1])); 139 | } 140 | } 141 | 142 | /** 143 | * 发送find_node请求 144 | * 145 | * @param array $address 对端链接信息 146 | * @param string $id node id 147 | * 148 | * @return void 149 | */ 150 | function find_node($address, $id = null) 151 | { 152 | global $nid; 153 | 154 | // 若未指定id则使用自身node id 155 | if (is_null($id)) 156 | $mid = $nid; 157 | else 158 | // 否则伪造一个相邻id 159 | $mid = Base::get_neighbor($id, $nid); 160 | 161 | // 定义发送数据 162 | $msg = array( 163 | 't' => Base::entropy(2), 164 | 'y' => 'q', 165 | 'q' => 'find_node', 166 | 'a' => array( 167 | 'id' => $nid, 168 | 'target' => $mid 169 | ) 170 | ); 171 | 172 | // 发送请求数据到对端 173 | send_response($msg, $address); 174 | } 175 | 176 | /** 177 | * 处理对端发来的请求 178 | * 179 | * @param array $msg 接收到的请求数据 180 | * @param array $address 对端链接信息 181 | * 182 | * @return void 183 | */ 184 | function request_action($msg, $address) 185 | { 186 | switch ($msg['q']) { 187 | case 'ping': 188 | on_ping($msg, $address); 189 | break; 190 | case 'find_node': 191 | on_find_node($msg, $address); 192 | break; 193 | case 'get_peers': 194 | // 处理get_peers请求 195 | on_get_peers($msg, $address); 196 | break; 197 | case 'announce_peer': 198 | // 处理announce_peer请求 199 | on_announce_peer($msg, $address); 200 | break; 201 | default: 202 | return false; 203 | } 204 | } 205 | 206 | /** 207 | * 处理接收到的find_node回复 208 | * 209 | * @param array $msg 接收到的数据 210 | * @param array $address 对端链接信息 211 | * 212 | * @return void 213 | */ 214 | function response_action($msg, $address) 215 | { 216 | // 先检查接收到的信息是否正确 217 | if (!isset($msg['r']['nodes']) || !isset($msg['r']['nodes'][1])) 218 | return false; 219 | 220 | // 对nodes数据进行解码 221 | $nodes = Base::decode_nodes($msg['r']['nodes']); 222 | 223 | // 对nodes循环处理 224 | foreach ($nodes as $node) { 225 | // 将node加入到路由表中 226 | append($node); 227 | } 228 | } 229 | 230 | /** 231 | * 处理ping请求 232 | * 233 | * @param array $msg 接收到的ping请求数据 234 | * @param array $address 对端链接信息 235 | * 236 | * @return void 237 | */ 238 | function on_ping($msg, $address) 239 | { 240 | global $nid; 241 | 242 | // 获取对端node id 243 | $id = $msg['a']['id']; 244 | // 生成回复数据 245 | $msg = array( 246 | 't' => $msg['t'], 247 | 'y' => 'r', 248 | 'r' => array( 249 | 'id' => $nid 250 | ) 251 | ); 252 | 253 | // 将node加入路由表 254 | append(new Node($id, $address[0], $address[1])); 255 | // 发送回复数据 256 | send_response($msg, $address); 257 | } 258 | 259 | /** 260 | * 处理find_node请求 261 | * 262 | * @param array $msg 接收到的find_node请求数据 263 | * @param array $address 对端链接信息 264 | * 265 | * @return void 266 | */ 267 | function on_find_node($msg, $address) 268 | { 269 | global $nid; 270 | 271 | // 获取node列表 272 | $nodes = get_nodes(16); 273 | // 获取对端node id 274 | $id = $msg['a']['id']; 275 | // 生成回复数据 276 | $msg = array( 277 | 't' => $msg['t'], 278 | 'y' => 'r', 279 | 'r' => array( 280 | 'id' => $nid, 281 | 'nodes' => Base::encode_nodes($nodes) 282 | ) 283 | ); 284 | 285 | // 将node加入路由表 286 | append(new Node($id, $address[0], $address[1])); 287 | // 发送回复数据 288 | send_response($msg, $address); 289 | } 290 | 291 | /** 292 | * 处理get_peers请求 293 | * 294 | * @param array $msg 接收到的get_peers请求数据 295 | * @param array $address 对端链接信息 296 | * 297 | * @return void 298 | */ 299 | function on_get_peers($msg, $address) 300 | { 301 | global $nid, $file; 302 | 303 | // 获取info_hash信息 304 | $infohash = $msg['a']['info_hash']; 305 | // 获取node id 306 | $id = $msg['a']['id']; 307 | 308 | // 生成回复数据 309 | $msg = array( 310 | 't' => $msg['t'], 311 | 'y' => 'r', 312 | 'r' => array( 313 | 'id' => $nid, 314 | 'nodes' => Base::encode_nodes(get_nodes()), 315 | 'token' => substr($infohash, 0, 2) 316 | ) 317 | ); 318 | //插入数据库 319 | insert(strtoupper(bin2hex($infohash))); 320 | // 将node加入路由表 321 | append(new Node($id, $address[0], $address[1])); 322 | // 向对端发送回复数据 323 | send_response($msg, $address); 324 | } 325 | 326 | /** 327 | * 处理announce_peer请求 328 | * 329 | * @param array $msg 接收到的announce_peer请求数据 330 | * @param array $address 对端链接信息 331 | * 332 | * @return void 333 | */ 334 | function on_announce_peer($msg, $address) 335 | { 336 | global $nid, $file; 337 | 338 | // 获取infohash 339 | $infohash = $msg['a']['info_hash']; 340 | // 获取token 341 | $token = $msg['a']['token']; 342 | // 获取node id 343 | $id = $msg['a']['id']; 344 | 345 | // 验证token是否正确 346 | if (substr($infohash, 0, 2) == $token) { 347 | /*$txt = array( 348 | 'action' => 'announce_peer', 349 | 'msg' => array( 350 | 'ip' => $address[0], 351 | 'port1' => $address[1], 352 | 'port2' => $msg['a']['port'], 353 | 'infohash' => $infohash 354 | ) 355 | ); 356 | var_dump($txt);*/ 357 | //插入数据库 358 | insert(strtoupper(bin2hex($infohash))); 359 | } 360 | 361 | // 生成回复数据 362 | $msg = array( 363 | 't' => $msg['t'], 364 | 'y' => 'r', 365 | 'r' => array( 366 | 'id' => $nid 367 | ) 368 | ); 369 | // 发送请求回复 370 | send_response($msg, $address); 371 | } 372 | 373 | /** 374 | * 向对端发送数据 375 | * 376 | * @param array $msg 要发送的数据 377 | * @param array $address 对端链接信息 378 | * 379 | * @return void 380 | */ 381 | function send_response($msg, $address) 382 | { 383 | global $serv, $file; 384 | 385 | if (filter_var($address[0], FILTER_VALIDATE_IP) === FALSE) { 386 | 387 | $ip = gethostbyname($address[0]); 388 | if (strcmp($ip, $address[0]) !== 0) { 389 | $address[0] = $ip; 390 | } else { 391 | } 392 | } 393 | $serv->sendto($address[0], $address[1], Base::encode($msg)); 394 | 395 | } 396 | 397 | /** 398 | * 添加node到路由表 399 | * 400 | * @param Node $node node模型 401 | * 402 | * @return boolean 是否添加成功 403 | */ 404 | function append($node) 405 | { 406 | global $nid, $table; 407 | 408 | // 检查node id是否正确 409 | if (!isset($node->nid[19])) 410 | return false; 411 | 412 | // 检查是否为自身node id 413 | if ($node->nid == $nid) 414 | return false; 415 | 416 | // 检查node是否已存在 417 | if (in_array($node, $table)) 418 | return false; 419 | 420 | // 如果路由表中的项达到200时, 删除第一项 421 | if (count($table) >= 200) 422 | array_shift($table); 423 | 424 | return array_push($table, $node); 425 | } 426 | 427 | function get_nodes($len = 8) 428 | { 429 | global $table; 430 | 431 | if (count($table) <= $len) 432 | return $table; 433 | 434 | $nodes = array(); 435 | 436 | for ($i = 0; $i < $len; $i++) { 437 | $nodes[] = $table[mt_rand(0, count($table) - 1)]; 438 | } 439 | 440 | return $nodes; 441 | } 442 | 443 | -------------------------------------------------------------------------------- /dht/header.php: -------------------------------------------------------------------------------- 1 | nid, ip2long($node->ip), $node->port); 74 | 75 | return $n; 76 | } 77 | 78 | /** 79 | * 对nodes列表解码 80 | * @param string $msg 要解码的数据 81 | * @return mixed 解码后的数据 82 | */ 83 | static public function decode_nodes($msg){ 84 | // 先判断数据长度是否正确 85 | if((strlen($msg) % 26) != 0) 86 | return array(); 87 | 88 | $n = array(); 89 | 90 | // 每次截取26字节进行解码 91 | foreach(str_split($msg, 26) as $s){ 92 | // 将截取到的字节进行字节序解码 93 | $r = unpack('a20nid/Nip/np', $s); 94 | $n[] = new Node($r['nid'], long2ip($r['ip']), $r['p']); 95 | } 96 | 97 | return $n; 98 | } 99 | } -------------------------------------------------------------------------------- /dht/inc/Bencode.class.php: -------------------------------------------------------------------------------- 1 | source = $source; 51 | $this->length = strlen($source); 52 | } 53 | 54 | /** 55 | * 解码bencode数据 56 | * @param string $source 要解码的数据 57 | * @return mixed 解码后的数据 58 | */ 59 | static public function decode($source){ 60 | // 检查数据是否正确 61 | if(!is_string($source)) 62 | return ''; 63 | 64 | // 调用类本身完成解码 65 | $decode = new self($source); 66 | $decoded = $decode->do_decode(); 67 | 68 | // 验证数据 69 | if($decode->offset != $decode->length) 70 | return ''; 71 | 72 | return $decoded; 73 | } 74 | 75 | /** 76 | * 选择操作类型 77 | * @return mixed 解码后的数据 78 | */ 79 | private function do_decode(){ 80 | // 截取数据字符判断操作类型 81 | switch($this->get_char()){ 82 | case 'i': 83 | ++$this->offset; 84 | return $this->decode_integer(); 85 | case 'l': 86 | ++$this->offset; 87 | return $this->decode_list(); 88 | case 'd': 89 | ++$this->offset; 90 | return $this->decode_dict(); 91 | default: 92 | if(ctype_digit($this->get_char())) 93 | return $this->decode_string(); 94 | } 95 | 96 | return ''; 97 | } 98 | 99 | /** 100 | * 解码数字类型数据 101 | * @return integer 解码后的数据 102 | */ 103 | private function decode_integer(){ 104 | $offset_e = strpos($this->source, 'e', $this->offset); 105 | 106 | if($offset_e === false) 107 | return ''; 108 | 109 | $current_off = $this->offset; 110 | 111 | if($this->get_char($current_off) == '-') 112 | ++$current_off; 113 | 114 | if($offset_e === $current_off) 115 | return ''; 116 | 117 | while($current_off < $offset_e){ 118 | if(!ctype_digit($this->get_char($current_off))) 119 | return ''; 120 | 121 | ++$current_off; 122 | } 123 | 124 | $value = substr($this->source, $this->offset, $offset_e - $this->offset); 125 | $absolute_value = (string) abs($value); 126 | 127 | if(1 < strlen($absolute_value) && '0' == $value[0]) 128 | return ''; 129 | 130 | $this->offset = $offset_e + 1; 131 | 132 | return $value + 0; 133 | } 134 | 135 | /** 136 | * 解码字符串类型数据 137 | * @return string 解码后的数据 138 | */ 139 | private function decode_string(){ 140 | if('0' === $this->get_char() && ':' != $this->get_char($this->offset + 1)) 141 | return ''; 142 | 143 | $offset_o = strpos($this->source, ':', $this->offset); 144 | 145 | if($offset_o === false) 146 | return ''; 147 | 148 | $content_length = (int) substr($this->source, $this->offset, $offset_o); 149 | 150 | if(($content_length + $offset_o + 1) > $this->length) 151 | return ''; 152 | 153 | $value = substr($this->source, $offset_o + 1, $content_length); 154 | $this->offset = $offset_o + $content_length + 1; 155 | 156 | return $value; 157 | } 158 | 159 | /** 160 | * 解码数组类型数据 161 | * @return array 解码后的数据 162 | */ 163 | private function decode_list(){ 164 | $list = array(); 165 | $terminated = false; 166 | $list_offset = $this->offset; 167 | 168 | while($this->get_char() !== false){ 169 | if($this->get_char() == 'e'){ 170 | $terminated = true; 171 | break; 172 | } 173 | 174 | $list[] = $this->do_decode(); 175 | } 176 | 177 | if(!$terminated && $this->get_char() === false) 178 | return ''; 179 | 180 | $this->offset++; 181 | 182 | return $list; 183 | } 184 | 185 | /** 186 | * 解码词典类型数据 187 | * @return array 解码后的数据 188 | */ 189 | private function decode_dict(){ 190 | $dict = array(); 191 | $terminated = false; 192 | $dict_offset = $this->offset; 193 | 194 | while($this->get_char() !== false){ 195 | if($this->get_char() == 'e'){ 196 | $terminated = true; 197 | break; 198 | } 199 | 200 | $key_offset = $this->offset; 201 | 202 | if(!ctype_digit($this->get_char())) 203 | return ''; 204 | 205 | $key = $this->decode_string(); 206 | 207 | if(isset($dict[$key])) 208 | return ''; 209 | 210 | $dict[$key] = $this->do_decode(); 211 | } 212 | 213 | if(!$terminated && $this->get_char() === false) 214 | return ''; 215 | 216 | $this->offset++; 217 | 218 | return $dict; 219 | } 220 | 221 | /** 222 | * 截取数据 223 | * @param integer $offset 截取索引 224 | * @return string|false 截取到的数据 225 | */ 226 | private function get_char($offset = null){ 227 | if($offset === null) 228 | $offset = $this->offset; 229 | 230 | if(empty($this->source) || $this->offset >= $this->length) 231 | return false; 232 | 233 | return $this->source[$offset]; 234 | } 235 | } 236 | 237 | class Encode{ 238 | /** 239 | * 保存编码数据 240 | * @var mixed 241 | */ 242 | private $data; 243 | 244 | /** 245 | * 析构函数, 传入要编码的数据 246 | * @param mixed $data 要编码的数据 247 | */ 248 | private function __construct($data){ 249 | $this->data = $data; 250 | } 251 | 252 | /** 253 | * bencode编码 254 | * @param mixed $data 要编码的数据 255 | * @return string 编码后的数据 256 | */ 257 | static public function encode($data){ 258 | if(is_object($data)){ 259 | if(method_exists($data, 'toArray')) 260 | $data = $data->toArray(); 261 | else 262 | $data = (array) $data; 263 | } 264 | 265 | $encode = new self($data); 266 | $encoded = $encode->do_encode(); 267 | 268 | return $encoded; 269 | } 270 | 271 | /** 272 | * 选择操作类型 273 | * @param mixed $data 要编码的数据 274 | * @return string 编码后的数据 275 | */ 276 | private function do_encode($data = null){ 277 | $data = is_null($data) ? $this->data : $data; 278 | 279 | if(is_array($data) && (isset($data[0]) || empty($data))){ 280 | return $this->encode_list($data); 281 | }elseif(is_array($data)){ 282 | return $this->encode_dict($data); 283 | }elseif(is_integer($data) || is_float($data)){ 284 | $data = sprintf("%.0f", round($data, 0)); 285 | return $this->encode_integer($data); 286 | }else{ 287 | return $this->encode_string($data); 288 | } 289 | } 290 | 291 | /** 292 | * 编码数字类型数据 293 | * @param integer $data 要编码的数据 294 | * @return string 编码后的数据 295 | */ 296 | private function encode_integer($data = null){ 297 | $data = is_null($data) ? $this->data : $data; 298 | 299 | return sprintf("i%.0fe", $data); 300 | } 301 | 302 | /** 303 | * 编码字符串类型数据 304 | * @param string $data 要编码的数据 305 | * @return string 编码后的数据 306 | */ 307 | private function encode_string($data = null){ 308 | $data = is_null($data) ? $this->data : $data; 309 | 310 | return sprintf("%d:%s", strlen($data), $data); 311 | } 312 | 313 | /** 314 | * 编码数组数据 315 | * @param array $data 要编码的数据 316 | * @return string 编码后的数据 317 | */ 318 | private function encode_list(array $data = null){ 319 | $data = is_null($data) ? $this->data : $data; 320 | $list = ''; 321 | 322 | foreach($data as $value) 323 | $list .= $this->do_encode($value); 324 | 325 | return "l{$list}e"; 326 | } 327 | 328 | /** 329 | * 编码词典类型数据 330 | * @param array $data 要编码的数据 331 | * @return string 编码后的数据 332 | */ 333 | private function encode_dict(array $data = null){ 334 | $data = is_null($data) ? $this->data : $data; 335 | ksort($data); 336 | $dict = ''; 337 | 338 | foreach($data as $key => $value) 339 | $dict .= $this->encode_string($key) . $this->do_encode($value); 340 | 341 | return "d{$dict}e"; 342 | } 343 | } -------------------------------------------------------------------------------- /dht/inc/Node.class.php: -------------------------------------------------------------------------------- 1 | nid = $nid; 31 | $this->ip = $ip; 32 | $this->port = $port; 33 | } 34 | 35 | /** 36 | * 使外部可获取私有属性 37 | * @param string $name 属性名称 38 | * @return mixed 属性值 39 | */ 40 | public function __get($name){ 41 | // 检查属性是否存在 42 | if(isset($this->$name)) 43 | return $this->$name; 44 | 45 | return null; 46 | } 47 | 48 | /** 49 | * 使外部可直接对私有属性赋值 50 | * @param string $name 属性名称 51 | * @param mixed $value 属性值 52 | * @return void 53 | */ 54 | public function __set($name, $value){ 55 | $this->$name = $value; 56 | } 57 | 58 | /** 59 | * 检查属性是否设置 60 | * @param string $name 属性名称 61 | * @return boolean 是否设置 62 | */ 63 | public function __isset($name){ 64 | return isset($this->$name); 65 | } 66 | 67 | /** 68 | * 将Node模型转换为数组 69 | * @return array 转换后的数组 70 | */ 71 | public function to_array(){ 72 | return array('nid' => $this->nid, 'ip' => $this->ip, 'port' => $this->port); 73 | } 74 | } -------------------------------------------------------------------------------- /dht/infohash.log: -------------------------------------------------------------------------------- 1 | #error_log 2 | -------------------------------------------------------------------------------- /dht/lib/smtp.class.php: -------------------------------------------------------------------------------- 1 | debug = FALSE; 36 | 37 | $this->smtp_port = $smtp_port; 38 | 39 | $this->relay_host = $relay_host; 40 | 41 | $this->time_out = 30; //is used in fsockopen() 42 | # 43 | 44 | $this->auth = $auth;//auth 45 | 46 | $this->user = $user; 47 | 48 | $this->pass = $pass; 49 | 50 | # 51 | 52 | $this->host_name = "localhost"; //is used in HELO command 53 | $this->log_file = ""; 54 | 55 | $this->sock = FALSE; 56 | 57 | } 58 | 59 | /* Main Function */ 60 | 61 | function sendmail($to, $from, $subject = "", $body = "", $mailtype, $cc = "", $bcc = "", $additional_headers = "") 62 | 63 | { 64 | 65 | $mail_from = $this->get_address($this->strip_comment($from)); 66 | 67 | $body = preg_replace("/(^|(\r\n))(\.)/", "\1.\3", $body); 68 | //$string = ereg_replace(' value', ' ', trim($string)); if(eregi('^('value', $value) 69 | //$string = preg_replace('{ value}', ' ', trim($string)); 70 | 71 | $header = "MIME-Version:1.0\r\n"; 72 | 73 | if($mailtype=="HTML"){ 74 | 75 | $header .= "Content-Type:text/html\r\n"; 76 | 77 | } 78 | 79 | $header .= "To: ".$to."\r\n"; 80 | 81 | if ($cc != "") { 82 | 83 | $header .= "Cc: ".$cc."\r\n"; 84 | 85 | } 86 | 87 | $header .= "From: $from<".$from.">\r\n"; 88 | 89 | $header .= "Subject: ".$subject."\r\n"; 90 | 91 | $header .= $additional_headers; 92 | 93 | $header .= "Date: ".date("r")."\r\n"; 94 | 95 | $header .= "X-Mailer:By Redhat (PHP/".phpversion().")\r\n"; 96 | 97 | list($msec, $sec) = explode(" ", microtime()); 98 | 99 | $header .= "Message-ID: <".date("YmdHis", $sec).".".($msec*1000000).".".$mail_from.">\r\n"; 100 | 101 | $TO = explode(",", $this->strip_comment($to)); 102 | 103 | if ($cc != "") { 104 | 105 | $TO = array_merge($TO, explode(",", $this->strip_comment($cc))); 106 | 107 | } 108 | 109 | if ($bcc != "") { 110 | 111 | $TO = array_merge($TO, explode(",", $this->strip_comment($bcc))); 112 | 113 | } 114 | 115 | $sent = TRUE; 116 | 117 | foreach ($TO as $rcpt_to) { 118 | 119 | $rcpt_to = $this->get_address($rcpt_to); 120 | 121 | if (!$this->smtp_sockopen($rcpt_to)) { 122 | 123 | $this->log_write("Error: Cannot send email to ".$rcpt_to."\n"); 124 | 125 | $sent = FALSE; 126 | 127 | continue; 128 | 129 | } 130 | 131 | if ($this->smtp_send($this->host_name, $mail_from, $rcpt_to, $header, $body)) { 132 | 133 | $this->log_write("E-mail has been sent to <".$rcpt_to.">\n"); 134 | 135 | } else { 136 | 137 | $this->log_write("Error: Cannot send email to <".$rcpt_to.">\n"); 138 | 139 | $sent = FALSE; 140 | 141 | } 142 | 143 | fclose($this->sock); 144 | 145 | $this->log_write("Disconnected from remote host\n"); 146 | 147 | } 148 | 149 | return $sent; 150 | 151 | } 152 | 153 | /* Private Functions */ 154 | 155 | function smtp_send($helo, $from, $to, $header, $body = "") 156 | 157 | { 158 | 159 | if (!$this->smtp_putcmd("HELO", $helo)) { 160 | 161 | return $this->smtp_error("sending HELO command"); 162 | 163 | } 164 | 165 | #auth 166 | 167 | if($this->auth){ 168 | 169 | if (!$this->smtp_putcmd("AUTH LOGIN", base64_encode($this->user))) { 170 | 171 | return $this->smtp_error("sending HELO command"); 172 | 173 | } 174 | 175 | if (!$this->smtp_putcmd("", base64_encode($this->pass))) { 176 | 177 | return $this->smtp_error("sending HELO command"); 178 | 179 | } 180 | 181 | } 182 | 183 | # 184 | 185 | if (!$this->smtp_putcmd("MAIL", "FROM:<".$from.">")) { 186 | 187 | return $this->smtp_error("sending MAIL FROM command"); 188 | 189 | } 190 | 191 | if (!$this->smtp_putcmd("RCPT", "TO:<".$to.">")) { 192 | 193 | return $this->smtp_error("sending RCPT TO command"); 194 | 195 | } 196 | 197 | if (!$this->smtp_putcmd("DATA")) { 198 | 199 | return $this->smtp_error("sending DATA command"); 200 | 201 | } 202 | 203 | if (!$this->smtp_message($header, $body)) { 204 | 205 | return $this->smtp_error("sending message"); 206 | 207 | } 208 | 209 | if (!$this->smtp_eom()) { 210 | 211 | return $this->smtp_error("sending . [EOM]"); 212 | 213 | } 214 | 215 | if (!$this->smtp_putcmd("QUIT")) { 216 | 217 | return $this->smtp_error("sending QUIT command"); 218 | 219 | } 220 | 221 | return TRUE; 222 | 223 | } 224 | 225 | function smtp_sockopen($address) 226 | 227 | { 228 | 229 | if ($this->relay_host == "") { 230 | 231 | return $this->smtp_sockopen_mx($address); 232 | 233 | } else { 234 | 235 | return $this->smtp_sockopen_relay(); 236 | 237 | } 238 | 239 | } 240 | 241 | function smtp_sockopen_relay() 242 | 243 | { 244 | 245 | $this->log_write("Trying to ".$this->relay_host.":".$this->smtp_port."\n"); 246 | 247 | $this->sock = @fsockopen($this->relay_host, $this->smtp_port, $errno, $errstr, $this->time_out); 248 | 249 | if (!($this->sock && $this->smtp_ok())) { 250 | 251 | $this->log_write("Error: Cannot connenct to relay host ".$this->relay_host."\n"); 252 | 253 | $this->log_write("Error: ".$errstr." (".$errno.")\n"); 254 | 255 | return FALSE; 256 | 257 | } 258 | 259 | $this->log_write("Connected to relay host ".$this->relay_host."\n"); 260 | 261 | return TRUE;; 262 | 263 | } 264 | 265 | function smtp_sockopen_mx($address) 266 | 267 | { 268 | 269 | $domain = preg_replace("^.+@([^@]+)$", "\1", $address); 270 | 271 | if (!@getmxrr($domain, $MXHOSTS)) { 272 | 273 | $this->log_write("Error: Cannot resolve MX \"".$domain."\"\n"); 274 | 275 | return FALSE; 276 | 277 | } 278 | //专注与php学习 http://www.daixiaorui.com 欢迎您的访问 279 | 280 | foreach ($MXHOSTS as $host) { 281 | 282 | $this->log_write("Trying to ".$host.":".$this->smtp_port."\n"); 283 | 284 | $this->sock = @fsockopen($host, $this->smtp_port, $errno, $errstr, $this->time_out); 285 | 286 | if (!($this->sock && $this->smtp_ok())) { 287 | 288 | $this->log_write("Warning: Cannot connect to mx host ".$host."\n"); 289 | 290 | $this->log_write("Error: ".$errstr." (".$errno.")\n"); 291 | 292 | continue; 293 | 294 | } 295 | 296 | $this->log_write("Connected to mx host ".$host."\n"); 297 | 298 | return TRUE; 299 | 300 | } 301 | 302 | $this->log_write("Error: Cannot connect to any mx hosts (".implode(", ", $MXHOSTS).")\n"); 303 | 304 | return FALSE; 305 | 306 | } 307 | 308 | function smtp_message($header, $body) 309 | 310 | { 311 | 312 | fputs($this->sock, $header."\r\n".$body); 313 | 314 | $this->smtp_debug("> ".str_replace("\r\n", "\n"."> ", $header."\n> ".$body."\n> ")); 315 | 316 | return TRUE; 317 | 318 | } 319 | 320 | function smtp_eom() 321 | 322 | { 323 | 324 | fputs($this->sock, "\r\n.\r\n"); 325 | 326 | $this->smtp_debug(". [EOM]\n"); 327 | 328 | return $this->smtp_ok(); 329 | 330 | } 331 | 332 | function smtp_ok() 333 | 334 | { 335 | 336 | $response = str_replace("\r\n", "", fgets($this->sock, 512)); 337 | 338 | $this->smtp_debug($response."\n"); 339 | 340 | if (!preg_match("/^[23]/", $response)) { 341 | 342 | fputs($this->sock, "QUIT\r\n"); 343 | 344 | fgets($this->sock, 512); 345 | 346 | $this->log_write("Error: Remote host returned \"".$response."\"\n"); 347 | 348 | return FALSE; 349 | 350 | } 351 | 352 | return TRUE; 353 | 354 | } 355 | 356 | function smtp_putcmd($cmd, $arg = "") 357 | 358 | { 359 | 360 | if ($arg != "") { 361 | 362 | if($cmd=="") $cmd = $arg; 363 | 364 | else $cmd = $cmd." ".$arg; 365 | 366 | } 367 | 368 | fputs($this->sock, $cmd."\r\n"); 369 | 370 | $this->smtp_debug("> ".$cmd."\n"); 371 | 372 | return $this->smtp_ok(); 373 | 374 | } 375 | 376 | function smtp_error($string) 377 | 378 | { 379 | 380 | $this->log_write("Error: Error occurred while ".$string.".\n"); 381 | 382 | return FALSE; 383 | 384 | } 385 | 386 | function log_write($message) 387 | 388 | { 389 | 390 | $this->smtp_debug($message); 391 | 392 | if ($this->log_file == "") { 393 | 394 | return TRUE; 395 | 396 | } 397 | 398 | $message = date("M d H:i:s ").get_current_user()."[".getmypid()."]: ".$message; 399 | 400 | if (!@file_exists($this->log_file) || !($fp = @fopen($this->log_file, "a"))) { 401 | 402 | $this->smtp_debug("Warning: Cannot open log file \"".$this->log_file."\"\n"); 403 | 404 | return FALSE;; 405 | 406 | } 407 | 408 | flock($fp, LOCK_EX); 409 | 410 | fputs($fp, $message); 411 | 412 | fclose($fp); 413 | 414 | 415 | return TRUE; 416 | 417 | } 418 | 419 | 420 | function strip_comment($address) 421 | 422 | { 423 | 424 | $comment = "\([^()]*\)"; 425 | 426 | while (preg_match('/'.$comment.'/', $address)) { 427 | 428 | $address = preg_replace($comment, "", $address); 429 | 430 | } 431 | 432 | 433 | return $address; 434 | 435 | } 436 | 437 | 438 | function get_address($address) 439 | 440 | { 441 | 442 | $address = preg_replace("/([ \t\r\n])+/", "", $address); 443 | 444 | $address = preg_replace("/^.*<(.+)>.*$/", "\1", $address); 445 | 446 | return $address; 447 | 448 | } 449 | 450 | function smtp_debug($message) 451 | 452 | { 453 | 454 | if ($this->debug) { 455 | 456 | echo $message; 457 | 458 | } 459 | 460 | } 461 | 462 | } 463 | 464 | ?> -------------------------------------------------------------------------------- /dht/sendEmail.php: -------------------------------------------------------------------------------- 1 | sendmail($smtpemailto, $smtpemailfrom, $emailsubject, $emailbody, $emailtype); 22 | return $rs; 23 | } 24 | 25 | ?> -------------------------------------------------------------------------------- /dht/sqlUtil.php: -------------------------------------------------------------------------------- 1 | $DB_HOST, 12 | 'user' => $DB_USER, 13 | 'password' => $DB_PASSWORD, 14 | 'database' => $DB_DATABASE, 15 | 'timeout' => '1000' 16 | ); 17 | $sql="INSERT INTO infohash_table (infohash) VALUES('$value')"; 18 | 19 | $db->connect($server, function ($db, $result) { 20 | global $sql; 21 | $db->query($sql , function (Swoole\MySQL $db, $result) { 22 | if ($result === false) { 23 | var_dump($db->error, $db->errno); 24 | } 25 | $db->close(); 26 | }); 27 | }); 28 | } 29 | 30 | 31 | 32 | //获取一个sql链接 33 | /** 34 | * @return mysqli 35 | */ 36 | /*function getMysqlConn(){ 37 | 38 | try{ 39 | $conn=mysqli_connect($DB_HOST,$DB_USER ,$DB_PASSWORD,$DB_DATABASE); 40 | 41 | }catch(Exception $e){ 42 | die("Connection failed:".mysqli_connect_error()); 43 | } 44 | return $conn; 45 | } 46 | //插入数据到infohash表中 47 | function insert($conn,$value){ 48 | 49 | $sql="INSERT INTO infohash_table (infohash) VALUES('$value')"; 50 | try{ 51 | mysqli_query($conn,$sql); 52 | 53 | }catch(Exception $e){ 54 | die("Insert failed!"); 55 | } 56 | }*/ 57 | 58 | 59 | -------------------------------------------------------------------------------- /dht_readme.md: -------------------------------------------------------------------------------- 1 | # dht协议解析 2 | 3 | BitTorrent使用“DHT(分布式散列表)”来存储“无追踪器”种子的对等联系信息。 实际上,每个对等体成为跟踪器。 该协议基于Kademila,并通过UDP实现。 4 | 5 | 请注意本文档中使用的术语,以避免混淆。 “peer(对等体)”是在实现BitTorrent协议的TCP端口上侦听的客户端/服务器。 “node(节点)”是在实现DHT(分布式哈希表)协议的UDP端口上侦听的客户端/服务器。 DHT由node组成,存储peer的位置。 BitTorrent客户端包括一个DHT节点,用于与DHT中的其他节点联系,以便使用BitTorrent协议获取peer的位置。 6 | 7 | ## 1.概述 8 | 9 | 每个节点具有称为“节点ID”的全局唯一标识符。从与BitTorrent infohashes相同的160位空间中随机选择节点ID。 “距离度量”用于比较两个节点ID或节点ID和“亲密度”的信息。节点必须维护包含少量其他节点的联系信息的路由表。随着ID越来越接近节点本身的ID,路由表变得更加详细。节点知道DHT中的许多其他节点具有与其自身“接近”的ID,但只有少数与自己的ID相距较远的联系人。 10 | 11 | 在Kademlia中,距离度量是XOR,结果被解释为无符号整数。距离(A,B)= | A xor B |价值越小越靠近。 12 | 13 | 当一个节点想要找到一个torrent的对等体时,它使用距离度量来比较torrent的信息和它自己的路由表中节点的ID。然后,它将其知道的节点与最接近infohash的ID联系起来,并询问他们当前正在下载洪流的对等体的联系信息。如果联系的节点知道torrent的对等体,则返回对等联系人信息的响应。否则,所联系的节点必须使用最接近于洪流信息的路由表中的节点的联系信息进行响应。原始节点迭代地查询更接近目标信息的节点,直到找不到更靠近的节点。搜索结束后,客户端将自己的对等联系人信息插入到最接近洪流信息的ID的响应节点上。 14 | 15 | 对等体查询的返回值包括称为“令牌”的不透明值。对于一个节点宣布其控制对等体正在下载一个洪流,它必须在最近的对等体查询中呈现从相同查询节点接收到的令牌。当节点尝试“宣告”一个洪流时,查询的节点会根据查询节点的IP地址检查令牌。这是为了防止恶意主机注册其他主机的洪流。由于令牌仅由查询节点返回给相同的节点,所以从它接收到令牌,实现没有被定义。令牌在分发后必须接受合理的时间。 BitTorrent实现使用连接在一个秘密上的IP地址的SHA1散列,每五分钟更改一次,并接受十分钟以上的令牌。 16 | 17 | ## 2.Routing Table(路由表) 18 | 19 | 每个节点维护已知好的节点的路由表。路由表中的节点用作DHT中查询的起点。响应来自其他节点的查询返回路由表中的节点。 20 | 21 | 不是我们学到的所有节点都是相等的。有些是“好”,有些不是。使用DHT的许多节点能够发送查询和接收响应,但是不能响应来自其他节点的查询。重要的是每个节点的路由表必须只包含已知的好的节点。一个好的节点是一个节点在最近15分钟内响应了我们的一个查询。如果一个节点对我们的一个查询作出了回应,并在最近15分钟内向我们发送了一个查询,那么一个节点也是很好的。 15分钟不活动后,一个节点变得有问题。当节点无法响应多行查询时,节点变得不好。我们知道的节点优先级高于具有未知状态的节点。 22 | 23 | 路由表涵盖从0到2160的整个节点ID空间。路由表被细分为“桶”,每个都包含一部分空间。一个空表有一个桶的ID空间范围为min = 0,max = 2160。当ID为“N”的节点被插入到表中时,它被放置在最小<= N <最大一个空表只有一个桶,所以任何节点都必须在其中。每个桶只能容纳K个节点,目前为8个,才能成为“满”。当桶已满的已知好的节点时,除非我们自己的节点ID落在桶的范围内,否则不再添加节点。在这种情况下,桶被替换为两个新的桶,每个桶具有旧桶的一半范围,并且来自旧桶的节点在两个新桶之间分配。对于只有一个桶的新表,全桶将始终分为两个新的桶,覆盖范围为0..2159和2159..2160。 24 | 25 | 当桶中充满了好的节点时,新节点被简单地丢弃。如果桶中的任何节点已知会变坏,则新节点将被替换。如果在最近15分钟内没有看到桶中有可疑的节点,则最近看到的最少节点被ping通。如果pinged节点响应,则下一个最近看到的可疑节点被ping通,直到一个响应失败或者桶中的所有节点被认为是好的。如果桶中的某个节点无法响应ping,建议再尝试一次,然后再丢弃该节点并将其替换为新的优良节点。以这种方式,表填满稳定的长运行节点。 26 | 27 | 每个桶应保持“最后更改”属性,以指示内容“新鲜”。当一个桶中的一个节点被ping通并响应时,或者一个节点被添加到一个桶中,或者一个桶中的一个节点被另一个节点替换时,应该更新桶的最后一个改变的属性。在15分钟内未更改的料桶应“刷新”。这是通过在桶的范围内选择一个随机ID并在其上执行find_nodes搜索来完成的。能够从其他节点接收查询的节点通常不需要经常刷新桶。不能从其他节点接收查询的节点通常需要定期刷新所有存储桶,以确保在需要DHT时,表中有好的节点。 28 | 29 | 在将第一个节点插入到它的路由表中,并且在此之后启动时,节点应该尝试在DHT中找到最接近的节点给自身。它通过向更近和更靠近的节点发出find_node消息,直到找不到更近的节点。路由表应保存在客户端软件的调用之间。 30 | 31 | ## 3.BitTorrent协议扩展 32 | 33 | BitTorrent协议已被扩展,以便在跟踪器引入的对等体之间交换节点UDP端口号。以这种方式,客户端可以通过下载常规的种子来自动种植他们的路由表。尝试在第一次尝试下载无追踪器的洪流的新安装的客户端将不会在其路由表中具有任何节点,并且需要包含在torrent文件中的联系人。 34 | 35 | 支持DHT的对等体设置BitTorrent协议握手中交换的8字节保留标志的最后一位。对等体接收握手指示远程对等体支持DHT应发送PORT消息。它以字节0x09开始,并且具有以网络字节顺序包含DHT节点的UDP端口的两字节有效载荷。接收该消息的对等体应尝试对接收的端口上的节点和远程对等体的IP地址进行ping。如果接收到ping的响应,则节点应根据通常的规则尝试将新的联系人信息插入到它们的路由表中。 36 | 37 | ## 4.Torrent文件扩展名 38 | 39 | 无追踪器的洪流字典没有“公告”键。相反,无追踪器的洪流具有“节点”键。这个密钥应该设置为BT中生成客户端路由表的K个最接近的节点。或者,密钥可以被设置为已知的良好节点,例如由生成洪流的人操作的节点。请勿自动将“router.bittorrent.com”添加到torrent文件,或者自动将此节点添加到客户端路由表中。 40 | ``` 41 | nodes = [[“”,],[“”,],...] 42 | nodes = [[“127.0.0.1”,6881],[“your.router.node”,4804]] 43 | ``` 44 | 45 | ## 5.KRPC协议 46 | 47 | KRPC协议是一种简单的RPC机制,由通过UDP发送的贝克罗斯数字字典组成。发送单个查询分组,响应发送单个分组。没有重试。有三种消息类型:查询,响应和错误。对于DHT协议,有四个查询:ping,find_node,get_peers和announce_peer。 48 | 49 | KRPC消息是具有每个消息通用的两个密钥和附加密钥的单个字典,这取决于消息的类型。每个消息都有一个带有表示交易ID的字符串值的键“t”。此事务ID由查询节点生成,并在响应中回显,因此响应可能与多个查询相关联到同一个节点。交易ID应该编码为一个二进制数字的短串,通常2个字符就足够了,因为它们涵盖2 ^ 16个未完成的查询。每个KRPC消息中包含的另一个密钥是“y”,单个字符值描述消息的类型。 “y”键的值是查询的“q”,响应的“r”,错误的“e”。 50 | 51 | ### 联系编码 52 | 对等体的联系信息被编码为6字节的字符串。也称为“紧凑型IP地址/端口信息”,4字节IP地址是网络字节顺序,网络字节顺序中的2字节端口连接到最后。 53 | 54 | 节点的联系信息被编码为26字节的字符串。也称为“紧凑节点信息”,网络字节顺序中的20字节节点ID具有紧凑的IP地址/端口信息连接到最后。 55 | 56 | ### 查询 57 | 查询或“y”值为“q”的KRPC消息字典包含两个附加键; “q”和“a”。键“q”具有包含查询的方法名称的字符串值。密钥“a”具有包含查询的命名参数的字典值。 58 | 59 | ### 回应 60 | 响应或“y”值为“r”的KRPC消息字典包含一个附加键“r”。值“r”是包含命名返回值的字典。响应消息在成功完成查询后发送。 61 | 62 | ### 错误 63 | 错误或“y”值为“e”的KRPC消息字典包含一个附加键“e”。 “e”的值是一个列表。 第一个元素是表示错误代码的整数。 第二个元素是包含错误消息的字符串。 无法履行查询时发送错误。 下表描述了可能的错误代码: 64 | ``` 65 | Code Description 66 | 201 Generic Error 67 | 202 Server Error 68 | 203 Protocol Error, such as a malformed packet, invalid arguments, or bad token 69 | 204 Method Unknown 70 | ``` 71 | 示例错误包: 72 | ``` 73 | generic error = {"t":"aa", "y":"e", "e":[201, "A Generic Error Ocurred"]} 74 | bencoded = d1:eli201e23:A Generic Error Ocurrede1:t2:aa1:y1:ee 75 | ``` 76 | 77 | ## 6.DHT查询 78 | 79 | 所有查询都有一个“id”键和包含查询节点的节点ID的值。所有响应都具有“id”键和包含响应节点的节点ID的值。 80 | 81 | ### ping 82 | 83 | 最基本的查询是ping。 “q”=“ping”ping查询有一个参数,“id”值是一个20字节的字符串,包含网络字节顺序中的发件人节点ID。对ping的适当响应具有包含响应节点的节点ID的单个密钥“id”。 84 | ``` 85 | arguments: {"id" : ""} 86 | response: {"id" : ""} 87 | ``` 88 | 示例数据包 89 | ``` 90 | ping Query = {"t":"aa", "y":"q", "q":"ping", "a":{"id":"abcdefghij0123456789"}} 91 | bencoded = d1:ad2:id20:abcdefghij0123456789e1:q4:ping1:t2:aa1:y1:qe 92 | Response = {"t":"aa", "y":"r", "r": {"id":"mnopqrstuvwxyz123456"}} 93 | bencoded = d1:rd2:id20:mnopqrstuvwxyz123456e1:t2:aa1:y1:re 94 | ``` 95 | 96 | ### find_node 97 | 98 | 查找节点用于查找给定其ID的节点的联系人信息。 “q”==“find`_`node”find`_`node查询有两个参数,“id”包含查询节点的节点ID,“target”包含查询者寻求的节点的ID。当一个节点接收到一个find_node查询时,它应该响应一个关键字“nodes”和一个字符串的值,该字符串包含目标节点或其自身路由表中最接近的K(8)个好节点的紧凑节点信息。 99 | ``` 100 | arguments: {"id" : "", "target" : ""} 101 | response: {"id" : "", "nodes" : ""} 102 | ``` 103 | 示例数据包 104 | ``` 105 | find_node Query = {"t":"aa", "y":"q", "q":"find_node", "a": {"id":"abcdefghij0123456789", "target":"mnopqrstuvwxyz123456"}} 106 | bencoded = d1:ad2:id20:abcdefghij01234567896:target20:mnopqrstuvwxyz123456e1:q9:find_node1:t2:aa1:y1:qe 107 | Response = {"t":"aa", "y":"r", "r": {"id":"0123456789abcdefghij", "nodes": "def456..."}} 108 | bencoded = d1:rd2:id20:0123456789abcdefghij5:nodes9:def456...e1:t2:aa1:y1:re 109 | ``` 110 | 111 | ### get_peers 112 | 113 | 获取与torrent infohash相关联的对等体。 “q”=“get`_`peers”get`_`peers查询有两个参数,“id”包含查询节点的节点ID,“info`_`hash”包含torrent的infohash。如果查询节点具有infohash的对等体,则将其作为字符串列表以关键字“values”的形式返回。每个字符串包含单个对等体的“紧凑”格式对等体信息。如果所查询的节点没有infohash的对等体,则返回包含最接近查询中提供的infohash的查询节点路由表中的K个节点的密钥“nodes”。在任一情况下,返回值中也包含“令牌”键。令牌值是未来的announce`_`peer查询的必需参数。令牌值应该是一个短的二进制字符串。 114 | ``` 115 | arguments: {"id" : "", "info_hash" : "<20-byte infohash of target torrent>"} 116 | response: {"id" : "", "token" :"", "values" : ["", ""]} 117 | or: {"id" : "", "token" :"", "nodes" : ""} 118 | ``` 119 | 示例数据包: 120 | ``` 121 | get_peers Query = {"t":"aa", "y":"q", "q":"get_peers", "a": {"id":"abcdefghij0123456789", "info_hash":"mnopqrstuvwxyz123456"}} 122 | bencoded = d1:ad2:id20:abcdefghij01234567899:info_hash20:mnopqrstuvwxyz123456e1:q9:get_peers1:t2:aa1:y1:qe 123 | Response with peers = {"t":"aa", "y":"r", "r": {"id":"abcdefghij0123456789", "token":"aoeusnth", "values": ["axje.u", "idhtnm"]}} 124 | bencoded = d1:rd2:id20:abcdefghij01234567895:token8:aoeusnth6:valuesl6:axje.u6:idhtnmee1:t2:aa1:y1:re 125 | Response with closest nodes = {"t":"aa", "y":"r", "r": {"id":"abcdefghij0123456789", "token":"aoeusnth", "nodes": "def456..."}} 126 | bencoded = d1:rd2:id20:abcdefghij01234567895:nodes9:def456...5:token8:aoeusnthe1:t2:aa1:y1:re 127 | ``` 128 | ### announce_peer 129 | 130 | 宣布控制查询节点的对等体正在端口上下载一个洪流。 announce_peer有四个参数:包含查询节点的节点ID的“id”,包含torrent的infohash的“info_hash”,包含端口为整数的“port”,以及响应先前的get_peers查询收到的“token” 。查询节点必须验证令牌之前是否已发送到与查询节点相同的IP地址。然后查询节点应存储查询节点的IP地址和提供的端口号在其对等联系人信息存储下的infohash下。 131 | 132 | 有一个名为implied_port的可选参数,它的值为0或1.如果存在且非零,则端口参数应被忽略,UDP数据包的源端口应该用作对端的端口。这对于可能不知道其外部端口的NAT后面的对等体是有用的,并且支持uTP,它们接受与DHT端口相同的端口上的传入连接。 133 | ``` 134 | arguments: {"id" : "", 135 | "implied_port": <0 or 1>, 136 | "info_hash" : "<20-byte infohash of target torrent>", 137 | "port" : , 138 | "token" : ""} 139 | 140 | response: {"id" : ""} 141 | ``` 142 | 示例数据包: 143 | ``` 144 | announce_peers Query = {"t":"aa", "y":"q", "q":"announce_peer", "a": {"id":"abcdefghij0123456789", "implied_port": 1, "info_hash":"mnopqrstuvwxyz123456", "port": 6881, "token": "aoeusnth"}} 145 | bencoded = d1:ad2:id20:abcdefghij01234567899:info_hash20:
146 | mnopqrstuvwxyz1234564:porti6881e5:token8:aoeusnthe1:q13:announce_peer1:t2:aa1:y1:qe 147 | Response = {"t":"aa", "y":"r", "r": {"id":"mnopqrstuvwxyz123456"}} 148 | bencoded = d1:rd2:id20:mnopqrstuvwxyz123456e1:t2:aa1:y1:re 149 | ``` 150 | 151 | ### 原文英文版:Andrew Loewenstern , Arvid Norberg: DHT Protocol http://www.bittorrent.org/beps/bep_0005.html 152 | -------------------------------------------------------------------------------- /sql/sql_infohash.sql: -------------------------------------------------------------------------------- 1 | -- database phpspiderdht 2 | 3 | CREATE DATABASE phpspiderdht; 4 | 5 | -- use 6 | 7 | USE phpspiderdht; 8 | 9 | -- table infohash_table 10 | 11 | CREATE TABLE `infohash_table` ( 12 | `info_id` INT(10) UNSIGNED NOT NULL PRIMARY KEY, 13 | `infohash` CHAR(40) NOT NULL, 14 | `createtime` TIMESTAMP NOT NULL 15 | ) 16 | ENGINE = InnoDB 17 | DEFAULT CHARSET = utf8; 18 | 19 | -- AUTO_INCREMENT for table `infohash_table` 20 | 21 | ALTER TABLE `infohash_table` 22 | MODIFY `info_id` INT(10) UNSIGNED NOT NULL AUTO_INCREMENT, 23 | AUTO_INCREMENT = 1; 24 | 25 | -- mysql自动创建createtime 设置默认时间 CURRENT_TIMESTAMP 26 | 27 | ALTER TABLE `infohash_table` 28 | MODIFY COLUMN `createtime` TIMESTAMP DEFAULT CURRENT_TIMESTAMP; --------------------------------------------------------------------------------