BT源代碼學習心得(五):統一網絡服務接口--RawServer

BT源代碼學習心得(五):統一網絡服務接口--RawServer

發信人: wolfenstein (NeverSayNever), 個人文集
標  題: BT源代碼學習心得(五):統一網絡服務接口--RawServer
發信站: 水木社區 (Fri Aug  5 18:54:08 2005), 文集
(本文包含HTML標記,終端模式下可能無法正確瀏覽)
    以後的部分都需要網絡服務(種子文件的生成在本地就可以完成,但是通過這些種子文
件下載實際的內容和提供跟蹤器服務都需要網絡),在BT的程序設計中,爲網絡服務提供了
統一的接口,這樣程序中的其它部分需要打開一個網絡服務時,只需要向這個接口進行註冊
,並提供相應的處理對象(handler)即可,當網絡事件發生時,將會自動這個處理對象中的
相關函數進行處理。
    這個統一網絡服務接口定義在BitTorrent/RawServer.py中,由它去實際調用和網絡插
口(socket)有關的庫,另外,RawServer還提供add_task功能,可以允許一些任務被延後執
行。
    RawServer在初始化的時候,可以從外部傳入一個doneflag參數,這是一個Event的數據
類型,可以從其它地方觸發它,這樣可以隨時中斷RawServer中的主循環(listen_forever中
的)。另外還進行一些內部變量的設置。最後,它給自己增加了一個任務,
scan_for_timeouts,這個任務會定時得檢查超時的網絡連接,並關閉它們。
    我們可以看到add_task的所做的工作就是將要延時執行的任務計算出它的實際執行時間
,並把它添加到一個排好序的列表中(funcs),且保持這個列表仍然處於有序狀態,這個列
表以實際執行時間爲順序。
    當其它模塊要提供網絡服務時,它首先調用RawServer的create_serversocket函數,這
個函數會返回一個socket對象,並且這個socket返回時,已經處於listen狀態了。當然,這
個時候如果真有外部的網絡連接進來,還是不會有什麼動作的,因爲相應的處理對象還沒有
註冊進來。
    接下來應該調用start_listening函數,這個函數的作用是把得到的網絡插口和它對應
的處理對象添加到一個字典中,該字典以網絡插口的描述符(FD)爲主鍵。值得注意的是,這
個函數名稱中雖然有listen字樣,但是socket.listen函數卻不是在這裏調用,而是在
create_serversocket就已經被調用了。傳遞進來的處理對象的類型沒有限制,唯一的要求
是它必須包含有external_connection_made函數,這樣當外部網絡連接到來時,這個函數就
會被調用。處理對象通常還應提供data_came_in函數來處理網絡數據,以及
connection_flushed函數來處理數據已經正式發出(相對於還在緩衝區的情況)時的處理,後
面兩個函數也可以不提供,因爲在external_connection_made函數裏,可以把新連接的網絡
數據處理對象重新定位到一個包含有data_came_in函數和connection_flushed函數的對象。
start_listening函數處理完後,該網絡插口就已經存在於serversockets字典中了。
    而當其它模塊要連接到外部網絡時,應該調用start_connection函數,這個函數將把網
絡插口添加到另一個字典single_sockets中,當然,使用了SingleSocket對象對其進行了一
定程度的包裝。從後面的分析可以看到,這個SingleSocket對象的主要功能是對輸出的數據
進行了一定的緩衝,並在不會阻塞的情況下把這些數據實際得寫到socket中。
start_connection需要傳入的處理對象是必須包含data_came_in而可以不包含
external_connection_made的對象。
    在start_listening和start_connection中都用到了poll對象,這是系統提供的一個提
供輪詢機制的模塊,使用文件描述符作爲參數,可以得到相應的事件(即該文件描述符對應
的插口有數據流入或者留出等),而在這兩個函數中,都調用了poll的註冊函數,方便後面
的poll輪詢操作。
    需要注意的是,在上面的這些函數被執行後,網絡連接還是不會被處理,因爲雖然打開
