tornado基础

Hello World Demo

from tornado.web import Application,RequestHandler  # tornado 的基础web框架模块
from  tornado.ioloop import IOLoop # tornado 的核心io循环模块,封装了Linux的epoll和BSD的kqueue


class IndexHandler(RequestHandler):
    """视图类"""
    def get(self,*args,**kwargs):
        self.write('hello world')

if __name__ == '__main__':
    # 路由映射
    app = Application([
        (r'/',IndexHandler)
    ])
    # 绑定监听端口
    # 注意:此时服务器并没有开启监听
    app.listen(8000)   # 监听端口
    # tornado.ioloop.IOLoop.current() 返回当前线程的IOLoop实例
    # start 开启循环
    IOLoop.current().start()

流程概述

服务端启动socket服务,托管给linux的epoll管理socket
浏览器通过tcp连接(三次握手),将本次socket托管在epoll
当用户发送请求携带数据时,本次socket产生一个信号

tornado的IOLoop实例会时刻与epoll询问当前是否有可执行的任务,当客户端socket产生信号,即携带数据,解析数据后通tronado的web中的路由映射执行其对应的函数

httpserver

from tornado.web import Application,RequestHandler  # tornado 的基础web框架模块
from  tornado.ioloop import IOLoop  # tornado 的核心io循环模块,封装了Linux的epoll和BSD的kqueue
from  tornado.httpserver import HTTPServer

class IndexHandler(RequestHandler):
    """视图类"""
    def get(self,*args,**kwargs):
        self.write('hello world')

if __name__ == '__main__':
    app = Application([
        (r'/',IndexHandler)
    ])
    
    # 实例化http服务器对象并绑定端口
    httpServer = HTTPServer(app)
    httpServer.listen(8000)
    
    # tornado.ioloop.IOLoop.current() 返回当前线程的IOLoop实例
    # start 开启循环
    IOLoop.current().start()

单进程与多进程

tornado默认启动的是单进程

开启多进程

import tornado.web
import tornado.ioloop
import tornado.httpserver

class IndexHandler(RequestHandler):
    def get(self,*args,**kwargs):
        self.write('hello world')
        
if __name__ == '__main__':
    app = tornado.web.Application([
        (r'/',IndexHandler)
    ])
    
    httpServer = tornado.httpserver.HTTPServer(app)
    
    httpServer.bind(8000)
    httpServer.start(5)  # 默认开启1个进程,指定开启5个,None或者小于等于0默认开启cpu核心数的个数
    tornado.ioloop.IOLoop.current().start()

注意:

虽然tornado为我们提供了一次性启动多个进程的方式,但是由于一些问题,不建议以上方式启动多进程,而是使用命令行手动启动多进程,这样还能绑定不同的端口

为什么不通过代码启动多进程?

1.每个子进程都会从父进程中复制一份IOLoop的实例,如果在创建子进程前修改了IOLoop会影响所有的子进程
2.所有的进程都是由一个命令启动的,无法做到在不停止服务的情况下修改其中一个进程的代码
3.所有进程共享一个端口,想要分别监控很困难

options

tornado提供了一个tornado.options模块用以全局参数的定义存储,定义,修改

参数介绍

from tornado.options import define,options

# 方法原型
define(
    name,                  # 属性名,必须保证其唯一性,否则报Option %r already defined in %s"错误
    default=None,          # 设置选项的默认值
    type=None,             # 设置选项变量的类型,从命令行或配置文件导入参数时tornado会根据类型转换值,如果没有设置type 会根据default值进行转换,如果没设置default不会转换
    help=None,             # 帮助文本
    metavar=None,
    multiple=False,        # 允许多个值 如 list tupple  multiple=True
    group=None, 
    callback=None
)

# 定义变量
define('port',default=8000,type=int)
define('list',default=[],type=int,multiple=True)

# 调用变量
options.prot    # 全局的options对象,所有定义号的选项变量都会作为该对象的属性
options.list

