Tornado-6.0.3源碼分析之TCPServer

一、前述

在IOStream之上,區分爲服務端和客戶端的不同實現,對於服務端,實現方向是TCPServer–> HTTPServer --> web應用實現;對於客戶端,實現方向是TCPClient --> HTTPClient;在本系列的源碼分析上,優先以服務器方向爲準。

TCPServer類的使用方法是,繼承該類並實現其中的handle_stream方法,這個handle_stream方法,就是對於的具體的網絡連接,被包裝實例化爲IOStream(或SSLIOStream)後,協議層(如http層)需要完成的實現工作。

對於TCPServer類的初始化,有三種方法,這裏先簡單過一個眼熟,知道有這三種形式即可。

  • 方法一:適用於簡單的單進程程序
    server = TCPServer() # 初始化一個實例
    server.listen(8888) # 設置監聽端口和添加IOLoop監聽事件和回調處理函數,以便於等待處理客戶端的連接
    IOLoop.current().start() # IOLoop開啓事件監聽,這裏纔是真正的開始了網絡監聽
  • 方法二:使用bind/start方法,適用於簡單的多進程程序
    server = TCPServer()
    # 區別於方法一,是把listen方法,拆分成bind/start方法組合,其中,對於start方法,可以通過參數指定
    # 需要生成的子進程的數量,在該方法內部,會調用process.fork_processes去生成子進程。
    server.bind(8888)
    server.start(0)
    IOLoop.current().start()
  • 方法三:拆分socket的創建綁定過程和多進程的使用,適用於需要靈活創建sockets的高級多進程程序
    sockets = bind_sockets(8888)
    tornado.process.fork_processes(0)
    server = TCPServer()
    server.add_sockets(sockets)
    IOLoop.current().start()

通過進一步分析源碼可以知道的是,對於TCPServer的初始化過程,核心相關的方法調用主要是涉及了
tornado.netutil.bind_sockets方法和tornado.netutil.add_accept_handler方法,對於TCPServer內部相關方法,則涉及到_handle_connection方法和預留給上層協議層定義的handle_stream方法。在下面的分析中,我們重點以方法一的初始化形式來查看這些核心方法是如何被組合在一起的。

二、TCPServer.listen方法

def listen(self, port: int, address: str = "") -> None:
    """Starts accepting connections on the given port.

    This method may be called more than once to listen on multiple ports.
    `listen` takes effect immediately; it is not necessary to call
    `TCPServer.start` afterwards.  It is, however, necessary to start
    the `.IOLoop`.
    """
    # 直白的翻譯下上面的函數說明
    # 在特定的端口上,開始監聽接受網絡連接
    # 本方法可以被多次調用,以便於監聽多個不同的端口。本方法一旦調用後,監聽功能是立即生效的,不需要
    # 再去調用TCPServer.start方法,但是,在調用本方法後,開啓IOLoop事件循環監聽是必須要有的。
    sockets = bind_sockets(port, address=address)
    self.add_sockets(sockets)

代碼也是簡單,就是通過調用bind_sockets,解析獲取特定端口地址相關的socket事例,再進一步通過
add_sockets方法添加對這些sockets的監聽處理過程。

查看bind_sockets方法,可以看到其內部是很經典的socket創建過程,解析獲取特定端口地址信息,並依次在對應的端口和地址上創建網絡socket,設置該socket的網絡選項、綁定端口地址、設置監聽長度等工作。可以看出,到這裏,只是創建了相應的socket,但是還沒有添加這些socket的網絡事件監聽。
1 socket.getaddrinfo(address, port, family, socket.SOCK_STREAM, 0, flags)
獲取與給定的端口地址相關的所有網絡地址信息
2 sock = socket.socket(af, socktype, proto)
對於每個網絡地址,創建一個網絡socket
3 sock.setsockopt
設置相應的網絡選項,如端口可利用socket.SO_REUSEPORT等
4 sock.bind(sockaddr)
每個sock實例,綁定特定的端口地址
5 sock.listen(backlog)
設置監聽長度
6 sockets.append(sock),最後return sockets

而self.add_sockets方法,就是對這些sockets添加相應的網絡事件監聽。

