深入tornado中的IOStream

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

 


發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章