深入tornado中的IOStream

來源:互聯網
上載者:User

標籤:根據   cad   class   pre   locking   這一   參考   future   合并   

IOStream對tornado的高效起了很大的作用,他封裝了socket的非阻塞IO的讀寫操作。大體上可以這麼說,當串連建立後,服務端與用戶端的請求響應都是基於IOStream的,也就是說:IOStream是用來處理串連的。

接下來說一下有關接收請求的大體流程:

  當串連建立,伺服器端會產生一個對應該串連的socket,同時將該socket封裝至IOStream執行個體中(這代表著IOStream的初始化)。

  我們知道tornado是基於IO多工(就拿epoll來說),此時將socket進行register,事件為READABLE,這一步與IOStream沒有多大關係。 

  當該socket事件發生時,也就是意味著有資料從串連發送到了系統緩衝區中,這時就需要將chunk讀入到我們在記憶體中為其開闢的_read_buffer中,在IOStream中使用deque作為buffer。_read_buffer表示讀緩衝,當然也有_write_buffer,但不管是讀緩衝還是寫緩衝本質上就是tornado進程開闢的一段用來儲存資料的記憶體。

  而這些chunk一般都是用戶端發送的請求了,但是我們還需要對這些chunk作進一步操作,比如這個chunk中可能包含了多個請求,如何把請求分離?(每個請求首部的結束符是b‘\r\n\r\n‘),這裡就用到read_until來分離請求並設定callback了。同時會將被分離的請求資料從_read_buffer中移除。

  然後就是將callback以及他的參數(被分離的請求資料)添加至IOLoop._callbacks中,等待下一次IOLoop的執行,屆時會迭代_callbacks並執行回呼函數。

  

  補充: tornado是水平觸發,所以假如讀完一次chunk後系統緩衝區中依然還有資料,那麼下一次的epoll.poll()依然會返回該socket。

 

在iostream中有一個類叫做:IOStream  

有幾個較為重要的屬性:

def __init__():    self.socket = socket           # 封裝socket     self.socket.setblocking(False) # 設定socket為非阻塞    self.io_loop = io_loop or ioloop.IOLoop.current()        self._read_buffer = deque()    # 讀緩衝    self._write_buffer = deque()   # 寫緩衝     self._read_callback = None     # 讀到指定位元組資料時,或是指定標誌字串時,需要執行的回呼函數    self._write_callback = None    # 發送完_write_buffer的資料時,需要執行的回呼函數

有幾個較為重要的方法

class IOStream(object):    def read_until(self, delimiter, callback):     def read_bytes(self, num_bytes, callback, streaming_callback=None):     def read_until_regex(self, regex, callback):     def read_until_close(self, callback, streaming_callback=None):     def write(self, data, callback=None):

以上所有的方法都需要一個可選的callback參數,如果該參數為None則該方法會返回一個Future對象。

以上所有的讀方法本質上都是讀取該socket所發送來的資料,然後當讀到指定分隔字元或者標記的時候,停止讀,然後將該分隔字元以及其前面的資料作為callback(如果沒有callback,則將資料設定為Future對象的result)的參數,然後將callback添加至IOLoop._callbacks中。當然其中所有的"讀"操作是非阻塞的!
就拿最為常見的read_until方法來說,下面是代碼簡化版:

    def read_until(self, delimiter, callback=None, max_bytes=None):        future = self._set_read_callback(callback)     # 可能是Future對象,也可能是None        self._read_delimiter = delimiter          # 設定分隔字元        self._read_max_bytes = max_bytes          # 設定最大讀位元組數        self._try_inline_read()        return future

其中_set_read_callback會根據callback是否存在返回None或者Future對象(存在返回None,否則返回一個Future執行個體對象)

如果我們
再來看_try_inline_read方法的簡化版:

def _try_inline_read(self):        """            嘗試從_read_buffer中讀取所需資料        """        # 查看是否我們已經在之前的讀操作中得到了資料        self._run_streaming_callback() # 字元流回調,一般是讀操作沒有徹底讀夠而處於streaming狀態,一般預設是None,如果調用read_bytes和read_until_close並指定了streaming_callback參數就會造成這個回調        pos = self._find_read_pos()       # 嘗試在_read_buffer中找到分隔字元的位置。找到則返回分隔字元末尾所處的位置,如果不能,則返回None。        if pos is not None:            self._read_from_buffer(pos)            return
self._check_closed() # 檢查當前IOStream是否關閉 pos = self._read_to_buffer_loop() # 從系統緩衝中讀取一個chunk,檢查是否含有分隔字元,沒有則繼續讀取一個chunk,合并兩個chunk,再次檢查是否函數分隔字元…… 如果找到了分隔字元,會返回分隔字元末尾在_read_buffer中所處的位置 if pos is not None: # 如果找到了分隔字元, self._read_from_buffer(pos) # 將所需的資料從_read_buffer中移除,並將其作為callback的參數,然後將callback封裝後添加至IOLoop._callbacks中 return
# 沒找到分隔字元,要麼關閉IOStream,要麼為該socket在IOLoop中註冊事件 if self.closed(): self._maybe_run_close_callback() else: self._add_io_state(ioloop.IOLoop.READ)

上面的代碼被我用空行分為了三部分,每一部分順序的對應下面每一句話

分析該方法:

  1 首先在_read_buffer第一項中找分隔字元,找到了就將分隔字元以及其前的資料從_read_buffer中移除並將其作為參數傳入回呼函數,沒找到就將第二項與第一項合并然後繼續找……;

  2 如果在_read_buffer所有項中都沒找到的話就把系統緩衝中的資料讀取至_read_buffer,然後合并再次尋找,

  3 如果把系統緩衝中的資料都取完了都還沒找到,那麼就等待下一次該socket發生READ事件後再找,這時的找則就是:將系統緩衝中的資料讀取到_read_buffer中然後找,也就是執行第2步。

 來看一看這三部分分別調用了什麼方法:

第一部分中的_find_read_pos以及_read_from_buffer

前者主要是在_read_buffer中尋找分隔字元,並返回分隔字元的位置,後者則是將分隔字元以及分隔字元前面的所有資料從_read_buffer中取出並將其作為callback的參數,然後將callback封裝後添加至IOLoop._callbacks中

來看_find_read_pos方法的簡化版:

 _find_read_pos _read_from_buffer _run_read_callback

這裡面還用到一個很有意思的函數:_merge_prefix ,這個函數的作用就是將deque的首項調整為指定大小

 _merge_prefix

 

第二部分的_read_to_buffer_loop

 _read_to_buffer_loop

 

第三部分_add_io_state,該函數和ioloop非同步相關

 _add_io_state

 

參考:

http://www.nowamagic.net/academy/detail/13321051

深入tornado中的IOStream

聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在5個工作日內處理。

如果您發現本社區中有涉嫌抄襲的內容,歡迎發送郵件至: info-contact@alibabacloud.com 進行舉報並提供相關證據,工作人員會在 5 個工作天內聯絡您,一經查實,本站將立刻刪除涉嫌侵權內容。

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.