Python web框架 Tornado(一)异步非阻塞使用以及原理

Python web框架 Tornado(二)异步非阻塞

异步非阻塞

阻塞式:(适用于所有框架,Django,Flask,Tornado,Bottle)
  一个请求到来未处理完成,后续一直等待
  解决方案:多线程,多进程

异步非阻塞(存在IO请求): Tornado(单进程+单线程)
  使用异步非阻塞,需要遵循Tornado框架内部规则,gen

  多个连接请求,连接给服务端,如果是有异步非阻塞的话,服务端会接收所有的请求交由后台处理,等待其他链接的同时,原先连接不断开,直至返回后台处理完成的结果!
  外部请求,连接服务端 或在select中创建Future对象,然后服务端再把请求交给业务处理平台,此时select监听的列表中又会生成一个socket对象,当业务平台对请求处理完成之后就会把信息返回到服务端的select监听列表中,同时对这个Future对象赋值,用于标记服务端是否要给客户端返回请求信息。

  执行流程,本质上都是返回一个future对象,如果对这个对象被set_result了就返回值,否则就是夯住,一直保持连接,不终止请求。

1、基本使用

装饰器 + Future 从而实现Tornado的异步非阻塞

1

2

3

4

5

6

7

8

9

10

11

12

13

14

class AsyncHandler(tornado.web.RequestHandler):

  

    @gen.coroutine

    def get(self):

        future = Future()

        future.add_done_callback(self.doing)

        yield future

        # 或

        # tornado.ioloop.IOLoop.current().add_future(future,self.doing)

        # yield future

  

    def doing(self,*args, **kwargs):

        self.write('async')

        self.finish()

  当发送GET请求时,由于方法被@gen.coroutine装饰且yield 一个 Future对象,那么Tornado会等待,等待用户向future对象中放置数据或者发送信号,如果获取到数据或信号之后,就开始执行doing方法。

  异步非阻塞体现在当在Tornaod等待用户向future对象中放置数据时,还可以处理其他请求。

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

2、同步阻塞和异步非阻塞对比

复制代码

class SyncHandler(tornado.web.RequestHandler):

    def get(self):
        self.doing()
        self.write('sync')

    def doing(self):
        time.sleep(10)

复制代码

复制代码

class AsyncHandler(tornado.web.RequestHandler):
    @gen.coroutine
    def get(self):
        future = Future()
        tornado.ioloop.IOLoop.current().add_timeout(time.time() + 5, self.doing)
        yield future


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

复制代码

3、httpclient类库

  Tornado提供了httpclient类库用于发送Http请求,其配合Tornado的异步非阻塞使用。

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

27

28

29

30

31

32

33

34

35

36

37

38

39

#!/usr/bin/env python

# -*- coding:utf-8 -*-

  

  

import tornado.web

from tornado import gen

from tornado import httpclient

  

# 方式一:

class AsyncHandler(tornado.web.RequestHandler):

    @gen.coroutine

    def get(self*args, **kwargs):

        print('进入')

        http = httpclient.AsyncHTTPClient()

        data = yield http.fetch("http://www.google.com")

        print('完事',data)

        self.finish('6666')

  

# 方式二:

# class AsyncHandler(tornado.web.RequestHandler):

#     @gen.coroutine

#     def get(self):

#         print('进入')

#         http = httpclient.AsyncHTTPClient()

#         yield http.fetch("http://www.google.com", self.done)

#

#     def done(self, response):

#         print('完事')

#         self.finish('666')

  

  

  

application = tornado.web.Application([

    (r"/async", AsyncHandler),

])

  

if __name__ == "__main__":

    application.listen(8888)

    tornado.ioloop.IOLoop.instance().start()

复制代码

#!/usr/bin/env python
# -*- coding:utf-8 -*-
"""
需要先安装支持异步操作Mysql的类库: 
    Tornado-MySQL: https://github.com/PyMySQL/Tornado-MySQL#installation
    
    pip3 install Tornado-MySQL

"""

import tornado.web
from tornado import gen

import tornado_mysql
from tornado_mysql import pools

