Tornado框架入门教程

Tornado在知乎广为使用,当你用Chrome打开网页版本的知乎,使用开发者工具仔细观察Network里面的请求,就会发现有一个特别的状态码为101的请求,它是用浏览器的websocket技术和后端服务器建立了长连接用来接收服务器主动推送过来的通知消息。这里的后端服务器使用的就是tornado服务器。Tornado服务器除了可以提供websocket服务外,还可以提供长连接服务,HTTP短链接服务,UDP服务等。Tornado服务器由facebook开源,在掌阅的后端也广为使用。

这样一个强大的Tornado框架,究竟该如何使用,本文将带领读者循序渐进深入学习tornado作为web服务器的基础使用。

import tornado.ioloop
import tornado.webclass 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()

这是官方提供了Hello, World实例,执行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响应对象序列化发送给客户端。

同一个ioloop实例运行在一个单线程环境下。

阶乘服务

下面我们编写一个正常的web服务器,它将提供阶乘服务。也就是帮我们计算n!的值。服务器会提供阶乘的缓存,已经计算过的就存起来,下次就不用重新计算了。使用Python的好处就是,我们不用当心阶乘的计算结果会溢出,Python的整数可以无限大。

# fact.py
import tornado.ioloop
import tornado.webclass FactorialService(object):  # 定义一个阶乘服务对象def __init__(self):self.cache = {}   # 用字典记录已经计算过的阶乘def calc(self, n):if n in self.cache:  # 如果有直接返回return self.cache[n]s = 1for i in range(1, n):s *= iself.cache[n] = s  # 缓存起来return sclass FactorialHandler(tornado.web.RequestHandler):service = FactorialService()  # new出阶乘服务对象def get(self):n = int(self.get_argument("n"))  # 获取url的参数值self.write(str(self.service.calc(n)))  # 使用阶乘服务def make_app():return tornado.web.Application([(r"/fact", FactorialHandler),  # 注册路由])if __name__ == "__main__":app = make_app()app.listen(8888)tornado.ioloop.IOLoop.current().start()

执行python fact.py ,打开浏览器,键入http://localhost:8888/fact?n=50,可以看到浏览器输出了
608281864034267560872252163321295376887552831379210240000000000,如果我们不提供n参数,访问http://localhost:8888/fact,可以看到浏览器输出了400: Bad Request,告诉你请求错误,也就是参数少了一个。

使用Redis
--
上面的例子是将缓存存在本地内存中,如果换一个端口再其一个阶乘服务,通过这个新端口去访问的话,对于每个n,它都需要重新计算一遍,因为本地内存是无法跨进程跨机器共享的。

所以这个例子,我们将使用Redis来缓存计算结果,这样就可以完全避免重复计算。另外我们将不在返回纯文本,而是返回一个json,同时在响应里增加字段来说名本次计算来源于缓存还是事实计算出来的。另外我们提供默认参数,如果客户端没有提供n,那就默认n=1。

import json
import redis
import tornado.ioloop
import tornado.webclass FactorialService(object):def __init__(self):self.cache = redis.StrictRedis("localhost", 6379)  # 缓存换成redis了self.key = "factorials"def calc(self, n):s = self.cache.hget(self.key, str(n))  # 用hash结构保存计算结果if s:return int(s), Trues = 1for i in range(1, n):s *= iself.cache.hset(self.key, str(n), str(s))  # 保存结果return s, Falseclass FactorialHandler(tornado.web.RequestHandler):service = FactorialService()def get(self):n = int(self.get_argument("n") or 1)  # 参数默认值fact, cached = self.service.calc(n)result = {"n": n,"fact": fact,"cached": cached}self.set_header("Content-Type", "application/json; charset=UTF-8")self.write(json.dumps(result))def make_app():return tornado.web.Application([(r"/fact", FactorialHandler),])if __name__ == "__main__":app = make_app()app.listen(8888)tornado.ioloop.IOLoop.current().start()