# 获取参数的方法
options.parse_command_line()     # 将命令行的参数传到define中定义,

命令行启动实例

import tornado.web 
import tornado.ioloop  
import tornado.httpserver
from tornado.options import define,options

# 定义变量
define('port',default=8000,type=int)
define('list',default=[],type=int,multiple=True)

class IndexHandler(tornado.web.RequestHandler):
    """视图类"""
    def get(self,*args,**kwargs):
        self.write('hello world')

if __name__ == '__main__':
    # 转换命令行参数,并保存到options对象中,需要通过命令行启动文件
    options.parse_command_line()
    
    print(options.list)
    
    app = tornado.web.Application([
        (r'/',IndexHandler)
    ])
    
    httpServer = tornado.httpserver.HTTPServer(app)
    httpServer.listen(options.port)
    
    tornado.ioloop.IOLoop.current().start()

shell启动入口文件

python xxx.py --port=8080 --list=1,2,3,4

# 此时启动的就是在8080端口了

配置文件启动实例

config #####没有后缀

options.parse_config_file(path)    # 从配置文件导入参数,path是配置文件的路径

shell启动入口文件

python xxx.py

此时我们的配置文件仍需要严格按照python的语法书写,不支持字典类型

配置文件

刚开始使用的是options方式,后来改成py包的方式配置的

config.py

options = {
	'prot':8080,
        'list':[1,2,3]
}

入口文件

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

class IndexHandler(RequestHandler):
    """视图类"""
    def get(self,*args,**kwargs):
        self.write('hello world')

if __name__ == '__main__':
    
    print(config.options.list)
    
    app = tornado.web.Application([
        (r'/',IndexHandler)
    ])
    
    httpServer = tornado.httpserver.HTTPServer(app)
    httpServer.listen(config.options.port)
    
    tornado.ioloop.IOLoop.current().start()

配置文件加载到Application对象中

application.py

import config   # 导入模块
import views
from tornado.web import Application
class Application(Application):
    def __init__(self):
        handler = [
            (r'/',views.index.IndexHandle)
        ]
        super().__init__(handler,**config.settings)

基本配置参数

settings.py

BASE_DIRS = os.path.dirname(__file__)

# 配置
settings = {
    # 静态文件目录
    'static_path' : os.path.join(BASE_DIRS,'static'),
    # 模板目录
    'template_path' : os.path.join(BASE_DIRS,'template'),
    'debug':True   # 默认为false,即生产模式,项目上线需要改成false
    'autoreload':True # 只有自动重启的功能,需要注释debug配置 

    # 秘钥字符串配置
    'cookie_secret':'DL93JQb8Qv++5iia76G5a4kxJ4RnHUDjk7iRZJC9GP8='
   
    # xsrf保护
    'xsrf_cookies':True
    
    # 用户认证失败映射的路由
    'login_url':'/login'
}

# 生成随机字符串
import base64
import uuid
base64.b64encode(uuid.uuid4().bytes + uuid.uuid4().bytes)
### b'DL93JQb8Qv++5iia76G5a4kxJ4RnHUDjk7iRZJC9GP8='

参数描述

debug:True的特性
    1.tornado应用会监控源代码文件中如果有保存改动时,会重启服务器,可以减少手动重启次数,从而提高开发效率
    2.当服务器改动后,访问发生错误时,不会重启服务,需手动重启
    3.取消缓存编译的模板    'compiled_template_cache':True  # debug为True时,此参数为fasle 不缓存,可单独配置
    4.取消缓存静态文件的hash值  'static_hash_cache':True    # debug为True时,此参数为false不缓存,可单独配置
    5.提供追踪信息 'serve_traceback':True # debug为True时,此参数为True,提供追踪信息,可单独配置

logging日志

当使用parse_command_line 或者parse_config_file 时 tornado会默认开启logging模块功能,向终端输出一些打印信息

关闭日志

入口文件代码控制日志关闭

from tornado.options import options
if __name__ == '__main__':
    # 第一行就关闭
	options.logging=None

命令行关闭

