Python边学边用--BT客户端实现之(一)BitTorrent文件解析

BitTorrent文件解析:

BitTorrent文件使用bencode编码,其中包括了4种数据类型:

'd' 开头表示是dict类型,'e'表示结束

'l' (小写字母L)开头表示是list类型,'e'表示结束

'i'开头表示是integer类型,'e'表示结束,可以表示负数

以数字开头表示string类型,数字为string长度,长度与string内容以':'分割

默认所有text类型的属性为utf-8编码,但是大多数BitTorrent包含codepage 和 encoding属性,指定了text的编码格式

"announce" -- tracker服务器的地址,为string
"info" ---文件信息,为dict类型
  "name" --单文件模式,表示文件名,多文件模式表示根目录名。
  "length" --单文件模式表示文件长度,多文件模式不存在
  "piece length" --文件分片大小
  "pieces" --为一个长string, 没20个字节表示一个分片的SHA1 hash值。按照文件分片的顺序排列。
        分片是按照所以文件组合在一起进行的,即一个分片可能会跨越多个文件。
  "files" -- 多文件模式存在,为一个文件列表,每个文件为一个dict类型
      "path" -- 文件目录列表,最后一项为文件名
      "length" --文件长度

"peace length"  --分片大小

以下为draft bep定义的属性
"code page"
"announce-list" --tracker列表,为二维数组,即将tracker服务器分为多个组
"encoding" -- Text属性的编码类型,string 类型,如 UTF-8
"publisher" -- 发布者
"publisher url" --发布者 URL
"creater" --创建者,如btcomet,btspirit
"creation date" --创建日期,为UTC格式,需要转化为本地时区可读格式
"commnent" --注释
"nodes" -- DHT 节点列表

BitTorrent的标准参见:http://www.bittorrent.org/beps/bep_0003.html

以下是自己写的Python实现,初学Python,代码写起来还都是C/C++风格,慢慢改进吧。

 修改代码,bittorrent文件的解码使用异常处理解决文件格式错误的情况,简化处理过程。

bcodec
  1 '''
  2 Created on 2012-9-30
  3 
  4 @author: ddt
  5 '''
  6 class DataEncodedError(BaseException):
  7     def __str__(self):
  8         return 'Data Encoded Error'
  9 
 10 class DataTypeError(BaseException):
 11     def __str__(self):
 12         return 'Data Type Error'
 13     
 14 def bdecode(data):
 15     try:
 16         leading_chr = data[0]
 17         #print leading_chr,                 
 18         if leading_chr.isdigit():
 19             chunk, length = _read_string(data)
 20             #print chunk
 21         elif leading_chr == 'd':
 22             chunk, length = _read_dict(data)
 23             #print chunk is None
 24         elif leading_chr == 'i':
 25             chunk, length = _read_integer(data)
 26             #print chunk
 27         elif leading_chr == 'l':
 28             chunk, length = _read_list(data)
 29         else:
 30             raise DataEncodedError()
 31         return chunk, length
 32     except:
 33         raise DataEncodedError()
 34     
 35                            
 36 def _read_dict(data):
 37     chunk = {} 
 38     length = 1
 39     
 40     while data[length] != 'e':
 41         key, key_len = bdecode(data[length:])
 42         length += key_len
 43         
 44         value, value_len = bdecode(data[length:])
 45         length += value_len
 46         
 47         chunk[key] = value
 48         #print key
 49         
 50     length += 1
 51     return chunk, length
 52 
 53 def _read_list(data):
 54     chunk = []
 55     length = 1
 56     while data[length] != 'e':
 57         value, value_len = bdecode(data[length:])
 58         chunk.append(value)
 59         length += value_len  
 60         
 61     length += 1
 62     return chunk, length
 63 
 64 def _read_string(data):
 65     comm_index = data.find(':')
 66     str_len = int(data[:comm_index])
 67     value = data[comm_index+1:comm_index+1+str_len]
 68     
 69     length = comm_index + 1 + str_len
 70     return ''.join(value), length
 71 
 72 def _read_integer(data):
 73 
 74     end_index = data.find('e')
 75     value = int(data[1:end_index])
 76     length = end_index + 1
 77     
 78     return  value, length
 79 
 80 def bencode(data):
 81     data_type = type(data)
 82     if data_type == type({}):
 83         result = _write_dict(data)
 84     elif data_type == type([]):
 85         result = _write_list(data)
 86     elif data_type == type(''):
 87         result = _write_string(data)
 88     elif data_type == type(int(0)):
 89         result = _write_integer(data)
 90     else:
 91         raise DataTypeError()
 92     return result
 93 
 94 def _write_dict(data):
 95     result = 'd'
 96     for key, value in data.items():
 97         key_encode = bencode(key)
 98         value_encode = bencode(value)
 99         result += key_encode