当我们再次访问http://localhost:8888/fact?n=50,可以看到浏览器输出如下
{"cached": false, "fact": 608281864034267560872252163321295376887552831379210240000000000, "n": 50}
,再刷新一下,浏览器输出{"cached": true, "fact": 608281864034267560872252163321295376887552831379210240000000000, "n": 50},可以看到cached字段由true编程了false,表明缓存确实已经保存了计算的结果。我们重启一下进程,
再次访问这个连接,观察浏览器输出,可以发现结果的cached依旧等于true。说明缓存结果不再是存在本地内存中了。

圆周率计算服务
--
接下来我们再增加一个服务,计算圆周率,圆周率的计算公式有很多种,我们用它最简单的。

我们在服务里提供一个参数n,作为圆周率的精度指标,n越大,圆周率计算越准确,同样我们也将计算结果缓存到Redis服务器中,避免重复计算。

# pi.py
import json
import math
import redis
import tornado.ioloop
import tornado.webclass FactorialService(object):def __init__(self, cache):self.cache = cacheself.key = "factorials"def calc(self, n):s = self.cache.hget(self.key, str(n))if s:return int(s), Trues = 1for i in range(1, n):s *= iself.cache.hset(self.key, str(n), str(s))return s, Falseclass PiService(object):def __init__(self, cache):self.cache = cacheself.key = "pis"def calc(self, n):s = self.cache.hget(self.key, str(n))if s:return float(s), Trues = 0.0for i in range(n):s += 1.0/(2*i+1)/(2*i+1)s = math.sqrt(s*8)self.cache.hset(self.key, str(n), str(s))return s, Falseclass FactorialHandler(tornado.web.RequestHandler):def initialize(self, factorial):self.factorial = factorialdef get(self):n = int(self.get_argument("n") or 1)fact, cached = self.factorial.calc(n)result = {"n": n,"fact": fact,"cached": cached}self.set_header("Content-Type", "application/json; charset=UTF-8")self.write(json.dumps(result))class PiHandler(tornado.web.RequestHandler):def initialize(self, pi):self.pi = pidef get(self):n = int(self.get_argument("n") or 1)pi, cached = self.pi.calc(n)result = {"n": n,"pi": pi,"cached": cached}self.set_header("Content-Type", "application/json; charset=UTF-8")self.write(json.dumps(result))def make_app():cache = redis.StrictRedis("localhost", 6379)factorial = FactorialService(cache)pi = PiService(cache)return tornado.web.Application([(r"/fact", FactorialHandler, {"factorial": factorial}),(r"/pi", PiHandler, {"pi": pi}),])if __name__ == "__main__":app = make_app()app.listen(8888)tornado.ioloop.IOLoop.current().start()

因为两个Handler都需要用到redis,所以我们将redis单独抽出来,通过参数传递进去。另外Handler可以通过initialize函数传递参数,在注册路由的时候提供一个字典就可以传递任意参数了,字典的key要和参数名称对应。我们运行python pi.py,打开浏览器访问http://localhost:8888/pi?n=200,可以看到浏览器输出{"cached": false, "pi": 3.1412743276, "n": 1000},这个值已经非常接近圆周率了。

转载至:https://zhuanlan.zhihu.com/p/37382503