python xx.py --logging=none

关闭cmd后服务会关闭,当项目上线时,启动入口文件都是启动的守护进程,即后台运行,关闭终端也不会断开服务

shell

nohup python xxx.py &

路由系统

固定传参

urls.py

import views
from tornado.web import Application
class Application(Application):
    def __init__(self):
        handler = [
            (r'/',views.index.IndexHandle,{'world1':1,'world2':2})
        ]
        super().__init__(handler,**config.settings)

view(传参)

from tornado.web import RequestHandler
class IndexHandler(RequestHandler):
    """视图类"""
    def initialize(self,world1,world2):
        self.world1 = world1
        self.world2 = world2
        
    def get(self,*args,**kwargs):
        print(self.world1,self,world2)
        self.write('hello world')

反向解析

urls.py

from tornado.web import Application
from tornado.web import url
from views import index

class Application(Application):
    def __init__(self):
        handler = [
            url(r'/',index.IndexHandler,name='index')
        ]
        super().__init__(handler,**config.settings)

views

from tornado.web import RequestHandler
class IndexHandler(RequestHandler):
    """视图类"""
    def initialize(self):
        # 获取路由匹配时{}中传递的参数
        pass
        
    def get(self,*args,**kwargs):
        # 根据name获取url路径
        self.reverse_url('index')
        self.write('hello world')

RequestHandler

url携带参数

无名分组

url(r'/article/(\w+)/(\w+)',index.ArticleHandler,name='index')

获取参数views

from tornado.web import RequestHandler

class ArticleHandler(RequestHandler):
    """视图类"""
    
    def get(self,h1,h2,h3,*args,**kwargs):
        """通过get方法的位置传参"""
        print(h1,h2,h3)
        self.write('hello world')

命名分组

url(r'/article/(?P<page>\w+)/(\w+)',index.ArticleHandler,name='index')

from tornado.web import RequestHandler

class ArticleHandler(RequestHandler):
    """视图类"""
    
    def get(self,page,h2,h3,*args,**kwargs):
        """通过get方法的位置传参"""
        print(page,h2,h3)
        self.write('hello world')

get方法传递参数

http://www.xxx.py/article?a=1&b=2
    
http://www.xxx.py/article?a=1&a=2

路由匹配

url(r'/article',index.ArticleHandler,name='index')

view

from tornado.web import RequestHandler

class ArticleHandler(RequestHandler):
    """视图类"""
    
    def get(self,*args,**kwargs):
        """通过get方法的位置传参"""
        # 多个同名参数只取最后一个,如果参数不存在,返回default定义的值,如果没定义default,会抛出		   tornado.MissingArgumentError,strip 是否过滤两遍的空格
        self.get_query_argument(self, name, default=_ARG_DEFAULT, strip=True)
        
        a = self.get_query_argument('a')
        
        # 多个同名参数
        self.get_query_arguments(self, name, strip=True)
        art_list = self.get_query_arguments(a)
        print(art_list[1])
        self.write('hello world')

post方法传递参数

from tornado.web import RequestHandler

class ArticleHandler(RequestHandler):
    """视图类"""
    def get(self,*args,**kwargs):
        # 配置好template_path后,直接返回html文件
        self.render('articl.html')
    
    def post(self,*args,**kwargs):
        username = self.get_body_argument('username')
        password = self.get_body_argument('password')
        hobby_list = self.get_body_arguments('hobby')
        
        self.write('hello world')

既可以获取get方法请求参数,又可以获取post方法请求参数

from tornado.web import RequestHandler

class ArticleHandler(RequestHandler):
    """视图类"""
    def get(self,*args,**kwargs):
        # 配置好template_path后,直接返回html文件
        username = self.get_argument('username')
        password = self.get_argument('password')
        hobby_list = self.get_arguments('hobby')
        
        self.render('articl.html')
    
    def post(self,*args,**kwargs):
        username = self.get_argument('username')
        password = self.get_argument('password')
        hobby_list = self.get_arguments('hobby')
        
        self.write('hello world')