POOL = pools.Pool(
    dict(host='127.0.0.1', port=3306, user='root', passwd='123', db='cmdb'),
    max_idle_connections=1,
    max_recycle_sec=3)


@gen.coroutine
def get_user_by_conn_pool(user):
    cur = yield POOL.execute("SELECT SLEEP(%s)", (user,))
    row = cur.fetchone()
    raise gen.Return(row)


@gen.coroutine
def get_user(user):
    conn = yield tornado_mysql.connect(host='127.0.0.1', port=3306, user='root', passwd='123', db='cmdb',
                                       charset='utf8')
    cur = conn.cursor()
    # yield cur.execute("SELECT name,email FROM web_models_userprofile where name=%s", (user,))
    yield cur.execute("select sleep(10)")
    row = cur.fetchone()
    cur.close()
    conn.close()
    raise gen.Return(row)


class LoginHandler(tornado.web.RequestHandler):
    def get(self, *args, **kwargs):
        self.render('login.html')

    @gen.coroutine
    def post(self, *args, **kwargs):
        user = self.get_argument('user')
        data = yield gen.Task(get_user, user)
        if data:
            print(data)
            self.redirect('http://www.oldboyedu.com')
        else:
            self.render('login.html')


application = tornado.web.Application([
    (r"/login", LoginHandler),
])

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

复制代码

异步非阻塞渐进学习代码+笔记注释

复制代码

import tornado.ioloop
import tornado.web
from tornado.web import RequestHandler
from tornado.httpserver import HTTPServer

# 单线程操作,请求来排队等待,顺序执行
#人为干预模拟IO设置 sleep10秒
class IndexHandler(RequestHandler):
    def get(self):
        print('开始')
        import time
        time.sleep(10)
        self.write("Hello, world")
        print('结束')

application = tornado.web.Application([
    (r"/index", IndexHandler),
])


if __name__ == "__main__":
    # 单进程单线程
    application.listen(8888)
    tornado.ioloop.IOLoop.instance().start()

    # 利用多进程 实现
    # server = HTTPServer(application)
    # server.bind(8888)
    # server.start(4)  # Forks multiple sub-processes
    # tornado.ioloop.IOLoop.current().start()

复制代码

复制代码

import tornado.ioloop
import tornado.web
from tornado.web import RequestHandler
from tornado import gen # 执行异步IO 导入gen模块
from tornado.concurrent import Future # 执行异步IO导入 Future模块,引用Future对象
import time

# 单线程 实现异步非阻塞操作!所有的连接请求不等待直接执行
class IndexHandler(RequestHandler):
    @gen.coroutine # 异步IO 固定写法,在请求上以装饰器的形式添加
    def get(self):
        print('开始')
        future = Future() #创建 Future() 对象
        tornado.ioloop.IOLoop.current().add_timeout(time.time() + 10, self.doing) #给当前的客户端添加超时时间,固定写法
        yield future # yield 返回 Future() 对象 IO操作的固定写法

    # 操作完成之后,需要执行的回调函数,一般是用于给请求返回消息
    def doing(self, *args, **kwargs):
        self.write('async') # 返回消息
        self.finish() #结束连接


application = tornado.web.Application([
    (r"/index", IndexHandler),
])


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

    # 多进程
    # server = HTTPServer(application)
    # server.bind(8888)
    # server.start(4)  # Forks multiple sub-processes
    # tornado.ioloop.IOLoop.current().start()

复制代码

复制代码

import tornado.ioloop
import tornado.web
from tornado.web import RequestHandler
from tornado import gen
from tornado.concurrent import Future
import time
from tornado import httpclient #针对HTTP请求进行异步非阻塞处理的模块

# 针对API接口 HTTP请求 实现异步非阻塞
class IndexHandler(RequestHandler):
    @gen.coroutine
    def get(self):
        print('收到订单')
        http = httpclient.AsyncHTTPClient() #创建 执行异步非阻塞 客户端
        yield http.fetch("http://www.github.com", self.done) # 固定写法 请求对某个API接口(url地址)传递消息,处理完毕执行回调函数

    #请求处理完毕,执行的回调函数。
    def done(self, response):
        self.write('订单成功') # 给请求返回的信息
        self.finish() # 断开连接

