[原]tornado源碼分析系列(六)[HTTPServer詳解]


引言:上一章講了關於HTTPServer的原理,這一次通過分析源碼來了解更多的細節

看看HTTPServer類的組織結構:

HTTPServer的主要工作

一.提供了一些基礎的比如說listen,bind此類共有操作

二.完成了一個 _handle_events()的公有回調函數,此函數是 IOLoop的基礎,此函數為每一個連接創建一個單獨的 IOStream 對象

三.start函數,啟動HTTPServer,並設置相應的參數(如根據CPU個數來設置進程數等)

從HTTPServer類的構造函數可以看出,最重要的參數是設置回調函數,此回調函數用於處理request對象

每次有HTTP的請求,都會通過HTTPConnection 封裝一個HTTPRequest對象,這個對象包含了HTTP請求的所有信息

所以在HTTPServer層,需要對這個對象進行一番處理后調用 request.write將結果返回給客戶端

此回調函數會先注冊到HTTPServer,然后注冊到HTTPConnection 里面,因為request這個對象是由HTTPConnection對象產生

   def _handle_events(self, fd, events):
        while True:
            try:
                connection, address = self._socket.accept()
            except socket.error, e:
                if e.args[0] in (errno.EWOULDBLOCK, errno.EAGAIN):
                    return
                raise
            #SSL 選項
            if self.ssl_options is not None:
                assert ssl, "Python 2.6+ and OpenSSL required for SSL"
                try:
                    connection = ssl.wrap_socket(connection,
                                                 server_side=True,
                                                 do_handshake_on_connect=False,
                                                 **self.ssl_options)
                except ssl.SSLError, err:
                    if err.args[0] == ssl.SSL_ERROR_EOF:
                        return connection.close()
                    else:
                        raise
                except socket.error, err:
                    if err.args[0] == errno.ECONNABORTED:
                        return connection.close()
                    else:
                        raise
            try:
                if self.ssl_options is not None:
                    stream = iostream.SSLIOStream(connection, io_loop=self.io_loop)
                else:
                        #為每一個 connection 創建一個 iostream 實例,以后的IO操作由此實例負責
                        #IOLoop只負責 accept這個連接
                        
                    stream = iostream.IOStream(connection, io_loop=self.io_loop)
                
                #將 stream對象和對應的 address , callback加入到HTTPConnection 中
                #HTTPConnection稍后會有介紹
                #這里的 request_callback 是由Demo里 httpserver.HTTPServer(handle_request) 傳遞進來
                #現代的 HTTP 框架都采用這種模式
                #創建一個 handle_request 這個 回調函數嵌套的注冊到下層,直到真正處理request
                #一般情況是回調繼續傳遞下去直到遇到一個類方法能夠傳遞 request 對象給這個函數
                HTTPConnection(stream, address, self.request_callback,
                               self.no_keep_alive, self.xheaders)
            except:
                logging.error("Error in connection callback", exc_info=True)

通過調用HTTPConnection,然后傳遞stream,address和request_callback到HTTPConnection可以看到,處理request的回調函數注冊到了HTTPConnection.

還需要注意的地方就是,每一次有一個連接的到來,IOLoop都只負責處理accept此連接,然后后面的IO操作就交給IOStream來處理

在start()函數中,會為每個進程創建一個單獨的IOLoop,然后此IOLoop的回調函數統一采用_handle_events()

_handle_events()函數的處理流程總體來說是這樣:

1.注冊到本進程的IOLoop中

2.當有事件發生,只注冊了READ事件,也就是只接受新連接,每次有連接到來,都回調_handle_events()

3.accept此新連接,然后為此新連接創建一個IOStream對象,以后此IOStream負責本連接的所有IO操作,這里是一層抽象,實際

   在IOStream的讀寫事件也是注冊到了本進程的IOLoop中,只不過回調函數不一樣,因為注冊時候的描述符不同。

 調用方式是通過handle[fd]()這種方式調用,所以對於監聽套接口每次都只會調用_handle_events()而對於其他的IOStream的連接

   fd會調用在read_bytes(),read_utils()中注冊的回調函數

 

再看看在HTTPServer中的 start()函數

    def start(self, num_processes=1):

        assert not self._started
        self._started = True
        if num_processes is None or num_processes <= 0:
            num_processes = _cpu_count()
        if num_processes > 1 and ioloop.IOLoop.initialized():
            logging.error("Cannot run in multiple processes: IOLoop instance "
                          "has already been initialized. You cannot call "
                          "IOLoop.instance() before calling start()")
            num_processes = 1
        if num_processes > 1:
            logging.info("Pre-forking %d server processes", num_processes)
            #根據 處理器個數來決定 fork多少個進程
            for i in range(num_processes):
                if os.fork() == 0:# fork() == 0 表示子進程
                    import random
                    from binascii import hexlify
                    try:
                        # If available, use the same method as
                        # random.py
                        seed = long(hexlify(os.urandom(16)), 16)
                    except NotImplementedError:
                        # Include the pid to avoid initializing two
                        # processes to the same value
                        seed(int(time.time() * 1000) ^ os.getpid())
                    random.seed(seed)
                    #為每個進程創建一個IOLoop實例
                    self.io_loop = ioloop.IOLoop.instance()
                    #為每個IOLoop 添加回調函數,這里采用統一回調方式,和IOStream 一樣
                    self.io_loop.add_handler(
                        self._socket.fileno(), self._handle_events,
                        ioloop.IOLoop.READ)
                    return
            os.waitpid(-1, 0)#預防僵屍進程,Unix 環境編程介紹很多
        else:
            if not self.io_loop:
                self.io_loop = ioloop.IOLoop.instance()
            self.io_loop.add_handler(self._socket.fileno(),
                                     self._handle_events,
                                     ioloop.IOLoop.READ)

可以在代碼注釋中看到會根據每一個CPU一個IOLoop實例的方式處理,至於中間的產生隨機數是為什么,如果有人知道請告知我

在start()函數的最后可以看到add_handle將監聽套接口和_handle_events()函數注冊到了IOLoop中,這就是上面所講的HTTPServer處理連接的過程

總結:總結不想寫了,土逼Continue... 

 

  


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM