tonado Tornado全称Tornado Web Server,是一个用Python语言写成的Web服务器兼Web应用框架,由FriendFeed公司在自己的网站FriendFeed中使用,被Facebook收购以后框架在2009年9月以开源软件形式开放给大众。
Tornado与其他Web框架的区别以Django为代表的python web应用部署时采用wsgi协议与服务器对接(被服务器托管),而这类服务器通常都是基于多线程的,也就是说每一个网络请求服务器都会有一个对应的线程来用web应用(如Django)进行处理。
考虑两类应用场景
用户量大,高并发如秒杀抢购、双十一某宝购物、春节抢火车票
大量的HTTP持久连接
使用同一个TCP连接来发送和接收多个HTTP请求/应答,而不是为每一个新的请求/应答打开新的连接的方法。
对于HTTP 1.0,可以在请求的包头(Header)中添加Connection: Keep-Alive。
对于HTTP 1.1,所有的连接默认都是持久连接。
对于这两种场景,通常基于多线程的服务器很难应对。
对于前面提出的这种高并发问题,我们通常用C10K这一概念来描述。C10K—— Concurrently handling ten thousandconnections,即并发10000个连接。对于单台服务器而言,根本无法承担,而采用多台服务器分布式又意味着高昂的成本。如何解决C10K问题?
Tornado在设计之初就考虑到了性能因素,旨在解决C10K问题,这样的设计使得其成为一个拥有非常高性能的解决方案(服务器与框架的集合体)。
什么是 Python Tornado?
Python Tornado 是一个基于非阻塞 I/O 的 web 服务器框架,它使用了事件循环来处理并发请求。Tornado 适用于高并发的场景,如实时聊天、推送服务等。
安装
首先,确保已经安装了 Python 和 pip。然后,使用以下命令安装 Tornado:
pip3 install tornado
下面是一个简单的 Tornado Hello World 示例:
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()
在命令行中运行该脚本,然后在浏览器中访问 http://localhost:8888,将会看到 “Hello, World!” 的输出。
路由
Tornado 使用正则表达式来定义路由。在上面的示例中,(r"/", MainHandler) 定义了根路由,将请求映射到 MainHandler 类。
可以通过正则表达式捕获 URL 中的参数,并将其传递给处理函数。例如,(r"/user/(\d+)", UserHandler) 将匹配 /user/123,并将 123 作为参数传递给 UserHandler 类。
异步处理
Tornado 支持异步处理,以提高性能。在请求处理函数中,可以使用 tornado.gen 模块的装饰器来标记异步函数。以下是一个示例:
import tornado.ioloop
import tornado.web
import tornado.gen
class MainHandler(tornado.web.RequestHandler):
@tornado.gen.coroutine
def get(self):
result = yield self.async_function()
self.write(result)
@tornado.gen.coroutine
def async_function(self):
# 模拟一个异步操作
yield tornado.gen.sleep(1)
raise tornado.gen.Return("Async Function Result")
def make_app():
return tornado.web.Application([
(r"/", MainHandler),
])
if __name__ == "__main__":
app = make_app()
app.listen(8888)
tornado.ioloop.IOLoop.current().start()
在上面的示例中,get 方法使用了 tornado.gen.coroutine 装饰器来标记为异步函数。在异步函数中,可以使用 yield 关键字来暂停函数的执行,等待异步操作完成。
tornado中为什么不能写同步方法
tornado程序是基于单线程的协程模式,所以如果写同步方法会导致同时请求多个url时出现阻塞现象。
搭建项目:后台管理系统Tornado+Layui框架搭建教程
集成模块
用户管理:用于维护管理系统的用户,常规信息的维护与账号设置。
角色管理:角色菜单管理与权限分配、设置角色所拥有的菜单权限。
菜单管理:配置系统菜单,操作权限,按钮权限标识等。
职级管理:主要管理用户的职级。
岗位管理:主要管理用户担任职务。
部门管理:配置系统组织机构,树结构展现支持数据权限。
字典管理:对系统中常用的较为固定的数据进行统一维护。
配置管理:对系统的配置信息管理维护。
通知公告:系统通知公告信息发布维护。
软件信息
软件名称:DjangoAdmin敏捷开发框架Tornado+Layui版本
官网网址:https://www.djangoadmin.cn
文档网址:http://docs.tornado.layui.djangoadmin.cn
演示地址:http://manage.tornado.layui.djangoadmin.cn
核心组件
单图上传组件
{{ "avatar|头像|90x90|建议上传尺寸450x450|450x450"|image(data.avatar, "jpg|png|gif", 0) }}
多图上传组件
{{ "imgs|图集|90x90|20|建议上传尺寸450x450"|album(data.imgsList, "jpg|png|gif", 10) }}
下拉选择组件
{{ "gender|1|性别|name|id"|select("1=男,2=女,3=保密", data.gender) }}
单选按钮组件
{{ "gender|name|id"|radio("1=男,2=女,3=保密", 1) }}
复选框组件
{{ "gender|name|id"|checkbox("1=男,2=女,3=保密", 1) }}
城市选择组件
{{ data.district_code|default("")|city(3, 1) }}
开关组件
{{ "status"|switch("在用|禁用", data.status|default(1)) }}
日期组件
{{ "birthday|1|出生日期|date"|date(data.birthday) }}
图标组件
{{ "icon"|icon(data.icon|default("layui-icon-component")) }}
穿梭组件
{% transfer "func|0|全部节点,已赋予节点|name|id|220x350" "1=列表,5=添加,10=修改,15=删除,20=详情,25=状态,30=批量删除,35=添加子级,40=全部展开,45=全部折叠,50=导出数据,55=导入数据,60=分配权限,65=重置密码" funcList %}
模板布局
Layout布局
<!DOCTYPE html>
<html>
<!-- 头部开始 -->
{% include "public/header.html" %}
<!-- 头部结束 -->
<body>
<!-- 主体部分开始 -->
<div class="layui-fluid">
<div class="layui-card">
<div class="layui-card-body">
<!-- 内容区 -->
{% block content %}
{% endblock %}
</div>
</div>
</div>
<!-- 主体部分结束 -->
<!-- 脚部开始 -->
{% include "public/footer.html" %}
<!-- 脚部结束 -->
</body>
</html>
头部模板
<head>
<meta charset="utf-8"/>
<meta http-equiv="X-UA-Compatible" content="IE=edge,chrome=1">
<meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1">
<title>Tornado+Layui【旗舰版】敏捷开发框架</title>
<link href="/static/assets/images/favicon.ico" rel="icon">
<link type="text/css" rel="stylesheet" href="/static/assets/libs/layui/css/layui.css"/>
<link type="text/css" rel="stylesheet" href="/static/assets/module/admin.css"/>
<!--[if lt IE 9]>
<script src="https://oss.maxcdn.com/html5shiv/3.7.3/html5shiv.min.js' %}"></script>
<script src="https://oss.maxcdn.com/respond/1.4.2/respond.min.js' %}"></script>
<![endif]-->
<script type="text/javascript" src="/static/assets/libs/layui/layui.js"></script>
<script type="text/javascript" src="/static/assets/js/common.js"></script>
<script type="text/javascript">
var url = window.location.pathname.substring(1);
var item = url.split("/");
var C = item[0];
var A = item[1];
var cUrl = "/" + C;
</script>
</head>
脚部模板
<!-- JS部分 -->
<script type="text/javascript">
var url = window.location.pathname.substring(1);
var item = url.split("/");
var jsUrl = "/static/module/djangoadmin_" + item[0] + ".js";
document.write("<script src='" + jsUrl + "'><\/script>");
</script>
搭建http服务
完整代码:
服务端:
import traceback
from io import BytesIO
import tornado
import tornado.ioloop
import tornado.web
from tornado.escape import json_encode
from PIL import Image
import pickle
from base64 import b64encode
import importlib,sys
importlib.reload(sys)
class Handler(tornado.web.RequestHandler):
def post(self):
result = {}
content = self.request.arguments["content"][0]
if content is None:
result["msg"] = "no content"
else:
byte_array, msg = self.content_process(content)
result["image"] = byte_array
result["msg"] = msg
self.write(json_encode(result))
def content_process(self, image_content):
try:
content = pickle.loads(image_content)
image = Image.open(BytesIO(content))
image.save("./server.png")
image = image.rotate(90)
byte_array = BytesIO()
image.save(byte_array, format='PNG')
byte_array = byte_array.getvalue()
byte_array = b64encode(byte_array).decode('utf-8')
return byte_array, "sucess"
except Exception as e:
print(traceback.format_exc())
return str(e), "wrong"
class ImageServer(object):
def __init__(self, port, server_address):
self.port = port
self.address = server_address
def process(self):
app = tornado.web.Application([(r"/image_server?", Handler)], )
app.listen(self.port, address=self.address)
tornado.ioloop.IOLoop.current().start()
if __name__ == "__main__":
server_port = "8080"
server_address = "192.168.10.1"
server = ImageServer(server_port, server_address)
print("begin server")
server.process()
客户端
import json
import urllib
import requests
import pickle
import urllib.request
from PIL import Image
from io import BytesIO
from base64 import b64decode
def post(server_url, params):
data = urllib.parse.urlencode(params).encode('utf-8')
request = urllib.request.Request(server_url, data)
return json.loads(urllib.request.urlopen(request, timeout=100).read())
def local_image(server_url, image_path):
r_file = open(image_path, "rb")
content = pickle.dumps(r_file.read())
params = {"content": content}
json_return = post(server_url, params)
msg = json_return["msg"]
image_bytes = b64decode(json_return["image"])
image = Image.open(BytesIO(image_bytes))
image.save("./user.png")
if __name__ == "__main__":
# url = "http://localhost:8080/image_server?"
url = "http://192.168.10.1:8080/image_server?"
file_path = "/home/yangwn/code/server_test/46.jpg"
local_image(url, file_path)
代码使用
代码只要修改了图片path和url就能直接用
客户端
在主函数main中的url要适配你服务端的url,如果不知道输入什么地址,就用注释的那一行,也就是localhost,注意端口号是必要的
在进入local_image函数后,将图片数据转成字节流,并打包成dict字典的形式传到post函数
在post函数里面继续把字典转成字节流,随后就上传到服务端,并等待服务端返回
接着将返回值接收,我这里返回的是一张图片和一个状态(msg),把返回的图片字节流进行转换后用BytesIO打开即可
服务端
如果前面的客户端你想要用localhost,那就把address设置为None
图片数据的传输方式都一样
就是在post中写数据的时候还用了json_encode,所以把图片用代码先转成字符了
byte_array = BytesIO()
image.save(byte_array, format='PNG')
byte_array = byte_array1.getvalue()
byte_array = b64encode(byte_array).decode('utf-8')
改进
在客户端读图的时候
r_file = open(image_path, "rb")
content = pickle.dumps(r_file.read())
params = {"content": content}
用了pickle,发现多此一举,可以改为
with open(image_path, "rb") as r_file:
content = b64encode(r_file.read())
params = {"content": content}
这时候服务端也要适配,将
content = pickle.loads(image_content)
改为
content = b64decode(image_content)
加入神经网络
当需要输入初始化参数的时候,比如输入一个网络,但是不需要每次post都初始化网络权重,于是可以传入参数。
先修改server,先初始化网络,然后再把网络传入到handle里面
class ImageServer(object):
def __init__(self, port, server_address):
...
self.model = Model()
def process(self):
app = tornado.web.Application([(r"/image_server?", Handler, dict(model=self.model))], )
接着再Handler里重写initialize函数,接收model
class Handler(tornado.web.RequestHandler):
def initialize(self, model):
self.model = model
def conten_process(self):
...
predict = self.model(image)
...
return ...