application = tornado.web.Application([
    (r"/index", IndexHandler),
])


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

    # server = HTTPServer(application)
    # server.bind(8888)
    # server.start(4)  # Forks multiple sub-processes
    # tornado.ioloop.IOLoop.current().start()

复制代码

复制代码

import tornado.ioloop
import tornado.web
from tornado.web import RequestHandler
from tornado import gen
from tornado.concurrent import Future
import time
from tornado import httpclient

fu = None
class IndexHandler(RequestHandler):
    @gen.coroutine
    def get(self):
        global fu
        print('疯狂的追求')
        fu = Future() # 创建Future对象,建立连接,如果没有人改变状态,请求就会永久存在连接不断开,除非Future() 对象被赋值或是反生改变
        # fu.set_result("") # 给Future 对象赋值,fu发生变化,返回请求。
        fu.add_done_callback(self.done)# 给fu添加要执行的回调函数
        yield fu

    def done(self, response):
        self.write('终于等到你')
        self.finish()

class TestHandler(RequestHandler):
    def get(self):
        fu.set_result(666) # 给future对象赋值,用以改变连接状态,返回消息 (注意:返回的内容就是 result的值)
        self.write('我只能帮你到这里了')

application = tornado.web.Application([
    (r"/index", IndexHandler),
    (r"/test", TestHandler),
])


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

复制代码

复制代码

import tornado.ioloop
import tornado.web
from tornado.web import RequestHandler
from tornado import gen
from tornado.concurrent import Future
import time
from tornado import httpclient
from threading import Thread

def waiting(futher):
    #线程要执行处理的函数
    import time
    time.sleep(10)
    futher.set_result(666)


class IndexHandler(RequestHandler):
    @gen.coroutine
    def get(self):
        global fu
        print('疯狂的追求')
        fu = Future()
        fu.add_done_callback(self.done)

        thread = Thread(target=waiting,args=(fu,)) # 开一个线程 自动给设置值,以自动给请求返回处理的消息
        thread.start()

        yield fu

    def done(self, response):
        self.write('终于等到你')
        self.finish()


application = tornado.web.Application([
    (r"/index", IndexHandler),
])


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

复制代码

复制代码

import tornado.web
import tornado.ioloop
from tornado import gen
import tornado_mysql

@gen.coroutine # 注意需要写上装饰器
def get_user(user):
    # 异步非阻塞,Task操作的函数,连接数据库,注意语法结构
    conn = yield tornado_mysql.connect(host='127.0.0.1', port=3306, user='root', passwd='123', db='tornadoTest',
                                       charset='utf8')
    cur = conn.cursor()
    # yield cur.execute("SELECT name,email FROM web_models_userprofile where name=%s", (user,))
    yield cur.execute("select sleep(10)")
    row = cur.fetchone()
    cur.close()
    conn.close()
    raise gen.Return(row) # 注意task函数的返回值


class LoginHandler(tornado.web.RequestHandler):
    def get(self, *args, **kwargs):
        self.render('login.html')

    @gen.coroutine
    def post(self, *args, **kwargs):
        user = self.get_argument('user')
        data = yield gen.Task(get_user, user) # 执行Task函数,内部还是返回future对象。Task函数上第一个参数是要执行的函数,第二个是参数
        if data:
            print(data)
            self.redirect('http://www.baidu.com')
        else:
            self.render('login.html')

    #原始方案,请求来了,连接数据库,等待操作完成,关闭连接!
    # def post(self, *args, **kwargs):
    #     user = self.get_argument('user')
    #     # 连接数据库: IO耗时
    #     # 查询语句: IO耗时
    #     # 获取结果
    #     data = {'id':1,'user':'alex'}
    #     if data:
    #         print(data)
    #         self.redirect('http://www.baidu.com')
    #     else:
    #         self.render('login.html')


application = tornado.web.Application([
    (r"/login", LoginHandler),
])

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

 

==================================================================================================

==================================================================================================

==================================================================================================

 

Tornado 和现在的主流 Web 服务器框架(包括大多数 Python 的框架)有着明显的区别:它是非阻塞式服务器,而且速度相当快。得利于其 非阻塞的方式和对 epoll 的运用,Tornado 每秒可以处理数以千计的连接,这意味着对于实时 Web 服务来说,Tornado 是一个理想的 Web 框架。

 

