客户端源码分析之二:Storage类


所属类别:临时文章

文章作者:rstevens

特别推荐:免费发布信息 承包关键词~~抢爆了!HOT!


客户端源码分析之二: Storage 类作者:小马哥日期:2004-6-28由于 Storage 类比较简单,我直接在源码基础上进行注释。掌握Storage,为进一步分析 StorageWrapper 类打下基础。几点说明:1、 Storage 类封装了对磁盘文件的读和写的操作。2、 BT既支持单个文件的下载,也支持多个文件,包括可以有子目录。但是它并不是以文件为单位进行下载和上传的,而是以“文件片断”为单位。这可以在BT协议规范以及另一篇讲BT技术的文章中看到。所以,对于多个文件的情况,它也是当作一个拼接起来的“大文件”来处理的。例如,有文件 aaa和bbb,大小分别是 400和1000,那么它看作一个大小为 1400 的大文件,并以此来进行片断划分。3、 文件在下载过程中,同时提供上传,所以是以读写方式打开的,wb+和rb+都指的读写方式。在下载完毕之后,改为只读方式。4、 由于下载可能中断,所以在 Storage 初始化的时候,磁盘上可能已经存在文件的部分数据,必须检查一下文件的大小。为了便于描述,我们把完整文件的大小称为“实际长度”,把文件当前的大小成为“当前长度”。classStorage:# files 是一个二元组的列表(list),二元组包含了文件名称和长度,例如:[(“aaa”, 100), (“bbb”, 200)]def__init__(self, files, open, exists, getsize): self.ranges = []# 注意,这里是0l,后面的l表示类型是长整形,而不是01。 total = 0l so_far = 0l for file, length in files: if length != 0: # ranges 是一个三元组列表,三元组的格式是:在“整个”文件的起始位置、结束位置、文件名。BT在处理多个文件的时候,是把它们看作一个拼接起来的大文件。 self.ranges.append((total, total + length, file)) total += length# so_far 是实际存在的文件的总长度,好像没有起作用 if exists(file): l = getsize(file) if l > length: l = lengthso_far += l# 如果文件长度为0,则创建一个空文件 elif not exists(file): open(file, 'wb').close()# begins 是一个列表,用来保存每个文件的起始位置 self.begins = [i[0] for i in self.ranges] self.total_length = total self.handles = {} self.whandles = {} self.tops = {} # 对于每一个文件,,, for file, length in files:# 如果文件已经存在 if exists(file): l = getsize(file)# 如果文件长度不一致,说明还没有下载完全,则以读写(rb+)的方式打开文件。 if l != length:handles 是一个字典,用来保存所有被打开文件(无论是只读还是读写)的句柄whandles 是一个字典,用来记录对应文件是否是以写的方式打开(读写也是一种写)。 self.handles[file] = open(file, 'rb+')self.whandles[file] = 1 (这里是数字1,而不是字母l)#如果文件长度大于实际长度,那么应该是出错了,截断它。 if l > length: self.handles[file].truncate(length)如果文件长度和实际长度一致,那么下载已经完成,以只读方式打开。 else:self.handles[file] = open(file, 'rb')# tops 是一个字典,保存对应文件的“当前长度”。 self.tops[file] = l (这里是字母l,不是数字1)# 如果文件并不存在,那么以读写(w+)的方式打开 else: self.handles[file] = open(file, 'wb+') self.whandles[file] = 1# 判断起始位置为pos,长度为length的文件片断,在Storage 初始化之前,是否就已经存在于磁盘上了。这个函数后面分析StoageWrapper 类的时候会再提到。如果已经存在,那么返回true,否则为false。注意:如果这个片断的部分数据已经存在于磁盘上的话,那么也返回false。在分析StorageWrapper 的时候,才发现这里分析的不对。这个函数意思应该是:判断起始位置为pos,长度为length的文件片断,在Storage 初始化之前,是否已经在磁盘上分配了空间。例如,大小为1024k的文件,如果获得了第1个片断(从256k到512k),那么这时候,磁盘上文件的大小是512k(也就是分配了512k),尽管第0个片断(从0到256k)还没有获得,但磁盘上会保留这个“空洞”。 def was_preallocated(self, pos, length): for file, begin, end in self._intervals(pos, length): if self.tops.get(file, 0) < end: return False return True# 将所有原来以读写方式打开的文件,改成只读方式打开 def set_readonly(self): # may raise IOError or OSError for file in self.whandles.keys(): old = self.handles[file] old.flush() old.close() self.handles[file] = open(file, 'rb')# 获取所有文件的总长度 def get_total_length(self): return self.total_length这个函数意思是检查起始位置为pos,大小为amount 的片断实际位置在哪里?例如,假设有两个文件,aaa和bbb,大小分别是400 和1000,那么pos 为300,amount为200的文件片断属于哪个文件了?它分别属于两个文件,所以返回的是[(“aaa”, 300, 400), (“bbb”, 0, 100)],也就是它既包含了aaa 文件中从300 到400这段数据,也包含了bbb 文件从0 到100 这段数据。 def _intervals(self, pos, amount): r = [] # stop 是这个片断的结束位置。 stop = pos + amount# 通过这个函数,可以首先定位在哪个文件中,注意,可能在多个文件中(如果某个文件过小,那么,一段数据可能跨越几个文件)# 通过例子来解释下面这句,假设begins = [ 100, 200, 400, 1000],而pos = 250,那么bisect_right(self.begins, pos) 返回的是2,而p = bisect_right(self.begins, pos) C 1 就是1,这表示起始位置为250 的文件“片断”,它至少属于第1个文件(从0 开始算起),也就是起始为200的文件。p = bisect_right(self.begins, pos) C 1# r 是一个三元组的列表,三元组格式是(文件名,在该文件的起始位置,在该文件的结束位置)。whilep < len(self.ranges) and self.ranges[p][0] < stop: begin, end, file = self.ranges[p] r.append((file, max(pos, begin) - begin, min(end, stop) - begin)) p += 1 return r# 把从pos开始,amount长的数据从文件中读出来,转换成一个字符串defread(self, pos, amount): r = [] for file, pos, end in self._intervals(pos, amount): h = self.handles[file] h.seek(pos)r.append(h.read(end - pos)) # 把list 转换为一个字符串 return '.join(r)# 把一段字符串写到相应的磁盘文件中。 def write(self, pos, s): # might raise an IOError total = 0 for file, begin, end in self._intervals(pos, len(s)): # 如果该文件并不是以写的方式打开的,那么改成读写的方式打开 if not self.whandles.has_key(file): self.handles[file].close() self.handles[file] = open(file, 'rb+') self.whandles[file] = 1h = self.handles[file]# 通过seek 函数移动文件指针,可以看出来,文件不是按照顺序来写的,因为所获取的文件片断是随机的,所以写也是随机的。# 这里有一个疑问,假设获得了第二个文件片断,起始是1000,大小是500,而第一个片断还没有获得,那么文件指针要移动到1000 处,并写500个字节。这时候,文件的大小应该是1500,尽管前面1000 个字节是“空洞”。那么如果,直到结束,都没有获得第一个片断,又如何检测出来了?(通过检查total?) h.seek(begin) h.write(s[total: total + end - begin]) total += end - begin# 关闭所有打开文件defclose(self): for h in self.handles.values(): h.close()关闭本页

相关信息

· ASP基础教程:ASP内建对象Application和Session

· Oracle诊断事件

· 九招让你的硬盘更快、系统更稳

· Your Uninstaller 2008卸你没商量








....

108714 86606