设为首页 收藏本站
查看: 728|回复: 0

[经验分享] Python边学边用--BT客户端实现之(一)BitTorrent文件解析

[复制链接]

尚未签到

发表于 2015-4-23 05:31:43 | 显示全部楼层 |阅读模式
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文件的解码使用异常处理解决文件格式错误的情况,简化处理过程。


DSC0000.gif DSC0001.gif 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']
  

运维网声明 1、欢迎大家加入本站运维交流群:群②:261659950 群⑤:202807635 群⑦870801961 群⑧679858003
2、本站所有主题由该帖子作者发表,该帖子作者与运维网享有帖子相关版权
3、所有作品的著作权均归原作者享有,请您和我们一样尊重他人的著作权等合法权益。如果您对作品感到满意,请购买正版
4、禁止制作、复制、发布和传播具有反动、淫秽、色情、暴力、凶杀等内容的信息,一经发现立即删除。若您因此触犯法律,一切后果自负,我们对此不承担任何责任
5、所有资源均系网友上传或者通过网络收集,我们仅提供一个展示、介绍、观摩学习的平台,我们不对其内容的准确性、可靠性、正当性、安全性、合法性等负责,亦不承担任何法律责任
6、所有作品仅供您个人学习、研究或欣赏,不得用于商业或者其他用途,否则,一切后果均由您自己承担,我们对此不承担任何法律责任
7、如涉及侵犯版权等问题,请您及时通知我们,我们将立即采取措施予以解决
8、联系人Email:admin@iyunv.com 网址:www.yunweiku.com

所有资源均系网友上传或者通过网络收集,我们仅提供一个展示、介绍、观摩学习的平台,我们不对其承担任何法律责任,如涉及侵犯版权等问题,请您及时通知我们,我们将立即处理,联系人Email:kefu@iyunv.com,QQ:1061981298 本贴地址:https://www.yunweiku.com/thread-59675-1-1.html 上篇帖子: python 模拟登陆园子 下篇帖子: 转: Python WSGI fastcgi
您需要登录后才可以回帖 登录 | 立即注册

本版积分规则

扫码加入运维网微信交流群X

扫码加入运维网微信交流群

扫描二维码加入运维网微信交流群,最新一手资源尽在官方微信交流群!快快加入我们吧...

扫描微信二维码查看详情

客服E-mail:kefu@iyunv.com 客服QQ:1061981298


QQ群⑦:运维网交流群⑦ QQ群⑧:运维网交流群⑧ k8s群:运维网kubernetes交流群


提醒:禁止发布任何违反国家法律、法规的言论与图片等内容;本站内容均来自个人观点与网络等信息,非本站认同之观点.


本站大部分资源是网友从网上搜集分享而来,其版权均归原作者及其网站所有,我们尊重他人的合法权益,如有内容侵犯您的合法权益,请及时与我们联系进行核实删除!



合作伙伴: 青云cloud

快速回复 返回顶部 返回列表