一、Tornado的两种模式使用

1.同步阻塞模式

由于doing中sleep10秒,此时其他连接将被阻塞,必须等这次请求完成后其他请求才能连接成功。

复制代码

 1 import tornado.ioloop
 2 import tornado.web
 3    
 4    
 5 class MainHandler(tornado.web.RequestHandler):
 6     def get(self):
 7         self.doing()
 8         self.write("Hello, world")
 9    
10     def doing(self):
11         time.sleep(10)
12 
13 
14 application = tornado.web.Application([
15     (r"/index", MainHandler),
16 ])
17    
18    
19 if __name__ == "__main__":
20     application.listen(8888)
21     tornado.ioloop.IOLoop.instance().start()

复制代码

 

 2.异步非阻塞模式

1、基本使用

装饰器 + Future 从而实现Tornado的异步非阻塞:

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

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

复制代码

 1 class AsyncHandler(tornado.web.RequestHandler):
 2     @gen.coroutine
 3     def get(self):
 4         future = Future()
 5         tornado.ioloop.IOLoop.current().add_timeout(time.time() + 5, self.doing)
 6         yield future
 7 
 8 
 9     def doing(self, *args, **kwargs):
10         self.write('async')
11         self.finish()

复制代码

 

2、httpclient类库

当服务器接到的请求需要向第三方服务器发送请求才能解决时,Tornado提供了httpclient类库用于发送Http请求,其配合Tornado的异步非阻塞使用。

 

复制代码

 1 import tornado.web
 2 from tornado import gen
 3 from tornado import httpclient
 4  
 5 # 方式一:
 6 class AsyncHandler(tornado.web.RequestHandler):
 7     @gen.coroutine
 8     def get(self, *args, **kwargs):
 9         print('进入')
10         http = httpclient.AsyncHTTPClient()
11         data = yield http.fetch("http://www.google.com")
12         print('完事',data)
13         self.finish('6666')
14  
15 # 方式二:
16 # class AsyncHandler(tornado.web.RequestHandler):
17 #     @gen.coroutine
18 #     def get(self):
19 #         print('进入')
20 #         http = httpclient.AsyncHTTPClient()
21 #         yield http.fetch("http://www.google.com", self.done)
22 #
23 #     def done(self, response):
24 #         print('完事')
25 #         self.finish('666')
26  
27  
28  
29 application = tornado.web.Application([
30     (r"/async", AsyncHandler),
31 ])
32  
33 if __name__ == "__main__":
34     application.listen(8888)
35     tornado.ioloop.IOLoop.instance().start() 

复制代码

 

 

二、Tornado异步非阻塞的原理

1、普通同步阻塞服务器框架原理

通过select与socket我们可以开发一个微型的框架,使用select实现IO多路复用监听本地服务端socket。当有客户端发送请求时,select监听的本地socket发生变化,通过socket.accept()得到客户端发送来的conn(也是一个socket),并将conn也添加到select监听列表里。当客户端通过conn发送数据时,服务端select监听列表的conn发生变化,我们将conn发送的数据(请求数据)接收保存并处理得到request_header与request_body,然后可以根据request_header中的url来匹配本地路由中的url,然后得到对应的view函数,然后将view的返回值(一般为字符串)通过conn发送回请求客户端,然后将conn关闭,并且移除select监听列表中的conn,这样一次网络IO请求便算结束。

 自定义同步阻塞框架

 

 

 

2、Tornado异步非阻塞实现原理

tornado通过装饰器 + Future 从而实现异步非阻塞。在view中yield一个future对象,然后再在发送相应数据前判断view函数返回来的数据类型,如果是字符串类型直接返回,如果是future对象,则将返回来的future对象添加到async_request_dict中,先不给客户端返回响应数据(此时可以处理其他客户端的连接请求),等future对象的result有值时再返回,还可以设置超时时间,在规定的时间过后返回响应数据。  !! 关键是future对象,future对象里有result属性,默认为None,当result有值时再返回数据。

