Tornado基础学习篇

1.1 Tornado是什么?

Tornado是使用Python编写的一个强大的、可扩展的Web服务器。它在处理严峻的网络流量时表现得足够强健,但却在创建和编写时有着足够的轻量级,并能够被用在大量的应用和工具中。

我们现在所知道的Tornado是基于Bret Taylor和其他人员为FriendFeed所开发的网络服务框架,当FriendFeed被Facebook收购后得以开源。不同于那些最多只能达到10,000个并发连接的传统网络服务器,Tornado在设计之初就考虑到了性能因素,旨在解决C10K问题,这样的设计使得其成为一个拥有非常高性能的框架。此外,它还拥有处理安全性、用户验证、社交网络以及与外部服务(如数据库和网站API)进行异步交互的工具。

延伸阅读:C10K问题

基于线程的服务器,如Apache,为了传入的连接,维护了一个操作系统的线程池。Apache会为每个HTTP连接分配线程池中的一个线程,如果所有的线程都处于被占用的状态并且尚有内存可用时,则生成一个新的线程。尽管不同的操作系统会有不同的设置,大多数Linux发布版中都是默认线程堆大小为8MB。Apache的架构在大负载下变得不可预测,为每个打开的连接维护一个大的线程池等待数据极易迅速耗光服务器的内存资源。

大多数社交网络应用都会展示实时更新来提醒新消息、状态变化以及用户通知,这就要求客户端需要保持一个打开的连接来等待服务器端的任何响应。这些长连接或推送请求使得Apache的最大线程池迅速饱和。一旦线程池的资源耗尽,服务器将不能再响应新的请求。

异步服务器在这一场景中的应用相对较新,但他们正是被设计用来减轻基于线程的服务器的限制的。当负载增加时,诸如Node.js,lighttpd和Tornodo这样的服务器使用协作的多任务的方式进行优雅的扩展。也就是说,如果当前请求正在等待来自其他资源的数据(比如数据库查询或HTTP请求)时,一个异步服务器可以明确地控制以挂起请求。异步服务器用来恢复暂停的操作的一个常见模式是当合适的数据准备好时调用回调函数。我们将会在第五章讲解回调函数模式以及一系列Tornado异步功能的应用。

自从2009年9月10日发布以来,TornadoTornado已经获得了很多社区的支持,并且在一系列不同的场合得到应用。除FriendFeed和Facebook外,还有很多公司在生产上转向Tornado,包括Quora、Turntable.fm、Bit.ly、Hipmunk以及MyYearbook等。

总之,如果你在寻找你那庞大的CMS或一体化开发框架的替代品,Tornado可能并不是一个好的选择。Tornado并不需要你拥有庞大的模型建立特殊的方式,或以某种确定的形式处理表单,或其他类似的事情。它所做的是让你能够快速简单地编写高速的Web应用。如果你想编写一个可扩展的社交应用、实时分析引擎,或RESTful API,那么简单而强大的Python,以及Tornado正是为你准备的!

tornado和其他的框架如django,flask相比优缺点

Django

优点:

  • 大和全(重量级框架)
  • 自带orm,template,view 
  • 需要的功能也可以去找第三方的app
  • 注重高效开发
  • 全自动化的管理后台(只需要使用起ORM,做简单的定义,就能自动生成数据库结构,全功能的管理后台)
  • session功能

缺点:

  • template不怎么好用(来自自身的缺点)
  • 数据库用nosql不方便(来自自身的缺点)
  • 如果功能不多,容易臃肿

Tornado

优点:

  • 少而精(轻量级框架)
  • 注重性能优越,速度快
  • 解决高并发(请求处理是基于回调的非阻塞调用)
  • 异步非阻塞
  • websockets 长连接
  • 内嵌了HTTP服务器
  • 单线程的异步网络程序,默认启动时根据CPU数量运行多个实例;利用CPU多核的优势
  • 自定义模块

缺点:

  • 模板和数据库部分有很多第三方的模块可供选择,这样不利于封装为一个功能模块

总结:

要性能, Tornado 首选;要开发速度,Django 和 Flask 都行,区别是 Flask 把许多功能交给第三方库去完成了,因此 Flask 更为灵活。

综上所述:

Django适合初学者或者小团队的快速开发,适合做管理类、博客类网站、或者功能十分复杂需求十分多的网站

Tornado适合高度定制,适合访问量大,异步情况多的网站

选择适合的才是最好的

1.2快速入门

这里我就不写安装步骤了,pip3 install tornado

hello,Tornado     从get请求例子开始

代码语言:javascript
复制
import tornado
from tornado import ioloop,httpserver
from tornado.web import RequestHandler,url
from tornado.options import define,options

tornado.options.define("port",type=int,default=8002,help="服务器客户端") #默认配置
class IndexHandler(RequestHandler):
def get(self):#get请求
self.write("hello Tornado")  #发送hello Tornado到缓冲区,可以发送多个,这里到最后会默认执行self.finish(),将所有缓冲区信息发送给报文
#入口文件
if __name__ == '__main__':
tornado.options.parse_command_line()
app = tornado.web.Application([(r"/",IndexHandler),],
debug=True) #路由映射
http_server = tornado.httpserver.HTTPServer(app)
http_server.listen(options.port)      #监听8002端口
tornado.ioloop.IOLoop.current().start() #开始循环监听

通过上面代码我们了解了tornado运行过程

tornado的基础web框架模块

RequestHandler
封装了对应一个请求的所有信息和方法,write(响应信息)就是写响应信息的一个方法;对应每一种http请求方式(get、post等),把对应的处理逻辑写进同名的成员方法中(如对应get请求方式,就将对应的处理逻辑写在get()方法中),当没有对应请求方式的成员方法时,会返回“405: Method Not Allowed”错误。

Application
Tornado Web框架的核心应用类,是与服务器对接的接口,里面保存了路由信息表,其初始化接收的第一个参数就是一个路由信息映射元组的列表;其listen(端口)方法用来创建一个http服务器实例,并绑定到给定端口(注意:此时服务器并未开启监听)

Tornado核心IOLoop循环模块

tornado的核心io循环模块,封装了Linux的epoll和BSD的kqueue,tornado高性能的基石。 以Linux的epoll为例,其原理如下图:

IOLoop.current() 返回当前线程的IOLoop实例。

IOLoop.start() 启动IOLoop实例的I/O循环,同时服务器监听被打开。

小结:

  1. tornado高性能Web原理是利用Linux epoll IO多路模型和协程异步编程

  2. tornado Web框架核心模块是 web 和 核心事件循环模块是 IOLoop

1.3基础应用

上面的例子是get请求,下面我们再来看一下post请求如何做

Post请求

代码语言:javascript
复制
import tornado
from tornado import ioloop,httpserver
from tornado.web import RequestHandler,url
from tornado.options import define,options

tornado.options.define("port",type=int,default=8002,help="服务器客户端")
class IndexHandler(RequestHandler):
def post(self):
# a = self.get_body_arguments("a")
a = self.get_body_argument("a")
print(a)
self.write("a的值为%s"%a)
#入口文件
if __name__ == '__main__':
tornado.options.parse_command_line()
app = tornado.web.Application([(r"/",IndexHandler),],
debug=True)
http_server = tornado.httpserver.HTTPServer(app)
http_server.listen(options.port)
tornado.ioloop.IOLoop.current().start()

使用RequestHandler对象的get_argument方法来捕获请求查询字符串的的参数。

get_argument可以获取post请求和get请求的全部参数。

get_query_argument获取get请求的所有参数。

get_body_argument获取get请求的所有参数。

这里需要注意的是上面的三个方法如果遇到相同的查询字符串,会以最后查询到的字符串值为结果。

这里返回的是最后一个a的值,大家可能会有一问,如果我想获取所有a的值,改怎么做呢。

很简单,上面代码中有个方法我给注释了,没错,就是get_body_arguments,方法会返回所有的a的结果数组。

Post发送json请求

在tornado中post请求接收的参数只允许使用两种格式

但是如果想发送json请求,我们就需要自定义解析json文件的过程

代码语言:javascript
复制
# coding:utf-8
import tornado
from tornado import ioloop,httpserver
from tornado.web import RequestHandler,url
from tornado.options import define,options
import json,time
tornado.options.define("port",type=int,default=8002,help="服务器客户端")
class IndexHandler(RequestHandler):

