BT源代碼學習心得(十六):客戶端源代碼分析(實際數據的傳輸及其速率限制策略)

BT源代碼學習心得(十六):客戶端源代碼分析(實際數據的傳輸及其速率限制策略)

發信人: wolfenstein (NeverSayNever), 個人文集
標  題: BT源代碼學習心得(十六):客戶端源代碼分析(實際數據的傳輸及其速率限制策略)
發信站: 水木社區 (Fri Aug 19 12:01:16 2005), 文集
(本文包含HTML標記,終端模式下可能無法正確瀏覽)
    上一次分析了下載過程中如何進行下載某一塊的選取。這次分析在收到對方的下載請求
後程序的處理行爲。
    首先,仍然看Connection._got_message中收到請求消息的處理代碼,即elif t ==
REQUEST:後面的部分。首先檢查這個消息是否符合格式,它的長度必須是13(1個字節的消息
類型加上3個4字節整數,分別代表塊的位置,塊內偏移,請求長度),以及塊的位置必須小
於自己擁有的總塊數,然後由Upload.got_request進行處理。在Upload.got_request中,首
先檢查狀態,如果對方還沒有聲明interested就或者申請的長度大於自己的
max_slice_length,即一次能夠發送出去的最長的數據塊,那麼中斷連接,由此可見,在
BT通信協議中,要先聲明interested纔可以向對方請求數據。然後在自己的Connection沒有
發送choke時就可以發送數據了,但是這裏發送數據它並不是直接發送數據,而是把請求保
持在自己的buffer中,然後讓RateLimiter把自己的Connection加入到它的隊列中。
    RateLimiter,在Multitorrent中定義,作用是對全局的速度進行限制。由於BT通信協
議中,只有發送實際的數據會需要比較多的帶寬,因而也只有在這種情況下會需要用
RateLimiter來對其進行限制。現在我們可以注意到在每個Connection中還有一個
next_upload變量,它在其它地方都沒有用到,僅僅是在這裏,它的作用就是把若干個連接
通過這種方式組成一個鏈表。next_upload的類型是Connection,不是Upload,這裏要注意
。我們看到RateLimiter.queue函數中進行的就是數據結構中很常見的鏈表操作,其中
self.last指向了上一個Connection對象,插入新的Connection對象時,last會指向它。另
外如果原來隊列是空的話,那麼開始try_send,否則就不用做什麼,因爲try_send會檢查隊
列,逐個從中取出連接對象,並且發送數據。try_send中首先計算offset_amount,這個值
的意義就相當於可以發送多少字節,也就是一種“配額”,它的值小於0就可以繼續發送,
發送了一些字節後增加相應的字節,如果大於0,那麼就停下來,把發送的任務往後延一段
時間。其中如果check_time標誌爲真的話,那就清0,以前的時間不算,重新開始計算。配
額每次減少的字節數是上一次的時間(self.lasttime)和這次的時間差乘以upload_rate,這
也很好理解,隔了這麼些時間,又可以上傳若干字節了。下面的while循環就是在配額還有
的情況下,不斷調用send_partial函數進行數據的發送,然後發送完畢後,檢查該連接是否
已經暫時沒有發送需求了(即返回的實際發送的字節數是0或者連接還未刷新,即flushed),
如果該連接暫時沒有需求,則將其從鏈表中刪除。但是無論它還有沒有需求,接下來發送的
都是鏈表中的下一個元素。另外,在python中允許while循環後跟一個else語句,它被執行
的條件是循環正常結束,即因爲while的循環條件不滿足而結束循環,而當使用break來退出
循環時,這個else語句後面的內容是不會被執行的。在這裏,while的結束條件是配額用完
。那麼意味着還有數據要下載,那麼就等待一段新的時間繼續執行此任務,等待多久呢?它
等待的時間是剛好能把配額又降到0的時間。另外,由於直接執行可能會有一些延遲,因此
,這裏肯定可以保證下次運行時有上傳配額。另外這個while循環中唯一的break只有在發現
隊列已經清空的情況下被執行到。
    Connection.send_partial,負責實際發送數據。它有個參數bytes,指定了它最多隻能
發送這麼多數據。_partial_message是它維護的分塊消息變量,如果它不能一次把它發送出
去,就把它截斷,然後下次發送。首先看看它是否是空的,如果是,先從Upload處獲取一塊
代上傳的消息(get_upload_chunk),這個函數的做法是從自己的buffer(Upload.buffer,前
面提到,表示自己要上傳的請求,但是當時只是把自己的連接對象加到RateLimiter的隊列
中)中獲取一塊請求,然後讓StorageWrapper.get_piece去實際得按照要求把某一塊的某一
部分讀取出來,然後再更新一些速率統計對象的值,最後把這塊數據返回。回到
send_partial中,得到數據塊後,把_partial_message製造出來,做成可以直接往網絡上發
送的那種格式。下面檢查bytes,如果這次不讓發送這麼多數據,則只發送開始的部分,然
後截斷剩餘的部分。這樣下次調用該連接的send_partial時就會繼續發送剩下的數據。而如
果可以一次發送完,則在其隊列尾部增加上choke或者unchoke消息,這裏,我們看到,程序
保證了一部分(其實就是一個slice)如果要發送的話一定能發送完,即使阻塞控制器要求阻
塞某個連接,它也只能阻止發送完一部分後再繼續發送下一部分。
    好了,現在終於能夠收到實際的數據了,我們繼續來看Connection._got_message中的
elif t == PIECE:這一段。再次提醒,如果程序執行到這裏的話,收到的部分一定是完整的
,因爲每一條消息都是先發送了它的長度然後纔是它的內容,而如果只收到部分消息的話,
程序最多執行到Connection._read_messages。當收到對方的發送的數據塊後,先把開始的
兩個整數解出來,即第幾塊,塊內偏移多少(長度多少不用給出,因爲已經有數據塊的實際
內容),然後做一些基本檢查。檢查通過後,將其交給SingleDownload,
SingleDownload.got_piece會對其進行進一步處理。如果這個函數返回真值,意思就是說這
一塊已經完整了,因爲每一塊被分成了若干個slice進行下載,因此下載到一個slice不一定
能使一塊完整。而如果這一塊確實完整了,那麼給此Encoder的所有的正常Connection都發
出HAVE消息(send_have),意思就是通知所有和自己連接的對等客戶,我剛剛下到了某一塊
,以後你們要下載這一塊也可以來找我。
    現在來研究SingleDownload.got_piece,它的作用就是處理網絡上到來的實際數據。首
先,從自己的active_requests中試圖清除掉該數據對應的請求,如果發現自己根本就沒有
請求那些數據,就直接丟棄它們。然後進行endgame檢查以及更新一些速率測量器。接下來
要注意,StorageWrapper.piece_came_in會對數據進行檢查,如果它返回真並不是說明這一
塊數據下載完了,只是說明它沒有檢查出問題,而如果它返回假的值,那麼後果就很嚴重了
,說明這一塊數據有問題,整塊的數據都需要重新下載。這個if塊內的代碼做的工作就是重
新分配下載任務。要調用StorageWrapper.do_I_have後才知道這個部分(slice)下載完後是
不是整個的這一塊也完成了,如果是則再將這一信息通知
PiecePicker(PiecePicker.complete)。下載完後要進行一些檢查,確定下一步的下載策略
,這些在以下的代碼中可以看到。最後返回的值是自己是否已經下載完了這一塊。
    現在我們已經把BT的運作原理,即對等客戶之間是如何交換數據基本上分析完了,剩下
的未分析的部分代碼基本上可以自行閱讀。下一次將對整個學習心得做一些總結。 
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章