复制代码

  1 import socket
  2 import select
  3 import time
  4 
  5 class HttpRequest(object):
  6     """
  7     用户封装用户请求信息
  8     """
  9     def __init__(self, content):
 10         """
 11 
 12         :param content:用户发送的请求数据:请求头和请求体
 13         """
 14         self.content = content
 15 
 16         self.header_bytes = bytes()
 17         self.body_bytes = bytes()
 18 
 19         self.header_dict = {}
 20 
 21         self.method = ""
 22         self.url = ""
 23         self.protocol = ""
 24 
 25         self.initialize()
 26         self.initialize_headers()
 27 
 28     def initialize(self):
 29 
 30         temp = self.content.split(b'\r\n\r\n', 1)
 31         if len(temp) == 1:
 32             self.header_bytes += temp
 33         else:
 34             h, b = temp
 35             self.header_bytes += h
 36             self.body_bytes += b
 37 
 38     @property
 39     def header_str(self):
 40         return str(self.header_bytes, encoding='utf-8')
 41 
 42     def initialize_headers(self):
 43         headers = self.header_str.split('\r\n')
 44         first_line = headers[0].split(' ')
 45         if len(first_line) == 3:
 46             self.method, self.url, self.protocol = headers[0].split(' ')
 47             for line in headers:
 48                 kv = line.split(':')
 49                 if len(kv) == 2:
 50                     k, v = kv
 51                     self.header_dict[k] = v
 52 
 53 class Future(object):
 54     def __init__(self,timeout=0):
 55         self.result = None
 56         self.timeout = timeout
 57         self.start = time.time()
 58 def main(request):
 59     f = Future(5)
 60     return f
 61 
 62 def index(request):
 63     return "indexasdfasdfasdf"
 64 
 65 
 66 routers = [
 67     ('/main/',main),
 68     ('/index/',index),
 69 ]
 70 
 71 def run():
 72     sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
 73     sock.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
 74     sock.bind(("127.0.0.1", 9999,))
 75     sock.setblocking(False)
 76     sock.listen(128)
 77 
 78     inputs = []
 79     inputs.append(sock)
 80 
 81     async_request_dict = {
 82         # 'socket': futrue
 83     }
 84 
 85     while True:
 86         rlist,wlist,elist = select.select(inputs,[],[],0.05)
 87         for r in rlist:
 88             if r == sock:
 89                 """新请求到来"""
 90                 conn,addr = sock.accept()
 91                 conn.setblocking(False)
 92                 inputs.append(conn)
 93             else:
 94                 """客户端发来数据"""
 95                 data = b""
 96                 while True:
 97                     try:
 98                         chunk = r.recv(1024)
 99                         data = data + chunk
100                     except Exception as e:
101                         chunk = None
102                     if not chunk:
103                         break
104                 # data进行处理:请求头和请求体
105                 request = HttpRequest(data)
106                 # 1. 请求头中获取url
107                 # 2. 去路由中匹配,获取指定的函数
108                 # 3. 执行函数,获取返回值
109                 # 4. 将返回值 r.sendall(b'alskdjalksdjf;asfd')
110                 import re
111                 flag = False
112                 func = None
113                 for route in routers:
114                     if re.match(route[0],request.url):
115                         flag = True
116                         func = route[1]
117                         break
118                 if flag:
119                     result = func(request)
120                     if isinstance(result,Future):
121                         async_request_dict[r] = result
122                     else:
123                         r.sendall(bytes(result,encoding='utf-8'))
124                         inputs.remove(r)
125                         r.close()
126                 else:
127                     r.sendall(b"404")
128                     inputs.remove(r)
129                     r.close()
130 
131         for conn in async_request_dict.keys():
132             future = async_request_dict[conn]
133             start = future.start
134             timeout = future.timeout
135             ctime = time.time()
136             if (start + timeout) <= ctime :
137                 future.result = b"timeout"
138             if future.result:
139                 conn.sendall(future.result)
140                 conn.close()
141                 del async_request_dict[conn]
142                 inputs.remove(conn)
143 
144 if __name__ == '__main__':
145     run()

复制代码

  • 1
    点赞
  • 9
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值