這篇文章,我們來分析
RawServer
以及一些相關的類。
RawServer
類的實現代碼,在
BitTorrent
子目錄的
RawServer.py
中
RawServer
這個類的作用是實現一個網絡服務器。關于網絡編程的知識,《
unix
網絡編程:卷
1
》是最經典的書籍,你如果對這塊不了解,建議抽時間看看這本書。
RawServer
實現的是一種事件多路復用、非阻塞的網絡模型。它使用的是
poll()
(而不是我們常見的
select()
,關于
poll
和
select
的比較,也在《
unix
網絡編程:卷
1
》中有介紹)函數,處理過程大致是這樣的:
首先創建一個監聽
socket
,然后將這個
socket
加入
poll
的事件源;
隨后進入服務處理循環,即:
調用
poll()
函數,這個函數會阻塞,直到網絡上有某些事件發生或者超時才返回給調用者;
在
poll()
返回之后,先檢查一下是否有沒有處理的任務,如果有,那么先完成這些任務。然后根據事件類型進行處理。
如果是連接請求(監聽
socket
上的
POLLIN
事件)到來,它
accept
這個請求,如果
accept
成功,那么就和一個
client
建立了連接,于是將
accept()
新創建的
socket
加入
poll
的事件源;
如果在已經建立的連接上(連接
socket
上的
POLLIN
事件),有數據可讀,那么將數據從
client
端讀過來,做進一步處理;
如果已經建立的連接已經準備好(連接
socket
上的
POLLOUT
事件),可以發送數據,則檢查是否有數據需要發送,如果有,那么發送數據給
client
端。
(所以,
tracker
是一個單進程的服務器,并沒有用到線程。)
Bram Cohen
認為軟件的可維護性非常重要,使代碼易于維護的重要一條就是設計可重用的類,
RawServer
在設計的時候,充分考慮到了可重用性,集中表現在兩個地方:
1、
將網絡
I/O
和數據分析處理分離。
網絡服務器的事件多路復用、網絡
I/O
部分通常是固定不變的,而數據在讀取之后,進行分析處理的過程則是可變的。
RawServer
將可變的數據處理工作,交給另外一個抽象的類
Handler
(實際上并沒有這么一個類)來處理。比如,在
tracker
服務器的實現中,具體使用的就是
HTTPHandler
類,而在
以后將要分析的
BT client
實現代碼中,用到的具體的
Handler
是
Encoder
類。
2、
采用任務隊列來抽象出任務處理的過程。
RawServer
維護了一個任務隊列
unscheduled_tasks
(實際是一個二元組的
list
,二元組的第一項是一個函數,第二項是超時時間)。在初始化的時候,首先向這個隊列中加入一個任務:
scan_for_timeouts()
,這樣,每隔一段時間,服務器就會去檢查一下是否有連接超時。如果有其它
RawServer
的成員函數中,對外暴露的有:
u
__init__
:(初始化函數)
u
add_task()
:
在任務列表中增加一項任務(一個任務是一個函數以及一個指定的超時時間的組合)
u
bind()
:
首先創建一個
socket
,然后設置
socket
的屬性:
SO_REUSEADDR
和
IP_TOS,
,這兩個屬性的具體含義請參考《
unix
網絡編程:卷
1
》,另外還將
socket
設置為非阻塞的。相對于阻塞的
socket
來說,非阻塞的
socket
在網絡
I/O
性能上要提高許多,但是與此同時,編程的復雜度也要提高一些。象
tracker
這種可能同時要處理成千上萬個并發連接的服務器,只能采用非阻塞的
socket
。
然后將該
socket
和指定
ip
已經端口綁定;
最后把這個
socket
加入
poll
的事件源。
u
start_connection()
:
對外主動建立一個連接,這個函數在處理
NAT
穿越的時候用到了,我們后面分析到
NAT
穿越的時候,再具體講解。
u
listen_forever()
:
這個函數的功能就是實現了我在前面描述的網絡服務器的處理過程。我們看到,它唯一的參數是
handler
,
handler
的作用就是封裝了對數據的具體處理。
listen_forever()
把對網絡事件的處理過程,交給了
handle_events()
。
其它函數,包括
handle_events()
,都是內部函數(也就是外部不會直接來調用這些函數)。
Python
沒有
c++
那樣
public
、
protected
、
private
這樣的保護機制,
python
類的內部函數命名的慣例是以下劃線開始,例如
RawServer
中的
_close_dead()
等。
u
handle_events()
:
事件處理過程,主要是根據三種不同的網絡事件分別處理,一是連接事件,二是讀事件、三是寫事件。
if sock == self.server.fileno()
這段代碼判斷發生事件的
socket
是否是監聽
socket
,如果是,那么說明是連接事件。
連接事件的處理:
通過
accept
來接受連接,并將新建立的
socket
設置為非阻塞。
判斷當前連接數是否已經達到了最大值(為了限制并發連接的數目,在初始化
RawServer
的時候,需要指定最大連接數目),如果已經達到最大值,那么關閉這個新建的連接。
否則,根據新的
socket
創建一個
SingleSocket
對象,(
SingleSocket
封裝了對
socket
的操作。)將這個對象加入內部的列表
single_sockets
中,以備后用。
將這個新
socket
加入
poll
的事件源
最后,調用
Handler
的
external_connection_made()
函數,關于這個函數,在后面分析
HTTPHandler
時再討論。
if (event & POLLIN) != 0:
這段代碼判斷是否是讀事件
讀事件的處理:
首先刷新一下連接的最后更新時間
(
last_hit
)。
然后讀取數據;
如果什么也沒讀到,那么說明連接被關閉了(在網絡編程中,如果一個連接正常的被關閉,那么,也會觸發讀事件,只不過什么也讀不到)
否則,調用
Handler
的
data_came_in()
函數來處理讀到的數據。
if (event & POLLOUT) != 0 and s.socket is not None and not s.is_flushed():
這段代碼判斷是否是寫事件,而且確實有數據需要發送。在一個連接可以寫的時候,就會發生寫事件。
寫事件的處理:
實際代碼是在
SingleSocket
的
try_write()
函數中。
在一個非阻塞的連接上發送指定大小的數據,很可能在一次發送過程中,數據沒有被完全發送出去(只發送了一部分)就返回了,所以,每次
write
之后,必須判斷是否完全發送了數據。如果沒有發送完,那么下次有讀事件的時候,還得回來繼續發送未完得數據。這也是這個函數叫做
try_write
的原因吧。
try_write()
在最后,要重新設置
poll
的事件源。如果數據全部發送完畢了,那么只需要監聽讀事件(
POLLIN
)否則,既要監聽讀事件,也要監聽寫事件(
POLLOUT
),這樣,一旦連接變的可寫,可以繼續將剩下的數據發送出去。
u
scan_for_timeouts()
:
任務處理函數,它首先把自身加入未處理任務隊列中,這樣,經過一段時間,可以保證這個函數再次被調用,從而達到周期性調用的效果。
它檢查每個連接是否超過指定時間沒有被刷新,如果是,則該連接可能已經僵死,那么它關閉這個連接。
u
pop_unscheduled()
:
從任務列表中彈出一個未處理的任務。
與
RawServer
配合使用的是
SingleSocket
類,這是一個輔助類,主要目的是封裝對
socket
的處理吧。包括數據的發送,都交給它來處理了。這個類比較簡單,大家可以自己去看,我就不羅嗦了。
以上是對
RasServer
的具體實現的一個分析,可能讀者看的還是暈暈糊糊,沒辦法,還是必須自己去看源代碼,然后在遇到問題的時候,回頭再來看這篇文章,才會有幫助。如果不親自看源碼,終究是紙上談兵。
我們再來小結一下。
RawServer
封裝了網絡服務器的實現細節,它實現了一種事件多路處理、非阻塞的網絡模型。它主要負責建立新的連接,從網絡讀取和發送數據,而對讀到的數據的具體處理工作,交給
Handler
類來處理,從而把網絡
I/O
和數據處理分離開來,使得
RawServer
可以重用。
Handler
類是在調用
listen_forever()
的時候,由調用者傳遞進來的,具體到
tracker
服務器,就是
HTTPHandler
。有了
RawServer
,
tracker
就可以作為一個網絡服務器運行了。
下一節,我們開始分析具體實現
tracker HTTP
協議處理的
HTTPHandler
類和
Tracker
類。
posted on 2007-01-19 00:18
苦笑枯 閱讀(373)
評論(0) 編輯 收藏 所屬分類:
P2P