到目前為止,我們已經(jīng)學(xué)習(xí)了大量關(guān)于詩歌下載客戶端的Twisted的知識,接下來,我們使用Twisted重新實(shí)現(xiàn)我們的服務(wù)器端。得益于Twisted的抽象機(jī)制,接下來你會發(fā)現(xiàn)我們前面已經(jīng)幾乎學(xué)習(xí)到了所需的全部知識。其實(shí)現(xiàn)源碼在twisted-server-1/fastpoetry.py中。之所以稱其為fastpoetry是因?yàn)槠鋫鬏斣姼璨]有任何延遲。注意到,其代碼量比客戶端代碼少多了。
讓我們一部分一部分地來看服務(wù)端的實(shí)現(xiàn),首先是poetryProtocol:
class PoetryProtocol(Protocol):
def connectionMade(self):
self.transport.write(self.factory.poem)
self.transport.loseConnection()
如同客戶端的實(shí)現(xiàn),服務(wù)器端使用Protocol來管理連接(在這里,連接是由客戶端發(fā)起的)。這里的Protocol實(shí)現(xiàn)了我們的詩歌下載邏輯的服務(wù)器端。由于我們協(xié)議邏輯處理的是單向的,服務(wù)器端的Protocol只負(fù)責(zé)發(fā)送數(shù)據(jù)。如果你訪問服務(wù)器端,協(xié)議請求服務(wù)器在連接建立后立即發(fā)送詩歌,因此我實(shí)現(xiàn)了connectionMade方法,其會在Protocol創(chuàng)建一個連接后被激活執(zhí)行。
這個方法告訴Transport做兩件事:將整首詩歌發(fā)送出去然后關(guān)閉連接。當(dāng)然,這兩個動作都是同步操作。因此調(diào)用write函數(shù)也可以說成“一定要將整首詩歌發(fā)送到客戶端”,調(diào)用loseConnection意味著“一旦將要求下載的詩歌發(fā)送完畢就關(guān)掉這個連接”。
也許你看到了,Protocol是從Factory中獲得詩歌內(nèi)容的:
class PoetryFactory(ServerFactory):
protocol = PoetryProtocol
def __init__(self, poem):
self.poem = poem
這么簡單!除了創(chuàng)建PoetryProtocol, 工廠僅有的工作是存儲要發(fā)送的詩歌。
注意到我們繼承了ServerFactory而不是ClientFactory。這是因?yàn)榉?wù)器是要被動地監(jiān)聽連接狀態(tài)而不是像客戶端一樣去主動的創(chuàng)建。我們何以如此肯定呢?因?yàn)槲覀兪褂昧薼istenTCP方法,其描述文檔聲明factory參數(shù)必須是ServerFactory類型的。
我們在main函數(shù)中調(diào)用了listenTCP函數(shù):
def main():
options, poetry_file = parse_args()
poem = open(poetry_file).read()
factory = PoetryFactory(poem)
from twisted.internet import reactor
port = reactor.listenTCP(options.port or 0, factory,nterface=options.iface)
print 'Serving %s on %s.' % (poetry_file, port.getHost())
reactor.run()
其做了三件事:
剩下的工作就是reactor來運(yùn)轉(zhuǎn)事件循環(huán)了。你可以使用前面任何一個客戶端來測試這個服務(wù)器。
回憶下第五部分中的圖8與圖9.這兩張圖說明了Twisted建立一個連接后如何創(chuàng)建一個協(xié)議并初始化它的。其實(shí)對于Twisted在其監(jiān)聽的端口處監(jiān)聽到一個連接之后的整個處理機(jī)制也是如此。這也是為什么connectTCP與listenTCP都需要一個factory參數(shù)的原因。
我們在圖9中沒有展示的是,connectionMade也會在Protocol初始化的時候被調(diào)用。無論在哪兒都一樣(Dave是想說,connectionMade都會在Protocol初始化時被調(diào)用),只是我們在客戶端處沒有使用這個方法。并且我們在客戶端的portocal中實(shí)現(xiàn)的方法也沒有在服務(wù)器中用到。因此,如果我們有這個需要,可以創(chuàng)建一個共享的PoetryProtocol供客戶端與服務(wù)器端同時使用。這種方式在Twisted經(jīng)常見到。例如,NetstringReceiver protocol即能從連接中讀也能向連接中寫netstrings。
我們略去了從低層實(shí)現(xiàn)服務(wù)器端的工作,但我們可以來思考一下這里做了些什么。首先,調(diào)用listenTCP來告訴Twisted創(chuàng)建一個 listening socket 并將其添加到事件循環(huán)中。在 listening socket 有事件發(fā)生并不意味有數(shù)據(jù)要讀,而是說明有客戶端在等待連接自己。
Twisted會自動接受連接請求,并創(chuàng)建一個新的客戶端連接來連接客戶端與服務(wù)器(中間橋梁)。這個新的連接也要加入事件循環(huán)中,并且Twisted會為其創(chuàng)建了一個Transport和一個專門為這個連接服務(wù)的PoetryProtocol。因此,Protocol實(shí)例總是連接到client socket,而不是listening socket。
我們可以在圖26中形象地看到這一結(jié)果:
http://wiki.jikexueyuan.com/project/twisted-intro/images/p11_server-1.png" alt="" />
在圖中,有三個客戶端連接到服務(wù)器。每個Transport代表一個client socket,加上listening socket總共是四個被select循環(huán)監(jiān)聽的文件描述符(file descriptor).當(dāng)一個客戶端斷開與其相關(guān)的transport的連接時,對應(yīng)的PoetryProtocol也會被解引用并當(dāng)作垃圾被回收。而PoetryFactory只要我們還在監(jiān)聽新的連接就會一直不停地工作(即PoetryFactory不會隨著一個連接的斷開導(dǎo)致的PoetryProtocol的銷毀而銷毀)。
如果我們提供的詩歌很短的話,那么這些client socket與其相關(guān)的各種對象的生命期也就很短。但也有可能會是一臺相當(dāng)繁忙的服務(wù)器以至于同時有千百個客戶端同時請求較長的詩歌。那沒關(guān)系,因?yàn)門wisted并沒有連接建立的限制。當(dāng)然,當(dāng)下載量持續(xù)的增加,在某個結(jié)點(diǎn)處,你會發(fā)現(xiàn)已經(jīng)到達(dá)了OS的上限。對于那些高下載量的服務(wù)器,仔細(xì)的檢測與測試是每天都必須要做的工作。
并且Twisted在監(jiān)聽端口的數(shù)量上亦無限制。實(shí)際上,一個單一的Twisted線程可以監(jiān)聽數(shù)個端口并為其提供不同的服務(wù)(通過使用不同的factory作為listenTCP的參數(shù)即可)。如果經(jīng)過精心的設(shè)計(jì),甚至可以推遲到部署階段來決定"是使用一個Twisted進(jìn)程來提供多個服務(wù)還是使用多個Twisted進(jìn)程來實(shí)現(xiàn)?"。
我們這個版本的服務(wù)器有些功能是沒有的。首先,它無法產(chǎn)生任何日志來幫助我們調(diào)試和分析網(wǎng)絡(luò)出現(xiàn)的問題。其次,服務(wù)器也不是做為一個守護(hù)進(jìn)程來運(yùn)行的,按下ctrl+c或者退出登陸都會使其中止執(zhí)行。后面章節(jié)我們會解決這兩個問題,但是第十二部分我們先來完成另一個改進(jìn)版的服務(wù)器。
本部分原作參見: dave @ http://krondo.com/?p=2048
本部分翻譯內(nèi)容參見楊曉偉的博客 http://blog.sina.com.cn/s/blog_704b6af70100q97x.html