request对象

# 参数:
method    # 请求方法
host      # 被请求的主机ip:端口
uri       # 请求的完整资源地址,包括路径和get查询参数部分
path      # 请求路径
query     # 请求参数
version   # 使用的http版本
headers   # 请求头的协议头,字典类型
body      # 请求体的数据
remote_ip # 客户端的ip地址
files     # 用户上传的文件 字典类型 
remote_ip # 当前用户ip

实例

from tornado.web import RequestHandler

class ArticleHandler(RequestHandler):
    """视图类"""
    def get(self,*args,**kwargs):
        self.request.method
        
        self.render('articl.html')
    


HTTPFile对象

tornado.httputil.HTTPFile

# 上传一个文件就是HTTPFile对象,对象属性
filename
body
content-type   # 文件类型

Response

# 返回数据
self.write()

程序先将数据存放到write_buffer缓冲区,再由缓冲区写入服务器
刷新缓冲的情况:
    1.程序结束,请求结束
    2.手动刷新
    3.缓冲区慢了
    4.遇到\n
   
# 返回模板
self.render('home.html',{'a':1})
# 刷新缓冲区
self.finish() # 手动刷新缓冲区,关闭当次请求通道,再write就没有意义了

# 利用write()写json数据
利用json模块dumps的json字符串的content-type类型为text/html
而使用write自动序列化的方式 content-type为application/json

# 设置响应头
self.set_header(name,value) #手动设置一个名为name 值为value的响应头
self.set_header('Content-Type','application/json;charset=UTF-8')

class IndexHandler(RequestHandler):
    def set_default_headers(self):
    	'''在get前调用,可以预先设置默认的headers,如果get中重新set_header后会被覆盖'''
        self.set_header('Content-Type','application/json;charset=UTF-8')
        
    def get(self):
        pass
    def post(self):
        pass

# 返回状态码
'''status_code:int类型状态码,reason 描述信息:str类型,如果reason=None,namestatus_code必须是规定好的正常状态码,导致服务器错误'''
self.set_status(status_code,reason=None)

# 重定向
self.redirect(url)


   
# 返回错误信息,tornado会调用self.write_error()
self.send_error(status_code=500,**kwargs)

# 用来处理send_error()抛出的错误信息,并返回给浏览器
def write_error(status_code,**kwargs)


class IndexHandler(RequestHandler):
    def write_error(self,status_code,**kwargs):
    	'''在get前调用,可以预先设置默认的headers,如果get中重新set_header后会被覆盖'''
        if status_code==500 :
            self.set_status(500)
            self.render('error500.html')
    def get(self):
        # 调用write_error()
        self.send_error(status=500)
    def post(self):
        pass

预留接口执行顺序

set_default_headers()   #设置响应头
initialize()   # 可接收路由匹配传的字典参数
prepare()      # 做预处理,在执行对应请求方法前调用,比如反爬策略,限流,认证权限之类的
write_error()
on_finish()    # 请求处理结束后调用,资源释放,日志处理,尽量不要做响应逻辑

在正常流程执行未抛出错误时的顺序

set_default_headers -- initialize --  prepare --  HTTP请求方法逻辑  -- on_finish

抛出错误时

set_default_headers -- initialize --  prepare --  HTTP请求方法逻辑  -- set_default_headers -- write_error  -- on_finish

模板

static_url创建了一个机遇文件内容的hash值,并将其以?拼接的方式当做查询参数添加到URL末尾,以便快速查询到该文件

路径配置

{'template_path' : os.path.join(BASE_DIRS,'template'),}

模板语法

变量

{{ num }}
{{num + 10}}
{{dict['num']}}   # 不能dict.num

流程控制

{% if num == 1 %}
{% elif %}
{% else %}
{% end %}

# for循环
{% for num in num_list %}
{% end %}

# while循环
{% while 条件 %}
{% end %}

反向解析

static_url()

# 示例:
<link href="{{static_url('css/home.css')}}"></link>

