posts - 56,  comments - 12,  trackbacks - 0

          作者:小馬哥

          日期: 2004-6-28

           

          由于 Storage 類比較簡單,我直接在源碼基礎上進行注釋。掌握 Storage ,為進一步分析 StorageWrapper 類打下基礎。

           

          幾點說明:

          1、  Storage 類封裝了對磁盤文件的讀和寫的操作。

          2、  BT 既支持單個文件的下載,也支持多個文件,包括可以有子目錄。但是它并不是以文件為單位進行下載和上傳的,而是以“文件片斷”為單位。這可以在 BT 協議規范以及另一篇講 BT 技術的文章中看到。所以,對于多個文件的情況,它也是當作一個拼接起來的“大文件”來處理的。例如,有文件 aaa bbb ,大小分別是 400 1000 ,那么它看作一個大小為 1400 的大文件,并以此來進行片斷劃分。

          3、  文件在下載過程中,同時提供上傳,所以是以讀寫方式打開的, wb+ rb+ 都指的讀寫方式。在下載完畢之后,改為只讀方式。

          4、  由于下載可能中斷,所以在 Storage 初始化的時候,磁盤上可能已經存在文件的部分數據,必須檢查一下文件的大小。為了便于描述,我們把完整文件的大小稱為“實際長度”,把文件當前的大小成為“當前長度”。

           

           

          class Storage:

           

          # 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 = length

          so_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) – 1 就是 1 ,這表示起始位置為 250 的文件“片斷”,它至少屬于第 1 個文件(從 0 開始算起),也就是起始為 200 的文件。

           

          p = bisect_right(self.begins, pos) – 1

           

          # r 是一個三元組的列表,三元組格式是(文件名,在該文件的起始位置,在該文件的結束位置)。

                 

          while p < 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 長的數據從文件中讀出來,轉換成一個字符串

          def read (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] = 1

          h = self.handles[file]

           

          # 通過 seek 函數移動文件指針,可以看出來,文件不是按照順序來寫的,因為所獲取的文件片斷是隨機的,所以寫也是隨機的。

          # 這里有一個疑問,假設獲得了第二個文件片斷,起始是 1000 ,大小是 500 ,而第一個片斷還沒有獲得,那么文件指針要移動到 1000 處,并寫 500 個字節。這時候,文件的大小應該是 1500 ,盡管前面 1000 個字節是“空洞”。那么如果,直到結束,都沒有獲得第一個片斷,又如何檢測出來了?(通過檢查 total ?)

                      h.seek(begin)

                      h.write(s[total: total + end - begin])

                      total += end - begin

           

          # 關閉所有打開文件

          def close (self):

                  for h in self.handles.s():

                      h.close()
          posted on 2007-01-19 00:20 苦笑枯 閱讀(392) 評論(0)  編輯  收藏 所屬分類: P2P
          收藏來自互聯網,僅供學習。若有侵權,請與我聯系!

          <2007年1月>
          31123456
          78910111213
          14151617181920
          21222324252627
          28293031123
          45678910

          常用鏈接

          留言簿(2)

          隨筆分類(56)

          隨筆檔案(56)

          搜索

          •  

          最新評論

          閱讀排行榜

          評論排行榜

          主站蜘蛛池模板: 梧州市| 思南县| 全州县| 明溪县| 龙泉市| 大名县| 天祝| 六枝特区| 泗阳县| 凤山县| 皋兰县| 乌兰察布市| 揭东县| 措勤县| 五原县| 盱眙县| 连平县| 威信县| 宁远县| 松滋市| 深圳市| 进贤县| 平阴县| 铁力市| 娱乐| 通渭县| 深水埗区| 宁夏| 梁山县| 安新县| 汉川市| 宝坻区| 盐亭县| 滕州市| 苍溪县| 水富县| 沙湾县| 黑龙江省| 上高县| 潮安县| 水城县|