def post(self):
    #json请求 解析
    if self.request.headers.get("Content-Type").startswith("application/json"): #判断请求格式
        json_data = self.request.body            #获取请求json
        json_data = str(json_data,'utf-8')       #转换字符串
        json_args = json.loads(json_data)          #转换字典
        self.write(json_args)

#入口文件
if name == 'main':
tornado.options.parse_command_line()
app = tornado.web.Application([(r"/",IndexHandler),
],
debug=True)
http_server = tornado.httpserver.HTTPServer(app)
http_server.listen(options.port)
tornado.ioloop.IOLoop.current().start()

request.headers.get方法用来获取报头信息,request.body获取body。

结果:

 Post请求上传文件

代码语言:javascript
复制
import tornado
from tornado import ioloop, httpserver
from tornado.web import RequestHandler, url
from tornado.options import define, options
import json, time

tornado.options.define("port", type=int, default=8002, help="服务器客户端")

class IndexHandler(RequestHandler):

def post(self):
    # 上传文件
    image1 = self.request.files["image1"][0]["body"]
    with open("image1.jpg",'wb')as f:  #文件保存
        f.write(image1)
        f.close()
    self.write("上传成功")

入口文件

if name == 'main':
tornado.options.parse_command_line()
app = tornado.web.Application([(r"/", IndexHandler)

                               ],
                              debug=True)
http_server = tornado.httpserver.HTTPServer(app)
http_server.listen(options.port)
tornado.ioloop.IOLoop.current().start()</code></pre></div></div><p>request.files方法可以接受上传文件,我们需要做的就是保存文件操作。</p><p>使用表单格式可以上传文件如图:</p><figure class=""><div class="rno-markdown-img-url" style="text-align:center"><div class="rno-markdown-img-url-inner" style="width:100%"><div style="width:100%"><img src="https://cdn.static.attains.cn/app/developer-bbs/upload/1723192157070281577.png" /></div></div></div></figure><h4 id="da2b1" name=""> </h4><h4 id="9bmpm" name="set_header%E6%96%B9%E6%B3%95%E8%87%AA%E5%AE%9A%E4%B9%89%E8%AF%B7%E6%B1%82%E5%A4%B4">set_header方法自定义请求头</h4><div class="rno-markdown-code"><div class="rno-markdown-code-toolbar"><div class="rno-markdown-code-toolbar-info"><div class="rno-markdown-code-toolbar-item is-type"><span class="is-m-hidden">代码语言:</span>javascript</div></div><div class="rno-markdown-code-toolbar-opt"><div class="rno-markdown-code-toolbar-copy"><i class="icon-copy"></i><span class="is-m-hidden">复制</span></div></div></div><div class="developer-code-block"><pre class="prism-token token line-numbers language-javascript"><code class="language-javascript" style="margin-left:0">import tornado

from tornado import ioloop, httpserver
from tornado.web import RequestHandler, url
from tornado.options import define, options
import json, time

tornado.options.define("port", type=int, default=8002, help="服务器客户端")

class IndexHandler(RequestHandler):
# 自定义header
def set_default_headers(self):
self.set_header("Content", "application/json; charset=UTF-8")
self.set_header("itcast", "python")

def get(self):

    # 返回json数据
    stu = {
        &#34;name&#34;: &#39;zhaozhi&#39;,
        &#34;age&#34;: 24,
        &#39;sex&#39;: &#34;男&#34;
    }
    self.write(stu)

入口文件

if name == 'main':
tornado.options.parse_command_line()
app = tornado.web.Application([(r"/", IndexHandler),

                               ],
                              debug=True)