自定义函数

# 自定义函数,后端传函数名
{% function(100,10) %}

自动转义

# 转义
# tornado自动开启自动转义功能,防止了网站受到恶意攻击,如果遇到标签,script代码等
# 关闭自动转义
{% raw 变量名 %}        # 单行
{% autoescape None %}   # 当前文档全部关闭
{'autoescape':None}     # 配置中配置,所有请求相关资源都会关闭,禁用
{% escape(变量名) %}     # 在当前整个关闭自动转义后,可以对某个特定变量开启转义

block块

{% block con %}
{% end %}

模板继承

{% extends 'base.html' %}

静态文件

static_path

# static_path是静态文件的目录路径

# 静态文件配置
settings = {
    'static_path' : os.path.join(BASE_DIRS,'static'),
}

StaticFileHandler

静态页面

不通过路由匹配,直接请求静态资源
比如:http://www.baidu.com   
# 通过/路径进行路由匹配出的资源

http://www.baidu.com/index.html
# 直接请求项目根路径下的index.html这个文档

tornado.web.StaticFileHandler

可以通过这个类来映射静态页面,不用渲染数据的页面
# 路由匹配,写在最下面,否则会出现其他的路由匹配不到了
(r'/(.*)$',tornado.web.StaticFileHandler,{'path':os.path.join(setting.BASE_DIRS,'static/html'),'default_filename':'index.html'})

"""
http://www.best.com               --- 访问default_filename的默认资源
http://www.best.com/index.html    --- 访问index.html
"""

数据库数据交互

初始化数据库连接对象

import config   # 导入模块
import views
from tornado.web import Application
class Application(Application):
    def __init__(self):
        handler = [
            (r'/',views.index.IndexHandle)
        ]
        super().__init__(handler,**config.settings)
        self.db = 数据库连接对象(参数....)

视图使用

class Artile(RequestHandler):
    def get(self,*args,**kwargs)
    	self.application.db    # 数据库对象

cookie

普通cookie

# 设置cookie
self.set_cookie(name, value, domain=None, expires=None, path="/",expires_days=None, **kwargs)

# 获取cookie
self.get_cookie(self, name, default=None)
self.get_cookie(id, default='未登录')

# 清除cookie
# 并不是立即删除浏览器的cookie 而是给cookie值设置为空,并改变有效期,真正删除cookie是浏览器发现为空时,自动清除
self.clear_cookie(self, name, path="/", domain=None)


# 清除所有cookie
self.clear_all_cookies(self, path="/", domain=None)
        

参数说明

name    # cookie名 
value   # cookie值
domain  # 提交cookie时匹配的ip
expires # cookie的有效期,时间戳,时间元祖,datatime UTC时间
path    # 提交cookie时匹配的路径
expires_days # 设置有效期时间,优先级低于expires

实际上是通过设置响应头实现的
# 同理
self.set_header('Set-Cookie',cookie值)

安全cookie

由于cookie是存储在客户端的,很容易被篡改,tornado提供了对cookie进行混淆加密的方法,单不是完全安全,cookie中不要存储敏感性cookie
# cookie_secret 随机字符串,同理django的SECRET_KEY
# 生成随机字符串
import base64
import uuid
base64.b64encode(uuid.uuid4().bytes + uuid.uuid4().bytes)
### b'DL93JQb8Qv++5iia76G5a4kxJ4RnHUDjk7iRZJC9GP8='

# 配置文件配置
{'cookie_secret':'DL93JQb8Qv++5iia76G5a4kxJ4RnHUDjk7iRZJC9GP8='}

1624723253121

原型

# 设置安全cookie
self.set_secure_cookie(self, name, value,expires_days=30, version=None,**kwargs)

# 获取安全cookie
self.get_secure_cookie(self, name, value=None, max_age_days=31,min_version=None)
# max_age_days  --- 超过31天的过滤掉,根据expires_days+1计算
# return bytes类型


参数说明

