• [原]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... 

      

    文章属原创,转载请注明出处 联系作者: Email:zhangbolinux@sina.com QQ:513364476
  • 相关阅读:
    网络English word
    Top instruction significance and use sar command
    远程传输+用yum安装文件+make编译安装软件
    windows主机向虚拟机Linux传输过程的错误重重艰苦历程
    pair结构体数组
    set容器
    scanf多组样例输入
    贪心硬币
    补一下蛇形矩阵
    如何在eclipse运行asn
  • 原文地址:https://www.cnblogs.com/Bozh/p/2606765.html
Copyright © 2020-2023  润新知