http_server = tornado.httpserver.HTTPServer(app)
http_server.listen(options.port)
tornado.ioloop.IOLoop.current().start()</code></pre></div></div><p>函数set_default_headers函数是RequestHanlder的方法,用于设置全局默认header。</p><p>如果在get请求中出现set_header方法并且定义header属性和全局设置有相同,以get请求局部设置的header属性值为优先。</p><h4 id="ev9mt" name="Http%E7%8A%B6%E6%80%81%E7%A0%81">Http状态码</h4><p>可以使用RequestHandler类的set_status()方法自定义设置HTTP状态码。有很多定义好的状态码,例如:</p><p>404 Not Found</p><p>Tornado会在HTTP请求的路径无法匹配任何RequestHandler类相对应的模式时返回404(Not Found)响应码。</p><p>400 Bad Request</p><p>如果你调用了一个没有默认值的get_argument函数,并且没有发现给定名称的参数,Tornado将自动返回一个400(Bad Request)响应码。</p><p>405 Method Not Allowed</p><p>如果传入的请求使用了RequestHandler中没有定义的HTTP方法(比如,一个POST请求,但是处理函数中只有定义了get方法),Tornado将返回一个405(Methos Not Allowed)响应码。</p><p>500 Internal Server Error</p><p>当程序遇到任何不能让其退出的错误时,Tornado将返回500(Internal Server Error)响应码。你代码中任何没有捕获的异常也会导致500响应码。</p><p>200 OK</p><p>如果响应成功,并且没有其他返回码被设置,Tornado将默认返回一个200(OK)响应码。</p><p>set_status()方法有两个参数,第一个为状态码,第二个为响应结果(已有状态码可以不设置此参数,新的状态码必须设置)。这里就不给示例了,可以自己尝试写一下</p><p>你想使用自己的方法代替默认的错误响应,你可以重写write_error方法在你的RequestHandler类中。(检测到异常会抛出)</p><p>我们也可以在get方法中使用send_error方法来直接抛出异常。这两种方法和set_status方法参数一致。</p><div class="rno-markdown-code"><div class="rno-markdown-code-toolbar"><div class="rno-markdown-code-toolbar-info"><div class="rno-markdown-code-toolbar-item is-type"><span class="is-m-hidden">代码语言:</span>javascript</div></div><div class="rno-markdown-code-toolbar-opt"><div class="rno-markdown-code-toolbar-copy"><i class="icon-copy"></i><span class="is-m-hidden">复制</span></div></div></div><div class="developer-code-block"><pre class="prism-token token line-numbers language-javascript"><code class="language-javascript" style="margin-left:0">import tornado.httpserver

import tornado.ioloop
import tornado.options
import tornado.web

from tornado.options import define, options
define("port", default=8002, help="run on the given port", type=int)

class IndexHandler(tornado.web.RequestHandler):
def get(self):
greeting = self.get_argument('greeting', 'Hello')
self.write(greeting + ', friendly user!')
def write_error(self, status_code, **kwargs):
self.write("Gosh darnit, user! You caused a %d error." % status_code)

if name == "main":
tornado.options.parse_command_line()
app = tornado.web.Application(handlers=[(r"/", IndexHandler)])
http_server = tornado.httpserver.HTTPServer(app)
http_server.listen(options.port)
tornado.ioloop.IOLoop.instance().start()

redirect方法实现登录跳转

代码语言:javascript
复制
import tornado
from tornado import ioloop, httpserver
from tornado.web import RequestHandler, url
from tornado.options import define, options

tornado.options.define("port", type=int, default=8002, help="服务器客户端")

class IndexHandler(RequestHandler):

def get(self):
    self.write(&#34;登录成功&#34;)

#登录模块
class LoginHandler(RequestHandler):
def get(self):
self.write("<form method='post'><input type='submit' value='登录'></form> ")

def post(self):
    self.redirect(&#39;/&#39;)

入口文件

if name == 'main':
tornado.options.parse_command_line()
app = tornado.web.Application([(r"/", IndexHandler),
(r"/login", LoginHandler),
],
debug=True)
http_server = tornado.httpserver.HTTPServer(app)
http_server.listen(options.port)
tornado.ioloop.IOLoop.current().start()

不知道有小伙伴注意到了没,关于debug=True,调试模式,为什么修改代码后不会自动重启。

没找到原因(知道原因的小伙伴评论区解释一下),不过找到了另一个方法,import tornado.autoreload,导入模块就可以自动重启了,有兴趣的小伙伴可以取看看源码。这里就不解释了。

本文部分参考tornado中文文档 http://demo.pythoner.com/itt2zh/ch1.html

本章节到这里就结束了,谢谢大家阅读,感兴趣的话加个关注,后续还会继续更新。