解析完torrent文件之后,需要从Tracker服务器获取拥有每个piece的peer列表。
tracker服务器使用http协议提供支持,使用get的方式获取请求内容。也有使用udp协议的,暂时还没有分析。
get 格式如下:
announce-url?info_hash=xxxxxxxxxxxxxxxxxxxxx,peer_id=xxxxxxxxxxxxxxxxxxxx,ip=x.x.x.x,port=xxxx,uploaded=xx,downloaded=xx,left=xx,event=x
url中各参数需要经过url扰码处理。
其中,info_hash为torrent文件中info属性的value部分(bencode格式)的SHA1 hash,peer_id为任务启动时bt客户端为自己随即分配的20字节的ID, ip为本机外网ip,port为bt客户端监听的端口,uploaded,downloaded,left为本节点的下载属性。event为可选字段,包括 started,completed,stoped
tracker服务器的返回内容为经过bencode的dict类型,如果返回失败,会包含"failure reason" 键值,内容为可读的失败原因。
如果返回成功会包含以下几项内容:
"interval" --像tracker服务器发起常规查询的间隔时间(我理解为peers信息的刷新时间,自己需要定期向tracker报告下载状态,同时获取最新的peer信息)
"peers" --为一个peer list,每个peer包含以下几项内容:
"peer id" --20字节的peer自分配id
"ip" --peer ip地址
"port" --peer 监听端口
关于peers项,当前大多支持bep-23中第一的compact peers以减少返回内容的长度,该该格式下peers属性为一个string
每六个字节为一组,每组表示一个peer,其中前4个字节 表示peer ip,后两个字节表示peer port,均为大头模式表示。没有peer id,但是不影响后面的下载操作。
1 ''' 2 Created on 2012-10-2 3 4 @author: ddt 5 ''' 6 import httplib 7 import urllib 8 import bcodec 9 import re 10 import torrent_file 11 import threading 12 13 class TrackersInfo(object): 14 ''' 15 TODO: UDP tracker support 16 TODO: tracker polling support 17 ''' 18 19 __info_hash = '' 20 21 __peer_id = None 22 __host_ip = None 23 __host_port = None 24 25 __trackers = [] 26 __tracker_check_timeout = 0 #second 27 __tracker_get_timeout = 0 #second 28 __tracker_max_retrys = 0 29 30 def __init__(self, announce_list, host_info, info_hash): 31 ''' 32 Constructor 33 ''' 34 self.__info_hash = info_hash 35 self.__peer_id, self.__host_ip, self.__host_port = host_info 36 37 self.__tracker_check_timeout = 3 #second 38 self.__tracker_get_timeout = 10 #second 39 self.__tracker_max_retrys = 2 40 41 #init trackers 42 for tier in announce_list: 43 for announce in tier: 44 tracker = {} 45 tracker_addr = self.__get_tracker_addr(announce) 46 if tracker_addr == None: 47 continue 48 tracker['addr'] = tracker_addr 49 tracker['rsp'] = None 50 tracker['retrys'] = 0 51 tracker['error'] = '' 52 self.__trackers.append(tracker) 53 pass 54 55 def get_peers(self): 56 peers = [] 57 for tracker in self.__trackers: 58 rsp = tracker['rsp'] 59 if rsp != None: 60 for peer in rsp['peers']: 61 if peer not in peers: 62 peers.append(peer) 63 return peers 64 65 def refresh_trackers(self, download_state, refresh_intvl): 66 rsp_msg = None 67 thread_list = [] 68 69 for tracker in self.__trackers: 70 rsp = tracker['rsp'] 71 if rsp != None: 72 rsp['interval'] -= refresh_intvl 73 if rsp['interval'] > 0: 74 continue 75 if rsp['interval'] < 0: 76 rsp['interval'] = 0 77 78 if tracker['retrys'] < self.__tracker_max_retrys: 79 thread = threading.Thread(target=TrackersInfo.__request_tracker,args=(self,tracker,download_state)) 80 thread.start() 81 thread_list.append(thread) 82 #self.__request_tracker(tracker, download_state) 83 for thread in thread_list: 84 thread.join() 85 pass 86 87 def __request_tracker(self, tracker, download_state): 88 print "request_tracker: ", tracker['addr'] 89 90 web_addr,web_port,page_url = tracker['addr'] 91 tracker_con = httplib.HTTPConnection(web_addr, web_port,timeout=self.__tracker_get_timeout) 92 piece_request = self.__generate_request(download_state) 93 if not page_url: 94 page_url = '' 95 url = page_url + '?' + piece_request 96 print 'http://'+web_addr+url 97 try: 98 tracker_con.request("GET", url) 99 response = tracker_con.getresponse() 100 print response.status, response.reason 101 if response.reason.upper() != 'OK': 102 print 'Get tracker info error:%s! tracker:%s' %(response.reason, tracker['addr']) 103 tracker_con.close() 104 tracker['error'] = response.reason 105 tracker['retrys'] += 1 106 return 107 msg_encoded = response.read() 108 tracker_con.close() 109 rsp_msg = bcodec.bdecode(msg_encoded) 110 if rsp_msg == None: 111 print 'Get tracker info error:%s! tracker:%s' %(msg_encoded, tracker['addr']) 112 tracker['error'] = msg_encoded 113 tracker['retrys'] += 1 114 return 115 116 except Exception,e: 117 tracker_con.close() 118 print 'Get tracker info error:%s! tracker:%s' %(e.message, tracker['addr']) 119 tracker['error'] = e.message 120 tracker['retrys'] += 1 121 return 122 123 if 'failure reason' in rsp_msg.keys(): 124 print 'Get tracker info error:%s! tracker:%s' %(rsp_msg['failure reason'], tracker['addr']) 125 tracker['error'] = rsp_msg['failure reason'] 126 tracker['retrys'] += 1 127 return 128 print rsp_msg 129 peers_msg = rsp_msg['peers'] 130 peer_list = [] 131 if type(peers_msg) == type(''): 132 for i in range(0,len(peers_msg)-5,6): 133 one_peer = peers_msg[i:i+6] 134 #peer_id = '' 135 ip = one_peer[0:4] 136 port = one_peer[4:6] 137 ip = '%d.%d.%d.%d' %(ord(ip[0]),ord(ip[1]),ord(ip[2]),ord(ip[3]),) 138 port = ord(port[0])*256+ord(port[1]) 139 peer_list.append((ip,port)) 140 rsp_msg['peers'] = peer_list 141 print peer_list 142 elif type(peers_msg) == type([]): 143 for peer in peers_msg: 144 peer_list.append((peer['ip'],peer['port'])) 145 tracker['rsp'] = rsp_msg 146 tracker['retrys'] = 0 147 148 def __generate_request(self, download_state): 149 150 downloaded = download_state['downloaded'] 151 uploaded = download_state['uploaded'] 152 left = download_state['left'] 153 event = download_state['event'] 154 155 request = {} 156 request['info_hash'] = self.__info_hash 157 request['peer_id'] = self.__peer_id 158 request['ip'] = self.__host_ip 159 request['port'] = self.__host_port 160 request['uploaded'] = uploaded 161 request['downloaded'] = downloaded 162 request['left'] = left 163 request['event'] = event 164 request = urllib.urlencode(request) 165 return request 166 167 def __get_tracker_addr(self, announce): 168 tracker_addr = None 169 m = re.match(r'(http://)([^/,:]*)(:(\d*))?(/.*)?',announce) 170 if m != None: 171 web_addr = m.groups()[1] 172 web_port = m.groups()[3] 173 page_url = m.groups()[4] 174 else: 175 return None 176 177 if web_port != None: 178 web_port = int(web_port) 179 else: 180 web_port = 80 181 182 tracker_addr = (web_addr,web_port, page_url) 183 return tracker_addr 184 185 def __get_valid_tracker(self): 186 187 tracker_addr = None 188 189 if self.__annouce_list == None or len(self.__annouce_list) == 0: 190 return None 191 192 tracker_con = None 193 found_current = False 194 for tier in self.__annouce_list: 195 for announce in tier: 196 tracker_addr = self.__get_tracker_addr(announce) 197 if tracker_addr == None: 198 continue 199 200 (web_addr,web_port, page_url) = tracker_addr 201 202 tracker_con = httplib.HTTPConnection(web_addr, web_port,timeout=self.__tracker_check_timeout) 203 try: 204 tracker_con.connect() 205 except Exception, e: 206 print e 207 continue 208 209 tier.remove(announce) 210 tier.insert(0,announce) 211 tracker_con.close() 212 tracker_addr = (web_addr,web_port, page_url) 213 break 214 215 ## tiers sorting, none standard 216 if tracker_addr != None: 217 self.__annouce_list.remove(tier) 218 self.__annouce_list.insert(0,tier) 219 break 220 return tracker_addr 221 222 if __name__ == '__main__': 223 import down_load_task 224 filename = r".\narodo.torrent" 225 torrent = torrent_file.TorrentFile() 226 torrent.read_file(filename) 227 info_hash = (torrent.get_info_hash()) 228 print "info_hash: ", list(info_hash) 229 announce_list = torrent.get_announces() 230 peer_id = down_load_task._generate_peerid() 231 listening_addr = down_load_task._get_listening_addr() 232 host_info = (peer_id,) + listening_addr 233 print 'host_info: ', host_info 234 trackers = TrackersInfo(announce_list, host_info, info_hash) 235 download_state = {} 236 download_state['downloaded'] = 0 237 download_state['uploaded'] = 0 238 download_state['left'] = 364575 239 download_state['event'] = 'started' 240 trackers.refresh_trackers(download_state, 20) 241 peers = trackers.get_peers() 242 print peers
1 ''' 2 Created on 2012-10-2 3 4 @author: ddt 5 ''' 6 import httplib 7 import urllib 8 import bcodec 9 import re 10 11 class TrackerInfo(object): 12 ''' 13 TODO: multiple tracker support 14 TODO: UDP tracker support 15 TODO: tracker polling support 16 ''' 17 __announce_list = None 18 19 __piece_index = 0 20 21 __peer_id = None 22 __host_ip = None 23 __host_port = None 24 25 __tracker_addr = None 26 __tracker_rsp = None 27 28 __tracker_check_timeout = 0 #second 29 __tracker_get_timeout = 0 #second 30 31 def __init__(self, announce_list, host_info, piece_index): 32 ''' 33 Constructor 34 ''' 35 self.__annouce_list = announce_list 36 self.__piece_index = piece_index 37 self.__peer_id, self.__host_ip, self.__host_port = host_info 38 39 self.__tracker_check_timeout = 3 #second 40 self.__tracker_get_timeout = 10 #second 41 42 def refresh_tracker(self, piece_state, intvl): 43 rsp_msg = None 44 45 if self.__tracker_rsp != None: 46 self.__tracker_rsp['interval'] -= intvl 47 if self.__tracker_rsp['interval'] > 0: 48 return 49 else: 50 self.__tracker_rsp['interval'] = 0 51 52 53 if self.__tracker_addr == None: 54 self.__tracker_addr = self.__get_valid_tracker() 55 56 tracker_addr = self.__tracker_addr 57 58 while tracker_addr != None: 59 60 web_addr,web_port,page_url = self.__tracker_addr 61 62 tracker_con = httplib.HTTPConnection(web_addr, web_port,timeout=self.__tracker_get_timeout) 63 piece_request = self.__generate_request(piece_state) 64 if not page_url: 65 page_url = '' 66 url = page_url + '?' + piece_request 67 print 'http://'+web_addr+url 68 try: 69 tracker_con.connect() 70 tracker_con.request("GET", url) 71 response = tracker_con.getresponse() 72 print response.status, response.reason 73 if response.reason.upper() != 'OK': 74 print 'Get tracker info error:%s! piece_index:%d' %(response.reason, self.__piece_index) 75 tracker_con.close() 76 #tracker_addr = self.__get_valid_tracker(tracker_addr) 77 break 78 msg_encoded = response.read() 79 tracker_con.close() 80 rsp_msg = bcodec.bdecode(msg_encoded) 81 if rsp_msg == None: 82 print 'Get tracker info error:%s! piece_index:%d' %(msg_encoded, self.__piece_index) 83 #tracker_addr = self.__get_valid_tracker(tracker_addr) 84 break 85 86 except Exception,e: 87 tracker_con.close() 88 print 'Get tracker info error:%s! piece_index:%d' %(e.message, self.__piece_index) 89 #tracker_addr = self.__get_valid_tracker(tracker_addr) 90 break 91 92 if 'failure reason' in rsp_msg.keys(): 93 print 'Get tracker info error:%s! piece_index:%d' %(rsp_msg['failure reason'], self.__piece_index) 94 #tracker_addr = self.__get_valid_tracker(tracker_addr) 95 break 96 97 peers_msg = rsp_msg['peers'] 98 peer_list = [] 99 if type(peers_msg) == type(''): 100 for i in range(0,len(peers_msg)-5,6): 101 one_peer = peers_msg[i:i+6] 102 peer_id = '' 103 ip = one_peer[0:4] 104 port = one_peer[4:6] 105 ip = '%d.%d.%d.%d' %(ord(ip[0]),ord(ip[1]),ord(ip[2]),ord(ip[3]),) 106 port = ord(port[0])*255+ord(port[1]) 107 peer_list.append({'peer_id':peer_id,'ip':ip,'port':port}) 108 rsp_msg['peers'] = peer_list 109 self.__tracker_addr = tracker_addr 110 self.__tracker_rsp = rsp_msg 111 break 112 pass 113 114 def __generate_request(self, piece_state): 115 116 downloaded = piece_state['downloaded'] 117 uploaded = piece_state['uploaded'] 118 left = piece_state['left'] 119 event = piece_state['event'] 120 request = {} 121 request['info_hash'] = piece_state['info_hash'] 122 request['peer_id'] = self.__peer_id 123 request['ip'] = self.__host_ip 124 request['port'] = self.__host_port 125 request['uploaded'] = uploaded 126 request['downloaded'] = downloaded 127 request['left'] = left 128 request['event'] = event 129 request = urllib.urlencode(request) 130 return request 131 132 def __get_valid_tracker(self, old_tracker=None): 133 134 tracker_addr = None 135 136 if self.__annouce_list == None or len(self.__annouce_list) == 0: 137 return None 138 139 tracker_con = None 140 found_current = False 141 for tier in self.__annouce_list: 142 for announce in tier: 143 #print tracker_addr 144 m = re.match(r'(http://)([^/,:]*)(:(\d*))?(/.*)?',announce) 145 if m != None: 146 web_addr = m.groups()[1] 147 web_port = m.groups()[3] 148 page_url = m.groups()[4] 149 else: 150 continue 151 152 if web_port != None: 153 web_port = int(web_port) 154 else: 155 web_port = 80 156 157 if old_tracker != None: 158 if not found_current: 159 if old_tracker == (web_addr,web_port,page_url): 160 found_current = True 161 continue 162 163 tracker_con = httplib.HTTPConnection(web_addr, web_port,timeout=self.__tracker_check_timeout) 164 try: 165 tracker_con.connect() 166 except Exception, e: 167 print e 168 continue 169 170 tier.remove(announce) 171 tier.insert(0,announce) 172 tracker_con.close() 173 tracker_addr = (web_addr,web_port, page_url) 174 break 175 176 ## tiers sorting, none standard 177 if tracker_addr != None: 178 self.__annouce_list.remove(tier) 179 self.__annouce_list.insert(0,tier) 180 break 181 return tracker_addr