bubbleeee

  博客园 :: 首页 :: 博问 :: 闪存 :: 新随笔 :: 联系 :: 订阅 订阅 :: 管理 ::

Tornado简介

Tornado和Django、Flask一样是Python中比较主流的web框架
 

 

 

Tornado安装

pip install tornado

Tornado框架的基本组成

由一个简单的例子开始

import tornado.ioloop
import tornado.web
 
class MainHandler(tornado.web.RequestHandler):
    def get(self):
        self.write("Hello, world")
 
def make_app():
    return tornado.web.Application([
        (r"/", MainHandler),
    ])
 
if __name__ == "__main__":
    app = make_app()
    app.listen(8888)
    tornado.ioloop.IOLoop.current().start()

执行python hello.py,打开浏览器访问http://localhost:8888/就可以看到服务器的正常输出Hello, world

一个tornado web服务器通常由四大组件组成。

  1. ioloop实例,它是全局的tornado事件循环,是服务器的引擎核心,示例中tornado.ioloop.IOLoop.current()就是默认的tornado ioloop实例。
  2. app实例,它代表着一个完成的后端app,它会挂接一个服务端套接字端口对外提供服务。一个ioloop实例里面可以有多个app实例,示例中只有1个,实际上可以允许多个,不过一般几乎不会使用多个。
  3. handler类,它代表着业务逻辑,我们进行服务端开发时就是编写一堆一堆的handler用来服务客户端请求。
  4. 路由表,它将指定的url规则和handler挂接起来,形成一个路由映射表。当请求到来时,根据请求的访问url查询路由映射表来找到相应的业务handler。

这四大组件的关系是,一个ioloop包含多个app(管理多个服务端口),一个app包含一个路由表,一个路由表包含多个handler。ioloop是服务的引擎核心,它是发动机,负责接收和响应客户端请求,负责驱动业务handler的运行,负责服务器内部定时任务的执行。

当一个请求到来时,ioloop读取这个请求解包成一个http请求对象,找到该套接字上对应app的路由表,通过请求对象的url查询路由表中挂接的handler,然后执行handler。handler方法执行后一般会返回一个对象,ioloop负责将对象包装成http响应对象序列化发送给客户端。

Tornado的异步非阻塞

Web框架分阻塞式异步非阻塞2种。

大多数的Web框架(Django、Flask、Bottle)都是阻塞式的,体现在1个请求到达服务端如果服务端未处理完该请求,后续请求一直等待;解决方案可以是开启多线程/多进程,多个线程提高并发等。

异步非阻塞就是在服务端结合IO多路复用select/poll/epoll模板,做到1个线程在遇到IO操作的情况下,还可以做一些其他的任务;Tornado默认是阻塞的同时也支持异步非阻塞功能;Tornado异步非阻塞=IO多路复用(循环检查socket是否发生变化)+协程(哪个有变化?就切换到那个socket!)

具体过程如下:

1.客户端发送请求,如果请求内容不涉及IO操作(连接数据、或者请求其他网站获取内容),服务端直接响应客户端;

2.如果请求内容涉及IO操作,服务端把本次连接的socket信息添加到socket监听列表中监听起来;然后去连接其它socket(数据库、其它站点),由于是不阻塞的,所以服务端把这次发送socket信息也监听起来;(一直循环监听,直到socket监听列表中的socket发生变化)

3.把socket全部监听之后,就可以去继续接收其它请求了,如果检测到socket监听列表中的socket有变化(有数据返回),找到对应socket响应数据,并从socket监听列表中剔除;

 

阻塞式请求示例:
class MainHandler(tornado.web.RequestHandler):
    def get(self):
        self.render("index.html", list_info=[11, 22, 33], key1='value1')
class AsyncHandler(tornado.web.RequestHandler):

    def get(self):
        import time
        time.sleep(20)
        self.write('666')

application = tornado.web.Application([
    (r"/index", MainHandler),
    (r"/async", AsyncHandler),
], **settings)

测试上面的代码,不难发现:先打开async页面,没有任何返回,一直显示在加载,此时访问index页面,发现index页面同时也在加载出不来。当async执行完,index页面也很快就返回了。

 非阻塞式请求示例
import tornado.ioloop
import tornado.web
from tornado.web import UIModule
from tornado import gen
from tornado.concurrent import Future
import time

class MainHandler(tornado.web.RequestHandler):
    def get(self):
        self.render("index.html", list_info=[11, 22, 33], key1='value1')

class AsyncHandler(tornado.web.RequestHandler):

    @gen.coroutine
    def get(self):
        future = Future()
        tornado.ioloop.IOLoop.current().add_timeout(time.time() + 20, self.doing)
        yield future

    def doing(self, *args, **kwargs):
        self.write('async')
        self.finish()