# cookie
2|1:0|10:1624724193|3:ali|8:aGVsbG8=|b5cc0126960cd76c3d6583cb0ef42edfef1d7cc5354c1c077dd26dda1bfbc39e

# 安全cookie版本
2  --- # DEFAULT_SIGNED_VALUE_VERSION
1: --- # 后面数据的长度
0  --- # key_version
10:  ---# 后面数据的长度
1624724193 --- # 时间戳
ali --- # cookie名
aGVsbG8= --- # b64加密后的cookie值
签名  --- sha256加密后,通过秘钥字符串加盐后的字符串

cookie计数

访问频率限制,但会存在xsrf攻击

用户携带cookie访问频率
# 第一种方式
def prepare(self,*args,**kwargs):
    """
    执行视图逻辑之前
    """
    count = self.get_cookie('count',None)
    if not count:
        count = 1
    else:
        int(count) += 1
    self.set_cookie('count',count)
    # 判断在一定时间内用户的访问次数,如果频率太快就send_error(),需要根据用户进行隔离
    
#第二种方式:
对于相对安全的操作,一般不会将cookie计数放在prepare或者get方法中,一般是post方法,配合csrf_token 实现保护机制

xsrf保护机制

# xsrf  -- 跨站请求伪造

# 同源机制,原理是可以发送请求,服务器也能接受到,如果是get,但是浏览器会根据同源机制不会渲染本次请求数据,如果是post则会返回403,首先得开启xsrf保护

方式一:配置文件

// 开启xsrf_cookies 保护
{'xsrf_cookies':True}

# 配合模板中form表单应用
<form>
	{% module_xsrf_form_html() %}
</form>
"""django中是在前端模板中生成隐藏标签,值为token字符串,用户发送请求时,携带这个字符串,后端进行校验实现,而tornado中是通过设置cookie的方式实现_xsrf为cookie名,在关闭浏览器时失效,并且添加隐藏标签名为_xsrf"""

第二种方式:手动设置

# 或者通过js实现,form表单提交
# 1.将xsrf_token字符串,写入到前端的cookie中
self.xsrf_token

# css
<form>
<input id='xsrf' type='hidden' name='_xsrf' value=''></input>
</form>

# js
<script>
	function getCookie(name){
        var cookie = document.cookie.match("\\b"+name+"=([^;]*)\\b")
        return cookie ? cookie[1] : undefined
    }
	document.getElementByID('xsrf').value = getCookie('_xsrf')
</script>




方式三:ajax提交