100         result += value_encode
101 
102     result += 'e'
103     return result
104 
105 def _write_list(data):
106     result = 'l'
107     for value in data:
108         value_encode = bencode(value)
109         result += value_encode
110         
111     result += 'e'
112     return result
113 
114 def _write_string(data):
115     return '%d:%s' %(len(data), data)
116 
117 def _write_integer(data):
118     return 'i%de' %data
119 
120     
torrent_file.py
  1 from datetime import datetime
  2 import bcodec
  3 import hashlib
  4 
  5 _READ_MAX_LEN = -1
  6 
  7 class BTFormatError(BaseException):
  8     def __str__(self):
  9         return 'Torrent File Format Error'
 10 
 11 class TorrentFile(object):
 12     
 13     def __init__(self):    
 14         self.__metainfo = {}
 15         self.__file_name = ''
 16         self.__bencode_data = None
 17     
 18     def read_file(self, filename):
 19         
 20         torrent_file = open(filename, 'rb')
 21         data = torrent_file.read(_READ_MAX_LEN)
 22         torrent_file.close()
 23         
 24         try:
 25             metainfo, length = bcodec.bdecode(data)
 26             self.__file_name = filename
 27             self.__metainfo = metainfo
 28             self.__bencode_data = data
 29         except:
 30             raise BTFormatError()
 31         
 32     def __is_singlefile(self):
 33         
 34         return self.__get_meta_info('length') != None
 35     
 36     def __decode_text(self, text):
 37         encoding = 'utf-8'
 38         resultstr = ''
 39         if self.get_encoding() != None:
 40             encoding = self.get_encoding()
 41         elif self.get_codepage() != None:
 42             encoding = 'cp' + str(self.get_codepage())
 43         if text:
 44             try:
 45                 resultstr = text.decode(encoding=encoding)
 46             except ValueError:
 47                 return text
 48         else:
 49             return None
 50         return resultstr
 51     
 52     def __get_meta_top(self, key):
 53         if key in self.__metainfo.keys():
 54             return self.__metainfo[key]
 55         else:
 56             return None
 57     def __get_meta_info(self,key):
 58         meta_info = self.__get_meta_top('info')
 59         if meta_info != None and key in meta_info.keys():
 60                 return meta_info[key]
 61         return None
 62     
 63     def get_codepage(self):
 64         return self.__get_meta_top('codepage')
 65     def get_encoding(self):
 66         return self.__get_meta_top('encoding')
 67     
 68     def get_announces(self):
 69         announces = self.__get_meta_top('announce-list')
 70         if announces != None:
 71             return announces
 72         
 73         announces = [[]]
 74         ann = self.__get_meta_top('announce')
 75         if ann:
 76             announces[0].append(ann)
 77         return announces
 78     
 79     def get_publisher(self):
 80         return self.__decode_text(self.__get_meta_top('publisher'))
 81     def get_publisher_url(self):
 82         return self.__decode_text(self.__get_meta_top('publisher-url'))
 83     
 84     def get_creater(self):
 85         return self.__decode_text(self.__get_meta_top('created by'))
 86     def get_creation_date(self):
 87         utc_date = self.__get_meta_top('creation date')
 88         if utc_date == None:
 89             return utc_date
 90         creationdate = datetime.utcfromtimestamp(utc_date)
 91         return creationdate
 92     def get_comment(self):
 93         return self.__get_meta_top('comment')
 94           
 95     def get_nodes(self):
 96         return self.__get_meta_top('nodes')
 97     
 98     def get_piece_length(self):
 99         return self.__get_meta_info('piece length')