def add_sockets(self, sockets: Iterable[socket.socket]) -> None:
    """Makes this server start accepting connections on the given sockets.
    """
    for sock in sockets:
    	# self._sockets字典,保存每個sock實例
        self._sockets[sock.fileno()] = sock
        # self._handlers字典,保存每個socket實例的處理函數,這個處理函數主要是用在
        # selt.stop方法中,用於把對應的sock從IOLoop循環中去掉事件監聽。
        self._handlers[sock.fileno()] = add_accept_handler(
            sock, self._handle_connection
        )

從def add_accept_handler(sock: socket.socket, callback: Callable[[socket.socket, Any], None])的定義中可以看出,就是給定一個socket和在該socket有網絡連接上時,一個回調處理函數。接下來我們先看下add_accept_handler內部實現。

def add_accept_handler(
    sock: socket.socket, callback: Callable[[socket.socket, Any], None]
) -> Callable[[], None]:

    _loop = IOLoop.current()
    removed = [False]
    # 內部定義了一個sock的處理accept的回調函數,用於io loop的事件監聽註冊
    def accept_handler(fd: socket.socket, events: int) -> None:
        for i in range(_DEFAULT_BACKLOG):
            if removed[0]:
                return
            try:
                connection, address = sock.accept()
            except BlockingIOError:
                return
            except ConnectionAbortedError:
               continue
            set_close_exec(connection.fileno())
            # 一旦獲取連接了客戶端的connection連接,提交給原來傳參進來的回調處理函數去處理
            # 該連接後續的事情,如數據的讀寫等。
            callback(connection, address)
  
    # 內部定義一個去掉網絡監聽註冊的函數,外部程序可以在適當調用,去掉該sock的網絡監聽功能。
    def remove_handler() -> None:
        io_loop.remove_handler(sock)
        removed[0] = True

    # 對該sock向io loop循環添加一個可讀狀態的事件監聽,在有客戶端連接上來時,會觸發可讀狀態,從而回調
    # accept_handler函數,而accept_handler在成功獲取了客戶端的socket連接後,
    # 提交給callback(connection, address),由上層進一步處理客戶端的網絡連接。
    io_loop.add_handler(sock, accept_handler, IOLoop.READ)
    return remove_handler

概括一下add_accept_handler的異步處理過程:
(1)對給定的sock,向io loop事件循環中,設置監聽讀狀態,並添加一個內部定義的回調處理函數accept_handler,功能就是接收客戶端的連接。
(2)返回一個內部定義的函數對象remove_handler,由外層在適當的時候調用,去掉該sock的io loop監聽。

現在再看下add_sockets中對add_accept_handler的調用。

      self._handlers[sock.fileno()] = add_accept_handler(
            sock, self._handle_connection)

可以看到,對於返回的函數對象,統一保存在self._handlers字典中,留在後續調用(其實就是stop方法),再看下TCPServer中在接收一個客戶端連接時的回調處理函數self._handle_connection。

try:
    # 前面是對SSL的連接的處理

    # 對於連接上來的客戶端連接connection,依據TCPServer是否需要SSL連接,實例化出IOStream類或者SSLIOStream類對象。
    if self.ssl_options is not None:
        stream = SSLIOStream(
            connection,
            max_buffer_size=self.max_buffer_size,
            read_chunk_size=self.read_chunk_size,
        )  # type: IOStream
    else:
        stream = IOStream(
            connection,
            max_buffer_size=self.max_buffer_size,
            read_chunk_size=self.read_chunk_size,
        )

    # 調用self.handle_stream處理,注意,這個方法對於TCPServer是缺省的,需要由其子類來實現。
    # 也就是說,走到調用這一步時,如何進一步處理客戶端連接的IOStream實例,則子類自行定義,把控制權交給
    # 子類了。
    future = self.handle_stream(stream, address)
    # 如何返回的是異步的future對象,則添加到io loop循環事件中,異步執行。
    if future is not None:
        IOLoop.current().add_future(
            gen.convert_yielded(future), lambda f: f.result()
        )
except Exception:
    app_log.error("Error in connection callback", exc_info=True)
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章