Tornado框架入门教程相关推荐

  1. c++框架有哪些_Java Mybatis框架入门教程_v20200726

    MyBatis 的前身是 Apache 的开源项目 iBatis.MyBatis 几乎可以代替 JDBC,是一个支持普通 SQL 查询,存储过程和高级映射的基于 Java 的优秀持久层框架.MyBat ...

  2. Egg框架入门教程合集之插件/工具/教程/专栏/开源项目

    Egg框架入门教程之示例合集 Awesome Egg.js 很棒的清单,精选了最好的Egg.js插件,工具,教程,文章等.欢迎公关! 内容 博客 文章 讲解 会议活动 外挂程式 应用领域 样板 构架 ...

  3. 80篇各ajax框架入门教程

    AJAX经典入门教程,入门实例,入门代码收藏. *VS2008 Ajax.NET快速入门教程* (2008-3-25) [本站原创]Ajax!?!入门教程之道 (2008-4-13) [本站原创]Aj ...

  4. 腾讯Web前端JX框架入门教程(一)

    什么是JX框架 JX框架(Javascript eXtension tools)是模块化的非侵入式Web前端框架,适用于Web Page和Web App项目的开发,特别适合构建和组织大规模.工业级的W ...

  5. python的前端和后端_python前端和后端数据交互,tornado框架入门,初学小试牛刀!...

    Python前端和后端是如何交互的,怎么用tornado框架快速搭建前端和后端数据交互? 前端与后端的数据交互,最常用的就是GET.POST,比较常用的用法是:提交表单数据到后端,后端返回json 前 ...

  6. 【Java学习路线之JavaWeb】Spring MVC框架入门教程

    文章目录 读者 阅读条件 MVC设计模式简介 JSP+JavaBean Servlet+JSP+JavaBean MVC优缺点 优点 缺点 Spring MVC是什么 Spring MVC优点 第一个 ...

  7. Mybatis框架入门教程

    文章目录 MyBatis是什么?它和hibernate的区别有哪些? MyBatis的工作原理 *MyBatis的核心组件 1.SqlSessionFactory及其常见创建方式 2.SqlSessi ...

  8. 【动力节点】springmvc框架入门教程-从入门到精通

    目录 一.Springmvc入门 1.Springmvc是什么 2.Springmvc处理流程 3.入门程序 二.springMVC架构 1.springMVC的架构图 2.架构流程 3.spring ...

  9. MMDetection框架入门教程(三):配置文件详细解析

      在上一篇博客中提到,MMDetection搭建训练算法只需要3个步骤:1) 准备数据集 2) 编写配置文件 3) 执行train.py文件开始训练.但上篇博客只是很简略的介绍了一下大体流程,本文将 ...

最新文章

  1. mysql 前台启动_从Windows命令行启动MySQL
  2. 关于react中setState的深入理解
  3. volatile对原子性、可见性、有序性的保证
  4. 转: 用css把图片转为灰色图
  5. Abaqus中施加移动车辆荷载(待整理)
  6. mysql 查询优化 Explain关键字 高性能mysql笔记
  7. free mybatis 不生效_2019BATJ面试题汇总详解:MyBatis+MySQL+Spring+Redis+多线程
  8. java 格式化字符串_Java入门 - 语言基础 - 14.String类
  9. 6 频率_6年了 AMD二代推土机CPU频率再次冲击8.2GHz
  10. 又被ESLint 调戏了!!! ESLint:Newline required at end of file but not found. eslint(eol-last) [12, 22]
  11. k8s相关面试问题_最常被问到的20道Kubernetes面试题
  12. CRC32/CRC16算法C#中的实现
  13. 埃森哲互动并购了56家广告公司
  14. JS实现逆波兰表达式
  15. html 怎么做图标在圆圈上旋转,纯CSS3图标旋转效果代码
  16. 1033 旧键盘打字 (20分)
  17. thinkpad重装系统不引导_联想ThinkPad T470重装系统U盘无法启动怎么办?进bios设置启动教程...
  18. MIT线性代数笔记十七讲 正交矩阵和施密特正交化
  19. 第十届蓝桥杯省赛再现(编程部分)
  20. 判断tvs能抗住多少千伏浪涌的依据_如何计算出TVS所能承受的浪涌电压

热门文章

  1. 酷狗mv php解析api接口,酷狗音乐API接口大全(40 个)
  2. 《吴军硅谷来信》笔记(不定期加更)
  3. python实现AC自动机
  4. 氨法烟气脱硫都有哪些工艺?
  5. 基于ESP32 蓝牙游戏手柄设计
  6. 【数据库通关之路】 MySQL 全路线学习知识点梳理(上)
  7. Excle基础用法积累
  8. Android---requires android.permission.READ_CONTACTS or android.permission.WRITE_CONTACTS
  9. ERA5 积雪 降雪 区别_雪深超10厘米!黑龙江漠河迎入秋以来最大降雪
  10. python wordcloud详解_wordcloud详解