settings = {
    'template_path': 'tmp',
    'static_path': 'statics'
}

application = tornado.web.Application([
    (r"/index", MainHandler),
    (r"/async", AsyncHandler),
], **settings)

if __name__ == "__main__":
    application.listen(8888)
    tornado.ioloop.IOLoop.instance().start()

此时再执行上面的操作,就会发现当async阻塞等待返回的时候,其他网页是可以继续访问的。装饰器 + Future 实现了Tornado的异步非阻塞。

当发送GET请求时,由于方法被@gen.coroutine装饰且yield一个 Future对象,那么Tornado会等待,等待用户向future对象中放置数据或者发送信号,如果获取到数据或信号之后,就开始执行doing方法。异步非阻塞体现在当在Tornaod等待用户向future对象中放置数据时,还可以处理其他请求。

注意:在等待用户向future对象中放置数据或信号时,此连接是不断开的。

关于协程和asyncio

我们知道在程序在执行IO密集型任务的时候,程序会因为等待IO而阻塞,而协程作为一种用户态的轻量级线程,可以帮我们解决这个问题。协程拥有自己的寄存器上下文和栈。协程调度切换时,将寄存器上下文和栈保存,在调度回来的时候,恢复先前保存的寄存器上下文和栈。因此协程能保留上一次调用时的状态,即所有局部状态的一个特定组合。说白了就是,当协程遇到IO操作而阻塞时,立即切换到别的任务,如果操作完成则进行回调返回执行结果,提高了效率,同时这样也可以充分利用 CPU 和其他资源,这就是异步协程的优势,并且协程本质上是个单进程,相对于多进程来说,无需进程间上下文切换的开销,无需原子操作锁定及同步的开销,编程模型也非常简单。

在python2以及python3.3时代,使用协程还得基于greenlet或者gevent,greenlet机制的主要思想是:生成器函数或者协程函数中的yield语句挂起函数的执行,直到稍后使用next()或send()操作进行恢复为止。可以使用一个调度器循环在一组生成器函数之间协作多个任务,它的缺点是必须通过安装三方库进行使用,使用时由于封装特性导致性能有一定的流失。

终于在python3.4中,迎来了python的原生协程关键字:Async和Await,它们的底层基于生成器函数,使得协程的实现更加方便。Async 用来声明一个函数为异步函数,异步函数的特点是能在函数执行过程中挂起,去执行其他异步函数,等到挂起条件(假设挂起条件是sleep(5))消失后,也就是5秒到了再回来执行。Await 用来用来声明程序挂起,比如异步程序执行到某一步时需要等待的时间很长,就将此挂起,去执行其他的异步程序。

import time
import asyncio
async def job(t):  # 使用 async 关键字将一个函数定义为协程
    await asyncio.sleep(t)  # 等待 t 秒, 期间切换执行其他任务
    print('用了%s秒' % t)
async def main(loop):  # 使用 async 关键字将一个函数定义为协程
    tasks = [loop.create_task(job(t)) for t in range(1,3)]  # 创建任务, 不立即执行
    await asyncio.wait(tasks)  # 执行并等待所有任务完成
start = time.time()
loop = asyncio.get_event_loop()  # 建立 loop
loop.run_until_complete(main(loop))  # 执行 loop
loop.close()  # 关闭 loop

print(time.time()-start)

输出

用了1秒
用了2秒
2.0187768936157227

从运行结果可以看出,我们没有等待 job 1 执行结束再开始执行 job 2,而是 job 1 触发 await 的时候切换到了 job 2 。 这时 job 1 和 job 2 同时在执行 await asyncio.sleep(t),所以最终程序的执行时间取决于执行时间最长的那个 job,也就是 job 2 的执行时间:2 秒

 
 Tornado默认是同步阻塞机制,如果要激活异步非阻塞的特性,需要使用异步写法,上面一节介绍了装饰器的方法,同样可以使用async和await来进行协程的异步非阻塞任务。tornado兼容了python原生的异步io,那么带来的好处就是,几乎基于python异步io扩展的第三方库tornado都可以用。
import tornado.web
from tornado import gen
class IndexHandler(tornado.web.RequestHandler):
    def get(self):
        self.write('index')
async def doing():
    await gen.sleep(10)  # here are doing some things
    return 'Non-Blocking'
class NonBlockingHandler(tornado.web.RequestHandler):
    async def get(self):
        result = await doing()
        self.write(result)
application = tornado.web.Application([
    (r"/", IndexHandler),
    (r"/nonblocking", NonBlockingHandler),
])
if __name__ == "__main__":
    application.listen(8888)
    tornado.ioloop.IOLoop.instance().start()
 
 
posted on 2022-04-03 10:55  bubbleeee  阅读(58)  评论(0编辑  收藏  举报