[原]tornado源码分析系列(六)[HTTPServer详解]

强颜欢笑 提交于 2019-12-20 04:24:06

引言:上一章讲了关于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... 

 

  

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!