[翻译]深入理解Tornado——一个异步web服务器
原文地址:http://golubenco.org/?p=16
這篇文章的目的在于對(duì)Tornado這個(gè)異步服務(wù)器軟件的底層進(jìn)行一番探索。我采用自底向上的方式進(jìn)行介紹,從輪訓(xùn)開(kāi)始,向上一直到應(yīng)用層,指出我認(rèn)為有趣的部分。
所以,如果你有打算要閱讀Tornado這個(gè)web框架的源碼,又或者是你對(duì)一個(gè)異步web服務(wù)器是如何工作的感興趣,我可以在這成為你的指導(dǎo)。
通過(guò)閱讀這篇文章,你將可以:
- 自己寫(xiě)一個(gè)Comet架構(gòu)程序的服務(wù)器端部分,即使你是從拷貝別人的代碼開(kāi)始。
- 如果你想在Tornado框架上做開(kāi)發(fā),通過(guò)這篇文章你將更好的理解Tornado web框架。
- 在Tornado和Twisted的爭(zhēng)論上,你將更有見(jiàn)解。
介紹
假設(shè)你還不知道Tornado是什么也不知道為什么應(yīng)該對(duì)它感興趣,那我將用簡(jiǎn)短的話來(lái)介紹Tornado這個(gè)項(xiàng)目。如果你已經(jīng)對(duì)它有了興趣,你可以跳去看下一節(jié)內(nèi)容。
Tornado是一個(gè)用Python編寫(xiě)的異步HTTP服務(wù)器,同時(shí)也是一個(gè)web開(kāi)發(fā)框架。該框架服務(wù)于FriendFeed網(wǎng)站,最近Facebook也在使用它。FriendFeed網(wǎng)站有用戶數(shù)多和應(yīng)用實(shí)時(shí)性強(qiáng)的特點(diǎn),所以性能和可擴(kuò)展性是很受重視的。由于現(xiàn)在它是開(kāi)源的了(這得歸功于Facebook),我們可以徹底的對(duì)它是如何工作的一探究竟。
我覺(jué)得對(duì)非阻塞式IO (nonblocking IO) 和異步IO (asynchronous IO ?AIO)很有必要談一談。如果你已經(jīng)完全知道他們是什么了,可以跳去看下一節(jié)。我盡可能的使用一些例子來(lái)說(shuō)明它們是什么。
讓我們假設(shè)你正在寫(xiě)一個(gè)需要請(qǐng)求一些來(lái)自其他服務(wù)器上的數(shù)據(jù)(比如數(shù)據(jù)庫(kù)服務(wù),再比如新浪微博的open api)的應(yīng)用程序,然后呢這些請(qǐng)求將花費(fèi)一個(gè)比較長(zhǎng)的時(shí)間,假設(shè)需要花費(fèi)5秒鐘。大多數(shù)的web開(kāi)發(fā)框架中處理請(qǐng)求的代碼大概長(zhǎng)這樣:
def?handler_request(self,?request):????answ?=?self.remote_server.query(request)?#?this?takes?5?seconds
????request.write_response(answ)
如果這些代碼運(yùn)行在單個(gè)線程中,你的服務(wù)器只能每5秒接收一個(gè)客戶端的請(qǐng)求。在這5秒鐘的時(shí)間里,服務(wù)器不能干其他任何事情,所以,你的服務(wù)效率是每秒0.2個(gè)請(qǐng)求,哦,這太糟糕了。?
當(dāng)然,沒(méi)人那么天真,大部分服務(wù)器會(huì)使用多線程技術(shù)來(lái)讓服務(wù)器一次接收多個(gè)客戶端的請(qǐng)求,我們假設(shè)你有20個(gè)線程,你將在性能上獲得20倍的提高,所以現(xiàn)在你的服務(wù)器效率是每秒接受4個(gè)請(qǐng)求,但這還是太低了,當(dāng)然,你可以通過(guò)不斷地提高線程的數(shù)量來(lái)解決這個(gè)問(wèn)題,但是,線程在內(nèi)存和調(diào)度方面的開(kāi)銷是昂貴的,我懷疑如果你使用這種提高線程數(shù)量的方式將永遠(yuǎn)不可能達(dá)到每秒100個(gè)請(qǐng)求的效率。
如果使用AIO,達(dá)到每秒上千個(gè)請(qǐng)求的效率是非常輕松的事情。服務(wù)器請(qǐng)求處理的代碼將被改成這樣:
def?handler_request(self,?request):????self.remote_server.query_async(request,?self.response_received)?????
def?response_received(self,?request,?answ):????#?this?is?called?5?seconds?later
????request.write(answ)
AIO的思想是當(dāng)我們?cè)诘却Y(jié)果的時(shí)候不阻塞,轉(zhuǎn)而我們給框架一個(gè)回調(diào)函數(shù)作為參數(shù),讓框架在有結(jié)果的時(shí)候通過(guò)回調(diào)函數(shù)通知我們。這樣,服務(wù)器就可以被解放去接受其他客戶端的請(qǐng)求了。
然而這也是AIO不太好的地方:代碼有點(diǎn)不直觀了。還有,如果你使用像Tornado這樣的單線程AIO服務(wù)器軟件,你需要時(shí)刻小心不要去阻塞什么,因?yàn)樗斜驹撛诋?dāng)前返回的請(qǐng)求都會(huì)像上述處理那樣被延遲返回。
關(guān)于異步IO,比當(dāng)前這篇過(guò)分簡(jiǎn)單的介紹更好的學(xué)習(xí)資料請(qǐng)看 The C10K problem。
源代碼
該項(xiàng)目由github托管,你可以通過(guò)如下命令獲得,雖然通過(guò)閱讀這篇文章你也可以不需要它是吧。
git?clone?git://github.com/facebook/tornado.git在tornado的子目錄中,每個(gè)模塊都應(yīng)該有一個(gè).py文件,你可以通過(guò)檢查他們來(lái)判斷你是否從已經(jīng)從代碼倉(cāng)庫(kù)中完整的遷出了項(xiàng)目。在每個(gè)源代碼的文件中,你都可以發(fā)現(xiàn)至少一個(gè)大段落的用來(lái)解釋該模塊的doc string,doc string中給出了一到兩個(gè)關(guān)于如何使用該模塊的例子。
IOLoop模塊
讓我們通過(guò)查看ioloop.py文件直接進(jìn)入服務(wù)器的核心。這個(gè)模塊是異步機(jī)制的核心。它包含了一系列已經(jīng)打開(kāi)的文件描述符(譯者:也就是文件指針)和每個(gè)描述符的處理器(handlers)。它的功能是選擇那些已經(jīng)準(zhǔn)備好讀寫(xiě)的文件描述符,然后調(diào)用它們各自的處理器(一種IO多路復(fù)用的實(shí)現(xiàn),其實(shí)就是socket眾多IO模型中的select模型,在Java中就是NIO,譯者注)。
可以通過(guò)調(diào)用add_handler()方法將一個(gè)socket加入IO循環(huán)中:
def?add_handler(self,?fd,?handler,?events):????"""Registers?the?given?handler?to?receive?the?given?events?for?fd."""
????self._handlers[fd]?=?handler
????self._impl.register(fd,?events?|?self.ERROR)
_handlers這個(gè)字典類型的變量保存著文件描述符(其實(shí)就是socket,譯者注)到當(dāng)該文件描述符準(zhǔn)備好時(shí)需要調(diào)用的方法的映射(在Tornado中,該方法被稱為處理器)。然后,文件描述符被注冊(cè)到epoll(unix中的一種IO輪詢機(jī)制,貌似,譯者注)列表中。Tornado關(guān)心三種類型的事件(指發(fā)生在文件描述上的事件,譯者注):READ,WRITE 和 ERROR。正如你所見(jiàn),ERROR是默認(rèn)為你自動(dòng)添加的。
self._impl是select.epoll()和selet.select()兩者中的一個(gè)。我們稍后將看到Tornado是如何在它們之間進(jìn)行選擇的。
現(xiàn)在讓我們來(lái)看看實(shí)際的主循環(huán),不知何故,這段代碼被放在了start()方法中:
def?start(self):????"""Starts?the?I/O?loop.
????The?loop?will?run?until?one?of?the?I/O?handlers?calls?stop(),?which
????will?make?the?loop?stop?after?the?current?event?iteration?completes.
????"""
????self._running?=?True
????while?True:
????[?...?]
????????if?not?self._running:
????????????break
????????[?...?]
????????try:
????????????event_pairs?=?self._impl.poll(poll_timeout)
????????except?Exception,?e:
????????????if?e.args?==?(4,?"Interrupted?system?call"):
????????????????logging.warning("Interrupted?system?call",?exc_info=1)
????????????????continue
????????????else:
????????????????raise
????????#?Pop?one?fd?at?a?time?from?the?set?of?pending?fds?and?run
????????#?its?handler.?Since?that?handler?may?perform?actions?on
????????#?other?file?descriptors,?there?may?be?reentrant?calls?to
????????#?this?IOLoop?that?update?self._events
????????self._events.update(event_pairs)
????????while?self._events:
????????????fd,?events?=?self._events.popitem()
????????????try:
????????????????self._handlers[fd](fd,?events)
????????????except?KeyboardInterrupt:
????????????????raise
????????????except?OSError,?e:
????????????????if?e[0]?==?errno.EPIPE:
????????????????????#?Happens?when?the?client?closes?the?connection
????????????????????pass
????????????????else:
????????????????????logging.error("Exception?in?I/O?handler?for?fd?%d",
??????????????????????????????????fd,?exc_info=True)
????????????except:
????????????????logging.error("Exception?in?I/O?handler?for?fd?%d",
??????????????????????????????fd,?exc_info=True)
poll()方法返回一個(gè)形如(fd: events)的鍵值對(duì),并賦值給event_pairs變量。由于當(dāng)一個(gè)信號(hào)在任何一個(gè)事件發(fā)生前到來(lái)時(shí),C函數(shù)庫(kù)中的poll()方法會(huì)返回EINTR(實(shí)際是一個(gè)值為4的數(shù)值),所以"Interrupted system call"這個(gè)特殊的異常需要被捕獲。更詳細(xì)的請(qǐng)查看man poll。
在內(nèi)部的while循環(huán)中,event_pairs中的內(nèi)容被一個(gè)一個(gè)的取出,然后相應(yīng)的處理器會(huì)被調(diào)用。pipe 異常在這里默認(rèn)不進(jìn)行處理。為了讓這個(gè)類適應(yīng)更一般的情況,在http處理器中處理這個(gè)異常是一個(gè)更好的方案,但是選擇現(xiàn)在這樣處理或許是因?yàn)楦菀滓恍?/p>
注釋中解釋了為什么使用字典的popitem()方法,而不是使用更普遍一點(diǎn)的下面這種做法(指使用迭代,譯者注):
for?fd,?events?in?self._events.items():原因很簡(jiǎn)單,在主循環(huán)期間,這個(gè)_events字典變量可能會(huì)被處理器所修改。比如remove_handler()處理器。這個(gè)方法把fd(即文件描述符,譯者注)從_events字典中取出(extracts,意思是取出并從_events中刪除,譯者注),所以即使fd被選擇到了,它的處理器也不會(huì)被調(diào)用(作者的意思是,如果使用for迭代循環(huán)_events,那么在迭代期間_events就不能被修改,否則會(huì)產(chǎn)生不可預(yù)計(jì)的錯(cuò)誤,比如,明明調(diào)用了remove_handler()方法刪除了某個(gè)<fd, handler>鍵值對(duì),但是該handler還是被調(diào)用了,譯者注)。
(意義不大的)循環(huán)結(jié)束技巧
怎么讓這個(gè)主循環(huán)停止是很有技巧性的。self._running變量被用來(lái)在運(yùn)行時(shí)從主循環(huán)中跳出,處理器可以通過(guò)調(diào)用stop()方法把它設(shè)置為False。通常情況下,這就能讓主循環(huán)停止了,但是stop()方法還能被一個(gè)信號(hào)處理器所調(diào)用,所以,如果1)主循環(huán)正阻塞在poll()方法處,2)服務(wù)端沒(méi)有接收到任何來(lái)自客戶端的請(qǐng)求3)信號(hào)沒(méi)有被OS投遞到正確的線程中,你將不得不等待poll()方法出現(xiàn)超時(shí)情況后才會(huì)返回。考慮到這些情況并不時(shí)常發(fā)生,還有poll()方法的默認(rèn)超時(shí)時(shí)間只不過(guò)是0.2秒,所以這種讓主循環(huán)停止的方式還算過(guò)得去。
但不管怎樣,Tornado的開(kāi)發(fā)者為了讓主循環(huán)停止,還是額外的創(chuàng)建了一個(gè)沒(méi)有名字的管道和對(duì)應(yīng)的處理器,并把管道的一端放在了輪詢文件描述符列表中。當(dāng)需要停止時(shí),在管道的另一端隨便寫(xiě)點(diǎn)什么,這能高效率的(意思是馬上,譯者注)喚醒主循環(huán)在poll()方法處的阻塞(貌似Java NIO的Windows實(shí)現(xiàn)就用了這種方法,譯者注)。這里節(jié)選了一些代碼片段:
def?__init__(self,?impl=None):????[...]
????#?Create?a?pipe?that?we?send?bogus?data?to?when?we?want?to?wake
????#?the?I/O?loop?when?it?is?idle
????r,?w?=?os.pipe()
????self._set_nonblocking(r)
????self._set_nonblocking(w)
????self._waker_reader?=?os.fdopen(r,?"r",?0)
????self._waker_writer?=?os.fdopen(w,?"w",?0)
????self.add_handler(r,?self._read_waker,?self.WRITE)
def?_wake(self):
????try:
????????self._waker_writer.write("x")
????except?IOError:
????????pass
實(shí)際上,上述代碼中存在一個(gè)bug:那個(gè)只讀文件描述符r,雖然是用來(lái)讀的,但在注冊(cè)時(shí)卻附加上了WRITE類型的事件,這將導(dǎo)致該注冊(cè)實(shí)際不會(huì)被響應(yīng)。正如我先前所說(shuō)的,用不用專門(mén)找個(gè)方法其實(shí)沒(méi)什么的,所以我對(duì)他們沒(méi)有發(fā)現(xiàn)這個(gè)方法不起作用的事實(shí)并不感到驚訝。我在mail list中報(bào)告了這個(gè)情況,但是尚未收到答復(fù)。
定時(shí)器
另外一個(gè)在IOLoop模塊中很有特點(diǎn)的設(shè)計(jì)是對(duì)定時(shí)器的簡(jiǎn)單實(shí)現(xiàn)。一系列的定時(shí)器會(huì)被以是否過(guò)期的形式來(lái)維護(hù)和保存,這用到了python的bisect模塊:
def?add_timeout(self,?deadline,?callback):????"""Calls?the?given?callback?at?the?time?deadline?from?the?I/O?loop."""
????timeout?=?_Timeout(deadline,?callback)
????bisect.insort(self._timeouts,?timeout)
????return?timeout
在主循環(huán)中,所有過(guò)期了的定時(shí)器的回調(diào)會(huì)按照過(guò)期的順序被觸發(fā)。poll()方法中的超時(shí)時(shí)間會(huì)動(dòng)態(tài)的進(jìn)行調(diào)整,調(diào)整的結(jié)果就是如果沒(méi)有新的客戶端請(qǐng)求,那么下一個(gè)定時(shí)器就好像沒(méi)有延遲一樣的被觸發(fā)(意思是如果沒(méi)有新的客戶端的請(qǐng)求,poll()方法將被阻塞直到超時(shí),這個(gè)超時(shí)時(shí)間的設(shè)定會(huì)根據(jù)下一個(gè)定時(shí)器與當(dāng)前時(shí)間之間的間隔進(jìn)行調(diào)整,調(diào)整后,超時(shí)的時(shí)間會(huì)等同于距離下一個(gè)定時(shí)器被觸發(fā)的時(shí)間,這樣在poll()阻塞完后,下一個(gè)定時(shí)器剛好過(guò)期,譯者注)。
選擇select方案
讓我們現(xiàn)在快速的看一下poll和select這兩種select方案的實(shí)現(xiàn)代碼。Python已經(jīng)在版本2.6的標(biāo)準(zhǔn)庫(kù)中支持了epoll,你可以通過(guò)在select模塊上使用hasattr()方法檢測(cè)當(dāng)前Python是否支持epoll。如果python版本小于2.6,Tornado將用它自己的基于C的epoll模塊。你可以在tornado/epoll.c文件中找到它源代碼。如果最后這也不行(因?yàn)閑poll不是每個(gè)Linux都有的),它將回退到selec._Select并把_EPoll類包裝成和select.epoll一樣的api接口。在你做性能測(cè)試之前,請(qǐng)確定你能使用epoll,因?yàn)閟elect在有大量文件描述符情況下的效率非常低。
#?Choose?a?poll?implementation.?Use?epoll?if?it?is?available,?fall?back?to#?select()?for?non-Linux?platforms
if?hasattr(select,?"epoll"):
????#?Python?2.6+?on?Linux
????_poll?=?select.epoll
else:
????try:
????????#?Linux?systems?with?our?C?module?installed
????????import?epoll
????????_poll?=?_EPoll
????except:
????????#?All?other?systems
????????import?sys
????????if?"linux"?in?sys.platform:
????????????logging.warning("epoll?module?not?found;?using?select()")
????????_poll?=?_Select
通過(guò)上述閱讀,我們的介紹已經(jīng)涵蓋了大部分IOLoop模塊。正如廣告中介紹的那樣,它是一段優(yōu)雅而又簡(jiǎn)單的代碼。
從sockets到流
讓我們來(lái)看看IOStream模塊。它的目的是提供一個(gè)對(duì)非阻塞式sockets的輕量級(jí)抽象,它提供了三個(gè)方法:
- read_until(),從socket中讀取直到遇到指定的字符串。這為在讀取HTTP頭時(shí)遇到空行分隔符自動(dòng)停止提供了方便。
- read_bytes(),從socket中讀取指定數(shù)量的字節(jié)。這為讀取HTTP消息的body部分提供了方便。
- write(),將指定的buffer寫(xiě)入socket并持續(xù)監(jiān)測(cè)直到這個(gè)buffer被發(fā)送。
write()方法提供了將調(diào)用者提供的數(shù)據(jù)加以緩沖直到IOLoop調(diào)用了它的(指write方法的,譯者注)處理器的功能,因?yàn)榈侥菚r(shí)候就說(shuō)明socket已經(jīng)為寫(xiě)數(shù)據(jù)做好了準(zhǔn)備:
def?write(self,?data,?callback=None):????"""Write?the?given?data?to?this?stream.
????If?callback?is?given,?we?call?it?when?all?of?the?buffered?write
????data?has?been?successfully?written?to?the?stream.?If?there?was
????previously?buffered?write?data?and?an?old?write?callback,?that
????callback?is?simply?overwritten?with?this?new?callback.
????"""
????self._check_closed()
????self._write_buffer?+=?data
????self._add_io_state(self.io_loop.WRITE)
????self._write_callback?=?callback
該方法只是用socket.send()來(lái)處理WRITE類型的事件,直到EWOULDBLOCK異常發(fā)生或者buffer被發(fā)送完畢。
讀數(shù)據(jù)的方法和上述過(guò)程正好相反。讀事件的處理器持續(xù)讀取數(shù)據(jù)直到緩沖區(qū)被填滿為止。這就意味著要么讀取指定數(shù)量的字節(jié)(如果調(diào)用的是read_bytes()),要么讀取的內(nèi)容中包含了指定的分隔符(如果調(diào)用的是read_util()):
def?_handle_read(self):????try:
????????chunk?=?self.socket.recv(self.read_chunk_size)
????except?socket.error,?e:
????????if?e[0]?in?(errno.EWOULDBLOCK,?errno.EAGAIN):
????????????return
????????else:
????????????logging.warning("Read?error?on?%d:?%s",
????????????????????????????self.socket.fileno(),?e)
????????????self.close()
????????????return
????if?not?chunk:
????????self.close()
????????return
????self._read_buffer?+=?chunk
????if?len(self._read_buffer)?>=?self.max_buffer_size:
????????logging.error("Reached?maximum?read?buffer?size")
????????self.close()
????????return
????if?self._read_bytes:
????????if?len(self._read_buffer)?>=?self._read_bytes:
????????????num_bytes?=?self._read_bytes
????????????callback?=?self._read_callback
????????????self._read_callback?=?None
????????????self._read_bytes?=?None
????????????callback(self._consume(num_bytes))
????elif?self._read_delimiter:
????????loc?=?self._read_buffer.find(self._read_delimiter)
????????if?loc?!=?-1:
????????????callback?=?self._read_callback
????????????delimiter_len?=?len(self._read_delimiter)
????????????self._read_callback?=?None
????????????self._read_delimiter?=?None
????????????callback(self._consume(loc?+?delimiter_len))
如下所示的_consume方法是為了確保在要求的返回值中不會(huì)包含多余的來(lái)自流的數(shù)據(jù),并且保證后續(xù)的讀操作會(huì)從當(dāng)前字節(jié)的下一個(gè)字節(jié)開(kāi)始(先將流中的數(shù)據(jù)讀到self.read_buffer中,然后根據(jù)要求進(jìn)行切割,返回切割掉的數(shù)據(jù),保留切割后的數(shù)據(jù)供下一次的讀取,譯者注):
def?_consume(self,?loc):????result?=?self._read_buffer[:loc]
????self._read_buffer?=?self._read_buffer[loc:]
????return?result
還值得注意的是在上述_handle_read()方法中read buffer的上限——self.max_buffer_size。默認(rèn)值是100MB,這似乎對(duì)我來(lái)說(shuō)是有點(diǎn)大了。舉個(gè)例子,如果一個(gè)攻擊者和服務(wù)端建立了100個(gè)連接,并持續(xù)發(fā)送不帶頭結(jié)束分隔符的頭信息,那么Tornado需要10GB的內(nèi)存來(lái)處理這些請(qǐng)求。即使內(nèi)存ok,這種數(shù)量級(jí)數(shù)據(jù)的復(fù)制操作(比如像上述_consume()方法中的代碼)很可能使服務(wù)器超負(fù)荷。我們還注意到在每次迭代中_handle_read()方法是如何在這個(gè)buffer中搜索分隔符的,所以如果攻擊者以小塊形式發(fā)送大量的數(shù)據(jù),服務(wù)端不得不做很多次搜索工作。歸根結(jié)底,你應(yīng)該想要將這個(gè)參數(shù)和諧掉,除非你真的很希望那樣(Bottom of line, you might want to tune this parameter unless you really expect requests that big 不大明白怎么翻譯,譯者注)并且你有足夠的硬件條件。
HTTP 服務(wù)器
有了IOLoop模塊和IOStream模塊的幫助,寫(xiě)一個(gè)異步的HTTP服務(wù)器只差一步之遙,這一步就在httpserver.py中完成。
HTTPServer類它自己只負(fù)責(zé)處理將接收到的新連接的socket添加到IOLoop中。該監(jiān)聽(tīng)型的socket自己也是IOLoop的一部分,正如在listen()方法中見(jiàn)到的那樣:
def?listen(self,?port,?address=""):????assert?not?self._socket
????self._socket?=?socket.(socket.AF_INET,?socket.SOCK_STREAM,?0)
????flags?=?fcntl.fcntl(self._socket.fileno(),?fcntl.F_GETFD)
????flags?|=?fcntl.FD_CLOEXEC
????fcntl.fcntl(self._socket.fileno(),?fcntl.F_SETFD,?flags)
????self._socket.setsockopt(socket.SOL_SOCKET,?socket.SO_REUSEADDR,?1)
????self._socket.setblocking(0)
????self._socket.bind((address,?port))
????self._socket.listen(128)
????self.io_loop.add_handler(self._socket.fileno(),?self._handle_events,
?????????????????????????????self.io_loop.READ)
除了綁定給定的地址和端口外,上述代碼還設(shè)置了"close on exec"和"reuse address"這兩個(gè)標(biāo)志位。前者在應(yīng)用程序創(chuàng)建子進(jìn)程的時(shí)候特別有用。在這種情況下,我們不想讓套接字保持打開(kāi)的狀態(tài)(任何設(shè)置了"close on exec"標(biāo)志位的文件描述符,都不能被使用exec函數(shù)方式創(chuàng)建的子進(jìn)程讀寫(xiě),因?yàn)樵撐募枋龇趀xec函數(shù)調(diào)用前就會(huì)被自動(dòng)釋放,譯者注)。后者用來(lái)避免在服務(wù)器重啟的時(shí)候發(fā)生“該地址以被使用”這種錯(cuò)誤時(shí)很有用。
正如你所見(jiàn)到的,后備連接所允許的最大數(shù)目是128(注意,listen方法并不是你想象中的“開(kāi)始在128端口上監(jiān)聽(tīng)”的意思,譯者注)。這意味著如果有128個(gè)連接正在等待被accept,那么直到服務(wù)器有時(shí)間將前面128個(gè)連接中的某幾個(gè)accept了,新的連接都將被拒絕。我建議你在做性能測(cè)試的時(shí)候?qū)⒃搮?shù)調(diào)高,因?yàn)楫?dāng)新的連接被拋棄的時(shí)候?qū)⒅苯佑绊懩阕鰷y(cè)試的準(zhǔn)確性。
在上述代碼中注冊(cè)的_handle_events()處理器用來(lái)accept新連接,并創(chuàng)建相關(guān)的IOStream對(duì)象和初始化一個(gè)HTTPConnection對(duì)象,HTTPConnection對(duì)象負(fù)責(zé)處理剩下的交互部分:
def?_handle_events(self,?fd,?events):????while?True:
????????try:
????????????connection,?address?=?self._socket.accept()
????????except?socket.error,?e:
????????????if?e[0]?in?(errno.EWOULDBLOCK,?errno.EAGAIN):
????????????????return
????????????raise
????????try:
????????????stream?=?iostream.IOStream(connection,?io_loop=self.io_loop)
????????????HTTPConnection(stream,?address,?self.request_callback,
???????????????????????????self.no_keep_alive,?self.xheaders)
????????except:
????????????logging.error("Error?in?connection?callback",?exc_info=True)
可以看到這個(gè)方法在一次迭代中accept了所有正在等待處理的連接。也就是說(shuō)直到EWOULDBLOCK異常發(fā)生while True循環(huán)才會(huì)退出,這也就意味著當(dāng)前沒(méi)有需要處理accept的連接了。
HTTP頭的部分的解析工作開(kāi)始于HTTPConnection類的構(gòu)造函數(shù)__init()__():
def?__init__(self,?stream,?address,?request_callback,?no_keep_alive=False,?????????????xheaders=False):
????self.stream?=?stream
????self.address?=?address
????self.request_callback?=?request_callback
????self.no_keep_alive?=?no_keep_alive
????self.xheaders?=?xheaders
????self._request?=?None
????self._request_finished?=?False
????self.stream.read_until("rnrn",?self._on_headers)
如果你很想知道xheaders參數(shù)的意義,請(qǐng)看這段注釋:
如果xheaders為T(mén)rue,我們將支持把所有請(qǐng)求的HTTP頭解析成X-Real-Ip和X-Scheme格式,而原先我們將HTTP頭解析成remote?IP和HTTP?scheme格式。這種格式的HTTP頭在Tornado運(yùn)行于反向代理或均衡負(fù)載服務(wù)器的后端時(shí)將非常有用。_on_headers()回調(diào)函數(shù)實(shí)際用來(lái)解析HTTP頭,并在有請(qǐng)求內(nèi)容的情況下通過(guò)使用read_bytes()來(lái)讀取請(qǐng)求的內(nèi)容部分。_on_request_body()回調(diào)函數(shù)用來(lái)解析POST的參數(shù)并調(diào)用應(yīng)用層提供的回調(diào)函數(shù):
def?_on_headers(self,?data):????eol?=?data.find("rn")
????start_line?=?data[:eol]
????method,?uri,?version?=?start_line.split("?")
????if?not?version.startswith("HTTP/"):
????????raise?Exception("Malformed?HTTP?version?in?HTTP?Request-Line")
????headers?=?HTTPHeaders.parse(data[eol:])
????self._request?=?HTTPRequest(
????????connection=self,?method=method,?uri=uri,?version=version,
????????headers=headers,?remote_ip=self.address[0])
????content_length?=?headers.get("Content-Length")
????if?content_length:
????????content_length?=?int(content_length)
????????if?content_length?>?self.stream.max_buffer_size:
????????????raise?Exception("Content-Length?too?long")
????????if?headers.get("Expect")?==?"100-continue":
????????????self.stream.write("HTTP/1.1?100?(Continue)rnrn")
????????self.stream.read_bytes(content_length,?self._on_request_body)
????????return
????self.request_callback(self._request)
def?_on_request_body(self,?data):
????self._request.body?=?data
????content_type?=?self._request.headers.get("Content-Type",?"")
????if?self._request.method?==?"POST":
????????if?content_type.startswith("application/x-www-form-urlencoded"):
????????????arguments?=?cgi.parse_qs(self._request.body)
????????????for?name,?values?in?arguments.iteritems():
????????????????values?=?[v?for?v?in?values?if?v]
????????????????if?values:
????????????????????self._request.arguments.setdefault(name,?[]).extend(
????????????????????????values)
????????elif?content_type.startswith("multipart/form-data"):
????????????boundary?=?content_type[30:]
????????????if?boundary:?self._parse_mime_body(boundary,?data)
????self.request_callback(self._request)
將結(jié)果寫(xiě)回客戶端的工作在HTTPRequest類中處理,你可以在上面的_on_headers()方法中看到具體的實(shí)現(xiàn)。HTTPRequest類僅僅將寫(xiě)回的工作代理給了stream對(duì)象。
def?write(self,?chunk):????assert?self._request,?"Request?closed"
????self.stream.write(chunk,?self._on_write_complete)
未完待續(xù)?
通過(guò)這篇文章,我已經(jīng)涵蓋了從socket到應(yīng)用層的所有方面。這應(yīng)該能給你關(guān)于Tornado是如何工作的一個(gè)清晰的理解。總之,我認(rèn)為T(mén)ornado的代碼是非常友好的,我希望你也這樣認(rèn)為。
Tornado框架還有很大一部分我們沒(méi)有探索,比如wep.py(應(yīng)該是web.py,譯者注)這個(gè)實(shí)際與你應(yīng)用打交道的模塊,又或者是template engine模塊。如果我有足夠興趣的話,我也會(huì)介紹這些部分。可以通過(guò)訂閱我的RSS feed來(lái)鼓勵(lì)我。
轉(zhuǎn)載于:https://www.cnblogs.com/yiwenshengmei/archive/2011/06/08/understanding_tornado.html
總結(jié)
以上是生活随笔為你收集整理的[翻译]深入理解Tornado——一个异步web服务器的全部?jī)?nèi)容,希望文章能夠幫你解決所遇到的問(wèn)題。
- 上一篇: linux 3.0 kernel
- 下一篇: XenApp 6 license导入报错