(上接十四)
然后,更新 sh 这个 sha 对象,注意,是根据片断 i 剩下的数据来更新的。关于 sha::update() 的功能,请看 python的帮助。如果有两段数据 a 和 b,那么
sh = sha(a)
sh.update(b),等效于
sh = sha(a+b)
所以,下面这个表达式等于
sh.update(self.storage.read(piece_size*i, self._piecelen(i)))
sh.update(self.storage.read(piece_size * i + lastlen, self._piecelen(i) - lastlen))
所以,这次计算出来的就是片断i 的摘要
(原来的困惑:为什么不直接计算 i 的摘要,要这么绕一下了?后来分析清楚“空间分配算法”之后,这后面一段代码也就没有什么问题了。)
s = sh.digest()
如果计算出来的摘要和 hashes 一致(后者是从 torrent 文件中获得的),那么,这个片断有效且已经存在于磁盘上。
if s == hashes:
markgot(i, i)
elif targets.get(s)
and self._piecelen(i) == self._piecelen(targets[s][-1]):
markgot(targets[s].pop(), i)
elif not self.have[len(hashes) - 1]
and sp == hashes[-1]
and (i == len(hashes) - 1 or not self._waspre(len(hashes) - 1)):
markgot(len(hashes) - 1, i)
else:
self.places = i
if flag.isSet():
return
numchecked += 1
statusfunc({'fractionDone': 1 - float(self.amount_left) / self.total_length})
# 如果所有片断都下载完了,那么结束。
if self.amount_left == 0:
finished()
# 检查某个片断,是否已经在磁盘上分配了空间,调用的是 Storage:: was_preallocated()
def _waspre(self, piece):
return self.storage.was_preallocated(piece * self.piece_size,
self._piecelen(piece))
# 获取指定片断的长度,只有最后一个片断大小可能小于 piece_size
def _piecelen(self, piece):
if piece < len(self.hashes) - 1:
return self.piece_size
else:
return self.total_length - piece * self.piece_size
# 返回剩余文件的大小
def get_amount_left(self):
return self.amount_left
# 判断是否已经获得了一些文件片断
def do_I_have_anything(self):
return self.amount_left < self.total_length
# 将指定片断切割为“子片断”
def _make_inactive(self, index):
# 先获取该片断的长度
length = min(self.piece_size, self.total_length - self.piece_size * index)
l = []
x = 0
# 为了获得更好的传输性能,BT把每个文件片断又分为更小的“子片断”,我们可以在 download.py 文件中 default 变量中,找到“子片断”大小的定义:
'download_slice_size', 2 ** 14, "How many bytes to query for per request."
这里定义的“子片断”大小是16k。
下面这个循环,就是将一个片断进一步切割为“子片断”的过程。
while x + self.request_size < length:
l.append((x, self.request_size))
x += self.request_size
l.append((x, length - x))
# 将 l 保存到 inactive_requests 这个列表中
self.inactive_requests[index] = l
# 是否处于 endgame 模式,关于endgame模式,参加《Incentives Build Robustness in BitTorrent》
def is_endgame(self):
return self.endgame
def get_have_list(self):
return self.have.tostring()
def do_I_have(self, index):
return self.have[index]
# 判断指定的片断,是否还有 request没有发出?如果有,那么返回 true,否则返回 false。
def do_I_have_requests(self, index):
return not not self.inactive_requests[index]
为指定片断创建一个 request 消息,返回的是一个二元组,例如(32k, 16k),表示“子片断”的起始位置是 32k ,大小是 16k。
def new_request(self, index):
# returns (begin, length)
# 如果还没有为该片断创建 request。,那么调用 _make_inactive() 创建 request列表。(inactive_requests[index] 初始化的值是1)
if self.inactive_requests[index] == 1:
self._make_inactive(index)
# numactive[index] 记录了已经为该片断发出了多少个 request。
self.numactive[index] += 1
rs = self.inactive_requests[index]
# 从 inactive_request 中移出最小的那个request(也就是起始位置最小)。
r = min(rs)
rs.remove(r)
# amount_inactive 记录了尚没有发出request的子片断总的大小。
self.amount_inactive -= r[1]
# 如果这是最后一个“子片断”,那么进入 endgame 模式
if self.amount_inactive == 0:
self.endgame = T.rue
# 返回这个 request
return r
def piece_came_in(self, index, begin, piece):
try:
return self._piece_came_in(index, begin, piece)
except IOError, e:
self.failed('IO Error ' + str(e))
return True
如果获得了某个“子片断”,那么调用这个函数。
index:“子片断”所在片断的索引号,
begin:“子片断”在片断中的起始位置,
piece:实际数据
def _piece_came_in(self, index, begin, piece):
# 如果之前没有获得过该片断中任何“子片断”,那么首先需要在磁盘上为整个片断分配空间。
空间分配的算法如下:
假设一共是6个片断,现在已经为 0、1、4三个片断分配了空间,那么
holes:[2, 3, 5]
places:{0:0, 1:1, 4:4}
现在要为片断5分配空间,思路是把片断5的空间暂时先分配在片断2应该在的空间上。这样分配以后,
holes:[3, 5]
places: {0:0, 1:1, 4:4, 5:2}
假设下一步为片断2分配空间,因为2的空间已经被5占用,所以把5的数据转移到3上,2才可以使用自己的空间。这样分配之后,
holes:[5]
places:{0:0, 1:1, 2:2, 4:4, 5:3}
最后,为3分配空间,因为3的空间被5占用,所以把5的数据转移到5自己的空间上,3就可以使用自己的空间了。这样分配之后,
holes:[]
places:{0:0, 1:1, 2:2, 3:3, 4:4, 5:5}
下面这段比较晦涩的代码,实现的就是这种空间分配算法。
if not self.places.has_key(index):
n = self.holes.pop(0)
if self.places.has_key(n):
oldpos = self.places[n]
old = self.storage.read(self.piece_size * oldpos, self._piecelen(n))
if self.have[n] and sha(old).digest() != self.hashes[n]:
self.failed('data corrupted on disk - maybe you have two copies running?')
return True
self.storage.write(self.piece_size * n, old)
self.places[n] = n
if index == oldpos or index in self.holes:
self.places[index] = oldpos
else:
for p, v in self.places.items():
if v == index:
break
self.places[index] = index
self.places[p] = oldpos
old = self.storage.read(self.piece_size * index, self.piece_size)
self.storage.write(self.piece_size * oldpos, old)
elif index in self.holes or index == n:
if not self._waspre(n):
self.storage.write(self.piece_size * n,
self._piecelen(n) * chr(0xFF))
self.places[index] = n
else:
for p, v in self.places.items():
if v == index:
break
self.places[index] = index
self.places[p] = n
old = self.storage.read(self.piece_size * index, self._piecelen(n))
self.storage.write(self.piece_size * n, old)
# 调用 Stoarge::write() 将这个子片断写入磁盘,注意是写到 places[index] 所在的空间上。
self.storage.write(self.places[index] * self.piece_size + begin, piece)
# 既然获得了一个子片断,那么发出的request个数显然要减少一个。
self.numactive[index] -= 1
# 如果既没有尚未发出的 request,而且也没有已发出的request(每当获得一个子片断,numactive[index]减少1,numactive[index]为0,说明所有发出的 request都已经接收到了响应的数据),那么显然整个片断已经全部获得了。
if not self.inactive_requests[index] and not self.numactive[index]:
检查整个片断的有效性,如果通过检查
if sha(self.storage.read(self.piece_size * self.places[index],
self._piecelen(index))).digest() == self.hashes[index]:
#“我”已经拥有了这个片断
self.have[index] = True
self.inactive_requests[index] = None
# 也检查过了有效性
self.waschecked[index] = True
self.amount_left -= self._piecelen(index)
if self.amount_left == 0:
self.finished()
如果没有通过有效性检查
else:
self.data_flunked(self._piecelen(index))
得丢弃这个片断
self.inactive_requests[index] = 1
self.amount_inactive += self._piecelen(index)
return False
return True
# 如果向某个 peer 发送的获取“子片断”的请求丢失了,那么调用此函数
def request_lost(self, index, begin, length):
self.inactive_requests[index].append((begin, length))
self.amount_inactive += length
self.numactive[index] -= 1
def get_piece(self, index, begin, length):
try:
return self._get_piece(index, begin, length)
except IOError, e:
self.failed('IO Error ' + str(e))
return None
def _get_piece(self, index, begin, length):
if not self.have[index]:
return None
if not self.waschecked[index]:
# 检查片断的 hash值,如果错误,返回 None
if sha(self.storage.read(self.piece_size * self.places[index],
self._piecelen(index))).digest() != self.hashes[index]:
self.failed('told file complete on start-up, but piece failed hash check')
return None
# 通过 hash 检查
self.waschecked[index] = True
# 检查一下“子片断”长度是否越界
if begin + length > self._piecelen(index):
return None
# 调用 Storage::read() ,将该“子片断”数据从磁盘上读出来,返回值就是这段数据。
return self.storage.read(self.piece_size * self.places[index] + begin, length)
然后,更新 sh 这个 sha 对象,注意,是根据片断 i 剩下的数据来更新的。关于 sha::update() 的功能,请看 python的帮助。如果有两段数据 a 和 b,那么
sh = sha(a)
sh.update(b),等效于
sh = sha(a+b)
所以,下面这个表达式等于
sh.update(self.storage.read(piece_size*i, self._piecelen(i)))
sh.update(self.storage.read(piece_size * i + lastlen, self._piecelen(i) - lastlen))
所以,这次计算出来的就是片断i 的摘要
(原来的困惑:为什么不直接计算 i 的摘要,要这么绕一下了?后来分析清楚“空间分配算法”之后,这后面一段代码也就没有什么问题了。)
s = sh.digest()
如果计算出来的摘要和 hashes 一致(后者是从 torrent 文件中获得的),那么,这个片断有效且已经存在于磁盘上。
if s == hashes:
markgot(i, i)
elif targets.get(s)
and self._piecelen(i) == self._piecelen(targets[s][-1]):
markgot(targets[s].pop(), i)
elif not self.have[len(hashes) - 1]
and sp == hashes[-1]
and (i == len(hashes) - 1 or not self._waspre(len(hashes) - 1)):
markgot(len(hashes) - 1, i)
else:
self.places = i
if flag.isSet():
return
numchecked += 1
statusfunc({'fractionDone': 1 - float(self.amount_left) / self.total_length})
# 如果所有片断都下载完了,那么结束。
if self.amount_left == 0:
finished()
# 检查某个片断,是否已经在磁盘上分配了空间,调用的是 Storage:: was_preallocated()
def _waspre(self, piece):
return self.storage.was_preallocated(piece * self.piece_size,
self._piecelen(piece))
# 获取指定片断的长度,只有最后一个片断大小可能小于 piece_size
def _piecelen(self, piece):
if piece < len(self.hashes) - 1:
return self.piece_size
else:
return self.total_length - piece * self.piece_size
# 返回剩余文件的大小
def get_amount_left(self):
return self.amount_left
# 判断是否已经获得了一些文件片断
def do_I_have_anything(self):
return self.amount_left < self.total_length
# 将指定片断切割为“子片断”
def _make_inactive(self, index):
# 先获取该片断的长度
length = min(self.piece_size, self.total_length - self.piece_size * index)
l = []
x = 0
# 为了获得更好的传输性能,BT把每个文件片断又分为更小的“子片断”,我们可以在 download.py 文件中 default 变量中,找到“子片断”大小的定义:
'download_slice_size', 2 ** 14, "How many bytes to query for per request."
这里定义的“子片断”大小是16k。
下面这个循环,就是将一个片断进一步切割为“子片断”的过程。
while x + self.request_size < length:
l.append((x, self.request_size))
x += self.request_size
l.append((x, length - x))
# 将 l 保存到 inactive_requests 这个列表中
self.inactive_requests[index] = l
# 是否处于 endgame 模式,关于endgame模式,参加《Incentives Build Robustness in BitTorrent》
def is_endgame(self):
return self.endgame
def get_have_list(self):
return self.have.tostring()
def do_I_have(self, index):
return self.have[index]
# 判断指定的片断,是否还有 request没有发出?如果有,那么返回 true,否则返回 false。
def do_I_have_requests(self, index):
return not not self.inactive_requests[index]
为指定片断创建一个 request 消息,返回的是一个二元组,例如(32k, 16k),表示“子片断”的起始位置是 32k ,大小是 16k。
def new_request(self, index):
# returns (begin, length)
# 如果还没有为该片断创建 request。,那么调用 _make_inactive() 创建 request列表。(inactive_requests[index] 初始化的值是1)
if self.inactive_requests[index] == 1:
self._make_inactive(index)
# numactive[index] 记录了已经为该片断发出了多少个 request。
self.numactive[index] += 1
rs = self.inactive_requests[index]
# 从 inactive_request 中移出最小的那个request(也就是起始位置最小)。
r = min(rs)
rs.remove(r)
# amount_inactive 记录了尚没有发出request的子片断总的大小。
self.amount_inactive -= r[1]
# 如果这是最后一个“子片断”,那么进入 endgame 模式
if self.amount_inactive == 0:
self.endgame = T.rue
# 返回这个 request
return r
def piece_came_in(self, index, begin, piece):
try:
return self._piece_came_in(index, begin, piece)
except IOError, e:
self.failed('IO Error ' + str(e))
return True
如果获得了某个“子片断”,那么调用这个函数。
index:“子片断”所在片断的索引号,
begin:“子片断”在片断中的起始位置,
piece:实际数据
def _piece_came_in(self, index, begin, piece):
# 如果之前没有获得过该片断中任何“子片断”,那么首先需要在磁盘上为整个片断分配空间。
空间分配的算法如下:
假设一共是6个片断,现在已经为 0、1、4三个片断分配了空间,那么
holes:[2, 3, 5]
places:{0:0, 1:1, 4:4}
现在要为片断5分配空间,思路是把片断5的空间暂时先分配在片断2应该在的空间上。这样分配以后,
holes:[3, 5]
places: {0:0, 1:1, 4:4, 5:2}
假设下一步为片断2分配空间,因为2的空间已经被5占用,所以把5的数据转移到3上,2才可以使用自己的空间。这样分配之后,
holes:[5]
places:{0:0, 1:1, 2:2, 4:4, 5:3}
最后,为3分配空间,因为3的空间被5占用,所以把5的数据转移到5自己的空间上,3就可以使用自己的空间了。这样分配之后,
holes:[]
places:{0:0, 1:1, 2:2, 3:3, 4:4, 5:5}
下面这段比较晦涩的代码,实现的就是这种空间分配算法。
if not self.places.has_key(index):
n = self.holes.pop(0)
if self.places.has_key(n):
oldpos = self.places[n]
old = self.storage.read(self.piece_size * oldpos, self._piecelen(n))
if self.have[n] and sha(old).digest() != self.hashes[n]:
self.failed('data corrupted on disk - maybe you have two copies running?')
return True
self.storage.write(self.piece_size * n, old)
self.places[n] = n
if index == oldpos or index in self.holes:
self.places[index] = oldpos
else:
for p, v in self.places.items():
if v == index:
break
self.places[index] = index
self.places[p] = oldpos
old = self.storage.read(self.piece_size * index, self.piece_size)
self.storage.write(self.piece_size * oldpos, old)
elif index in self.holes or index == n:
if not self._waspre(n):
self.storage.write(self.piece_size * n,
self._piecelen(n) * chr(0xFF))
self.places[index] = n
else:
for p, v in self.places.items():
if v == index:
break
self.places[index] = index
self.places[p] = n
old = self.storage.read(self.piece_size * index, self._piecelen(n))
self.storage.write(self.piece_size * n, old)
# 调用 Stoarge::write() 将这个子片断写入磁盘,注意是写到 places[index] 所在的空间上。
self.storage.write(self.places[index] * self.piece_size + begin, piece)
# 既然获得了一个子片断,那么发出的request个数显然要减少一个。
self.numactive[index] -= 1
# 如果既没有尚未发出的 request,而且也没有已发出的request(每当获得一个子片断,numactive[index]减少1,numactive[index]为0,说明所有发出的 request都已经接收到了响应的数据),那么显然整个片断已经全部获得了。
if not self.inactive_requests[index] and not self.numactive[index]:
检查整个片断的有效性,如果通过检查
if sha(self.storage.read(self.piece_size * self.places[index],
self._piecelen(index))).digest() == self.hashes[index]:
#“我”已经拥有了这个片断
self.have[index] = True
self.inactive_requests[index] = None
# 也检查过了有效性
self.waschecked[index] = True
self.amount_left -= self._piecelen(index)
if self.amount_left == 0:
self.finished()
如果没有通过有效性检查
else:
self.data_flunked(self._piecelen(index))
得丢弃这个片断
self.inactive_requests[index] = 1
self.amount_inactive += self._piecelen(index)
return False
return True
# 如果向某个 peer 发送的获取“子片断”的请求丢失了,那么调用此函数
def request_lost(self, index, begin, length):
self.inactive_requests[index].append((begin, length))
self.amount_inactive += length
self.numactive[index] -= 1
def get_piece(self, index, begin, length):
try:
return self._get_piece(index, begin, length)
except IOError, e:
self.failed('IO Error ' + str(e))
return None
def _get_piece(self, index, begin, length):
if not self.have[index]:
return None
if not self.waschecked[index]:
# 检查片断的 hash值,如果错误,返回 None
if sha(self.storage.read(self.piece_size * self.places[index],
self._piecelen(index))).digest() != self.hashes[index]:
self.failed('told file complete on start-up, but piece failed hash check')
return None
# 通过 hash 检查
self.waschecked[index] = True
# 检查一下“子片断”长度是否越界
if begin + length > self._piecelen(index):
return None
# 调用 Storage::read() ,将该“子片断”数据从磁盘上读出来,返回值就是这段数据。
return self.storage.read(self.piece_size * self.places[index] + begin, length)