# 1.首先引入jquery,并用反向解析
<script>
function login(){
    $.ajax({ 
        url: "test.html", 
        method:'POST',
        data:{},
        context: document.body, 
        success: function(){
        $(this).addClass("done");
        // 设置请求头
      	headers:{
            'X-XSRFToken':getCookie('_xsrf')
        }
    }
    );
}
</script>

访问主页时就设置xsrf

urls.py

from tornado.web import Application
from tornado.web import url
from views import index

class Application(Application):
    def __init__(self):
        handler = [
            url(r'/',index.IndexHandler,name='index')
        ]
        super().__init__(handler,**config.settings)

views.py

from tornado.web import StaticFileHandler

class IndexHandler(StaticFileHandler):
    def __init__(*args,**kwargs):
        super().__init__(*args,**kwargs)
        # 将xsrf字符串设置到浏览器的cookie中
        self.xsrf_token
# 通过ajax发送请求的时候还要携带_xsrf,上面代码ajax提交会实现此代码

登录认证

authenticated装饰器

from tornado.web import RequestHandler,authenticated

class LoginHandler(RequestHandler):
    def get(self,*args,**kwargs):
        next_url = self.get_argument('next','/')
        url = '/login?next=%s'%next_url
        self.render('login.html',{"next_url":next_url})
    def post(self,*args,**kwargs):
        next_url = self.get_argument('next','/')
        if username == 1 and pwd == 2:
            # 获取next请求参数
            # 重定向到之前访问的页面,并携带已登录的标识
            self.redirect(next_url+'?flag=logined')
        else:
            # 可能会取不到next,因为前段form表单的请求路径写死了,需要动态变化,请求login页面时就将next传给前端的form请求路径中
            self.redirect('/login?next=%s'%next_url)

# 访问home,验证是否登录,如果登录执行get,没登录重定向到login
class HomeHandler(RequestHandler):
    def get_current_user(self):
        """
        return True: 直接执行请求方法
        return False : 如果是get/head方法,重定向到配置文件中配置的login_url,并携带一个next请求参数表示是从哪个页面跳转过来的,然后再执行loginview的视图逻辑
        """
        # 获取标识,如果有则返回的是logined字符串,为True,如果没有则是返回None,则为false
        flag = self.get_argument('flag',None)
        return flag 
    
    @authenticated
    def get(self,*args,**kwargs):
        self.render('home.html')

同步与异步

# 同步
先执行任务1,任务1,执行完毕后执行任务2,如果遇到io操作一直等待到io完毕后

# 异步
先执行任务1,遇到io,切换cpu执行下一个任务遇到io,切换回任务1.

# 异步应用场景
ajax
tornado 基于异步,才会高效

回调函数实现异步

# 实现思路
对于耗时的操作,交给另一个线程做,主线程继续往下执行,当耗时操作结束后将结果反馈给我们

import time
import threading
def Iojob(callback):
    def run(callback):
        time.sleep(3)
        print('doing job')
        # 执行回调函数
        callback('返回的数据')
    threading.Thread(target=run,args=(callback,)).start()

def callback(data):
    """回调函数返回data数据
    """
    print(data)

def workA():
    print('worka')
    Iojob(callback)

def workB():
    print('workb')
    Iojob(callback)

def run():

    workA()
    workB()
if __name__ == '__main__':
    run()

协程实现异步

# 实现思路
根据协程保持状态的特性,当遇到yield关键字时遇到io 程序会被挂起, send 的时候会唤醒当前协程

import time
import threading
gen = None

def Iojob():
    def run():
        time.sleep(3)
        print('doing job')
        try:
            global gen
            gen.send('status')
        except StopIteration:
            pass
    threading.Thread(target=run).start()
    
def gen(func):
    def wrapper(*args,**kwargs):
        global gen
        gen = func(*args,**kwargs)
        next(gen)
    return wrapper

@gen
def workA():
    print('worka')
    res = yield Iojob()
    print('处理完的结果:',res)
    
def workB():
    print('workb')
    Iojob()

def run():
    workA()
    
    workB()
    

if __name__ == '__main__':
    run()

消除全局变量最终版

import time
import threading

def Iojob():
    time.sleep(3)
    print('doing job')
    yield 'status'
    
def gen(func):
    def warpper(*args,**kwargs):
        gen1 = func(*args,**kwargs)   # workA的生成器
        gen2 = next(gen1) # Iojob的生成器

        def run(gen2):
            res = next(gen2)
            try:
                gen1.send(res)  # 返回给workA数据
            except StopIteration:
                pass
        threading.Thread(target=run,args=(gen2,)).start()
    return warpper

@gen
def workA():
    print('worka')
    res = yield Iojob()
    print('处理完的结果:', res)


def workB():
    print('workb')
    Iojob()


def run():
    workA()
    workB()


if __name__ == '__main__':
    run()

tornado中的异步

因为epoll 主要是用来解决网络io的并发问题,所以tornado的异步也是最主要体现在网络的IO异步上,即异步web请求

AsyncHTTPClient

tornado.httpclient.AsyncHTTPClient  # tornado提供的异步web请求客户端,用来进行异步web请求
fetch(request,callback=None)   --- # 用于执行一个web请求,并异步响应,返回一个tornado.httpclient.HttpResponse对象
# request可以需要发送请求的url,也可以是一个tornado.httpclient.HTTPRequest对象,如果传入的是url,会自动编程一个HTTPReqeust对象

HTTPRequest

# http请求类
构造函数的参数
url      
method
headers
body   # http请求体

HTTPResponse

# http响应类
code  -- 状态码
reason  --状态码的描述
body   -- 响应的数据
error   -- 异常

示例

# 对第三方服务发起异步请求

class GetMessage(ReqeustHandler)
    def on_response(self,response):
        if response.error:
            self.send_error(500)
        else:
            data = response.body
            self.wirte(data)
        self.finish()
    
    @tornado.web.asynchronous  # 不关闭http通道
	def get(self,*args,**kwargs):
        # 创建客户端
        client = AsyncHTTPClient()
        # 发送请求,如果成功执行self.on_response回调函数
        client.fetch(url,self.on_response)
        
        # self.write('ok') 因为响应时异步的,如果现在返回给浏览器的话就会关闭通道,异步请求到的结果就没办法接收到数据了

协程异步

class GetMessage(ReqeustHandler)
	
    @tornado.gen.coroutine  # 协程异步装饰器,get请求整个挂起
    def get(self,*args,**kwargs):
        # 创建客户端
        client = AsyncHTTPClient()
        # 基于协程实现的io操作
        res = yield client.fetch(url)
        if res.error:
            self.send_error(500)
        else:
            self.write(data)
        
        # self.write('ok') 因为响应时异步的,如果现在返回给浏览器的话就会关闭通道,异步请求到的结果就没办法接收到数据了

将IO操作单独封装

class GetMessage(ReqeustHandler)
	
    @tornado.gen.coroutine  # 协程异步装饰器,get请求整个挂起
	def get(self,*args,**kwargs):
       
        # 基于协程实现的io操作
        res = yield self.get_data()
        self.write(res)
        
    @tornado.gen.coroutine
    def get_data(self):
        # 创建客户端
        client = AsyncHTTPClient()
        res = yield client.fetch(url)
       	if res.error:
            ret = {'ret':0}
        else:
            ret = res.body
        raise tornado.gen.Return(ret)

websocket

tornado.websocket 提供了WebSocketHandler基类进行通信
open()  	 # 当建立websocket连接后服务端触发这个方法

on_message()  #客户端发送message给服务器时服务端触发这个方法

on_close()    # 当websocket连接关闭之后服务端触发这个方法

write_message(message,birnary=False)  #向客户端发送消息,一般为字符串类型,如果是字典类型则,内部转成json串,如果birnary的值为False 则message以utf-8编码发送,反之则以二进制模式发送,可以发送任意字节码

close()       # 服务器关闭websocke连接
check_origin(origin) # 判断源origin,对于符合条件的请求允许连接,否则返回403,可以重写该方法来解决websocket的跨域连接问题

即时通讯后端代码示例

from tornado.websocket import WebSocketHandler
class ChatHandler(WebSocketHandler):
    users = []
    def open(self,*args,**kwargs):
    	# 1.建立socket连接后执行这个函数,表示该用户已经登录了
         # 2.登录后通知好友列表中的所有人
         self.users.append(self)
		for user in users:
    		user.write_message('%s已上线'%username)
    def on_message(self,message):
        # 将消息,广播给每一个用户
        for user in self.users:
            user.write('%s 说:%s'%(user,message))
    def on_close(self):
        self.users.remove(self)
		for user in users:
    		user.write_message('%s已上线'%username)
    def check_origin(origin):
        # 可以跨域
    	return True

<script>
    <!--建立websocket链接 -->
    var ws = new WebSocket('ws://10.0.145.117:8000/chat')
    # 后端发送数据即执行onmessage方法
    ws.onmessage = function(data){
        data.data
    }
    function sendMessage(){
        var msg = document.getElementById('message').value
        # 通过websocket发送数据
        ws.send(msg)
    }
</script>

django和tornado的区别?

# django中是在前端模板中生成隐藏标签,值为token字符串,用户发送请求时,携带这个字符串,后端进行校验实现,而tornado中是通过设置cookie的方式实现

posted @ 2021-07-05 17:24  河图s  阅读(104)  评论(0)    收藏  举报