100     
101     def get_piece(self, index):
102         pieces = self.__get_meta_info('pieces')
103         if pieces == None:
104             return None
105         
106         offset = index*20
107         if offset+20 > len(pieces):
108             return None
109         return pieces[offset:offset+20]
110     
111     def get_pieces_num(self):
112         return len(self.__get_meta_info('pieces'))/20
113         
114     def get_files(self):
115         
116         files = []
117         name = self.__decode_text(self.__get_meta_info('name'))
118         piece_length = self.get_piece_length()
119         if name == None:
120             return files
121         
122         if self.__is_singlefile():
123             file_name = name
124             file_length = self.__get_meta_info('length')
125             if not file_length:
126                 return files
127             
128             pieces_num = file_length/piece_length
129             last_piece_offset =  file_length % piece_length
130             if last_piece_offset != 0:
131                 pieces_num = int(pieces_num) + 1
132                 last_piece_offset -= 1
133             else:
134                 last_piece_offset = piece_length - 1
135 
136             first_piece_offset = 0
137             
138             files.append({'name':[file_name], 'length':file_length, 'first-piece':(0, first_piece_offset), 'last-piece':(pieces_num-1,last_piece_offset)})
139             return files
140         
141         folder = name
142         meta_files = self.__get_meta_info('files')
143         if meta_files == None:
144             return files
145         
146         total_length = int(0)
147         for one_file in self.__get_meta_info('files'):
148             
149             file_info = {}
150             path_list = []
151             path_list.append(folder)
152                         
153             if 'path' not in one_file.keys():
154                 break
155             for path in one_file['path']:
156                 path_list.append(self.__decode_text(path))
157             file_info['name'] = path_list
158             
159             if 'length' not in one_file.keys():
160                 break
161             
162             file_info['length'] =  one_file['length']
163             
164             piece_index = total_length / piece_length
165             first_piece_offset =  total_length % piece_length
166             
167             total_length += one_file['length']
168             pieces_num = total_length / piece_length - piece_index
169             last_piece_offset = total_length % piece_length
170             
171             if last_piece_offset != 0:
172                 pieces_num += 1
173                 last_piece_offset -= 1
174             else:
175                 last_piece_offset = piece_length - 1
176             
177             file_info['first-piece'] = (piece_index,first_piece_offset)
178             file_info['last-piece'] = ((piece_index+pieces_num-1),last_piece_offset)
179             files.append(file_info)
180         return files
181     
182     def get_info_hash(self):
183         info_index = self.__bencode_data.find('4:info')
184         info_data_index = info_index+len('4:info')
185         
186         info_value, info_data_len = bcodec.bdecode(self.__bencode_data[info_data_index:])
187         info_data = self.__bencode_data[info_data_index:info_data_index+info_data_len]
188         
189         info_hash = hashlib.sha1()
190         info_hash.update(info_data)
191         return info_hash.digest()
192 
193     
194 if __name__ == '__main__':
195     filename = r".\narodo.torrent"
196 
197     torrent = TorrentFile()
198 
199     print "begin to read file"
200     torrent.read_file(filename)
201 
202     print "end to read file"
203 
204     print "announces: " , torrent.get_announces() 
205     print "info_hash: ", list(torrent.get_info_hash())
206     print "peace length:", torrent.get_piece_length()
207     print "code page:" , torrent.get_codepage()
208     print "encoding:" , torrent.get_encoding()
209     print "publisher:" ,torrent.get_publisher()
210     print "publisher url:", torrent.get_publisher_url()
211     print "creater:" , torrent.get_creater()
212     print "creation date:", torrent.get_creation_date()
213     print "commnent:", torrent.get_comment()
214     print "nodes:", torrent.get_nodes()
215     torrent.get_files()
216     for one_file in torrent.get_files():
217         print 'name:', '\\'.join(one_file['name'])
218         print 'length:', one_file['length']
219         print 'first-piece:', one_file['first-piece']
220         print 'last-piece:', one_file['last-piece']

 

posted on 2012-09-29 23:17  duandetao  阅读(5146)  评论(2编辑  收藏  举报