了相應的網絡插口,也註冊了相應的處理對象,但是整個的輪詢機制還沒有建立起來。直到
listen_forever函數被調用後,這個機制才真正得建立起來。這個函數的主體就是一個無限
的while循環,只有doneflag這個事件可以被用來中止這個循環。它首先做的事情是從添加
的任務funcs尋找最近要執行的任務的時間,並與當前時間相減,計算出period,然後用
poll輪詢這麼長的時間,這樣做就可以保證輪詢結束後不會耽誤外部任務過久。輪詢到的結
果返回在events裏,這是一個列表,它的元素是以文件描述符和事件所組成的二元組。接下
來就是根據時間的情況,把需要馬上執行的外部任務都執行了,_make_wrapped_call的主要
作用就是執行外部任務,只是給它們增加一些意外處理的保護代碼。執行完這些外部任務後
,調用_close_dead關閉不活躍的網絡連接,接下來就是使用_handle_events來處理前面的
poll蒐集到的網絡事件了。
    _handle_events的主體是一個for循環,檢查每一個sock和它對應的event。首先看它是
在serversockets字典中還是在single_sockets字典中,如果是前者,那麼這是一個偵聽中
的插口,再檢查網絡事件,如果不是出錯事件的話,那麼就說明是有外部連接到達,熟悉
socket編程的人都應該知道,這時正確的處理方式是建立一個新的socket,然後讓偵聽中的
插口去accept它,以後數據的讀寫應該在新的socket中進行。接下來的處理也是這樣,新的
socket被用SingleSocket包裝起來了,並且也被放到single_sockets字典中,因爲它和用
start_connection建立的socket一樣,都是有可能有數據流入的,而偵聽的插口只需要處理
網絡連接。接下來,前面註冊的處理對象中的external_connection_made函數被調用了,允
許進行一些其它的相關操作,我們注意到,這裏處理對象被原封不動得傳入到新的
SingleSocket中,當然實際上在external_connection_made函數中可以把SingleSocket的處
理對象重定向到其它對象中。
    接下來的else語句說明sock在single_sockets字典中,只有一種情況例外,就是
os.pipe。這種情況下不用處理這個事件,直接continue處理下一個事件即可。然後檢查事
件,如果是出錯則關閉該插口,否則就說明是有數據流動,而數據流動無非是流入和流出兩
種情況,如果是流入的話,就把數據讀到一個緩衝區裏,然後調用處理對象中提供的
data_came_in進行處理,而data_came_in得到的參數直接就是緩衝區中的數據,它不需要再
處理socket以及考慮可能會形成的阻塞等問題了。另外由於SingleSocket中對寫操作也進行
了包裝,即如果網絡有阻塞的可能,數據也會先寫入緩衝區,這樣data_came_in中就可以隨
便調用s.write了。最後如果是數據流出,則調用s.try_write,這個函數實現得也很安全。
最後檢查是否數據都已經真的發出去了(flushed),如果是,則調用處理對象中提供的
connection_flushed函數進行收尾工作。
    以後我們可以看到,在BT的實現中,創建了各種各樣的對象,而且這些對象之間有各種
各樣比較複雜的關係,但是所有的網絡服務,都是通過RawServer來進行的,再具體一些,
那就是RawServer這個對象只會被創建一個,而所有要求網絡服務的模塊都會把網絡服務的
處理對象註冊到這個RawServer中,方便統一管理。
    最後說一下,今天用google搜索發現原來去年就已經有人分析過BT的源代碼,不僅感嘆
自己孤陋寡聞,不過發現現在的版本(4.0.3)和當時的版本已經有了一些差別,而且我也可
以以我的閱讀源代碼的思路繼續前進,提供給大家一個不同的視角,因而決定把我的學習心
得繼續寫完,希望大家能夠支持。
 
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章