共计 10514 个字符,预计需要花费 27 分钟才能阅读完成。
python 中常用的几个 web 框架有 django, tornado, flask 等,今天来总结一下 django 和 tornado 的不同。工作中 django 和 tornado 都用过,使用 django 相对更多一些。个人感觉 django 虽然好用,有搭建项目快、自带 ORM、自动生成路由、自带管理后台等优势;但若实际工作中选择,我还是会偏向于使用 tornado 框架,因为 torndo 使用更加灵活,并且支持 websocket,tcp 等通信协议, 最重要的是 tornado 是异步非阻塞的 web 框架;而在 django 中要实现 websocket、异步非阻塞等功能则需要引入 dwebsocket、celery 等第三方模块。
本文使用的环境是 python3.6, django2.0, tornado5.1。
下面主要从以下几个方面介绍一下这两个框架的不同:1. 创建项目的方式 2. 数据库连接 3. 异步非阻塞请求 4.websocket 的使用
1. 项目创建方式 1)djangodjango 主要是通过下面两个命令创建项目:
django-admin startproject Test # 创建项目,名称为 Test
django-admin startpapp Test01 # 创建 app,名称为 Test01
执行完成后,会生成如下的目录结构:
D:.
│ manage.py
│ test.txt
│
├─.idea
│ │ misc.xml
│ │ modules.xml
│ │ Test.iml
│ │ workspace.xml
│ │
│ └─inspectionProfiles
│ profiles_settings.xml
│
├─Test
│ settings.py
│ urls.py
│ wsgi.py
│ __init__.py
│
└─Test01
│ admin.py
│ apps.py
│ models.py
│ tests.py
│ views.py
│ __init__.py
│
└─migrations
__init__.py
主要是 manage.py,Test,Test01 这几个文件和文件夹,manage.py 是管理项目的文件,通过它运行 django 的一些内置命令,如模型迁移、启动项目等;Test/settings.py 是配置文件,项目配置存放在这里 Test/urls.py 是路由文件,负责分发 http 请求 Test01/models.py 是模型文件,Test01 下创建的模型就放在这里,模型负责将表结构映射到数据库中 Test01/views.py 是视图文件,django 中的视图在这里定义 Test01/migrations 目录中存放迁移后生成的迁移文件。django 项目的基本结构就是这样。
2)tornadotornado 项目的创建比较灵活,没有什么项目名称和 app 的概念,全靠自己组织项目,就是创建一个个 python 文件和 python package。可以像下面一样来组织 tornado 项目:
├── App
│ ├── __init__.py
│ ├── Shop
│ │ ├── __init__.py
│ │ └── views.py
│ └── User
│ ├── __init__.py
│ └── views.py
├── application.py
├── Config
│ ├── config_base.py
│ ├── config_db.conf
│ ├── config_db_get.py
│ ├── config_engine.py
│ ├── __init__.py
├── Models
│ ├── __init__.py
│ ├── Shop
│ │ └── __init__.py
│ └── User
│ ├── BaseClass.py
│ ├── __init__.py
│ └── UserModel.py
├── server.py
├── static
│ └── __init__.py
├── templates
│ └── __init__.py
├── test.py
└── Urls
├── __init__.py
├── Shop.py
└── User.py
这里有几个主要文件 App, Config, Models, Urls, static, templates, application.py, server.py。项目的 app 可以集中放在 App 目录中,与数据库对应的模型文件可以放在 Models 中,http 路由可以放在 Urls 中,项目配置信息可以放在 Config 目录中,静态文件和模板分别放在 static 和 templates 中。application.py 文件可以加载路由信息和项目配置信息,server.py 文件负责启动项目。项目的基本配置信息可以放在 Config/config_base.py 中,如下:
# coding=utf-8
import os
BASE_DIR = os.path.dirname(__file__)
# 参数
options = {
“port”: 8001,
}
# 基本配置信息
settings = {
“debug”: True,
“static_path”: os.path.join(BASE_DIR, “static”),
“template_path”: os.path.join(BASE_DIR, “templates”)
}
路由信息可以放在 Urls/User.py 中,如下:
# coding=utf-8
from App.UserInfo import views
user_urls = [
(r’/user/’, views.IndexHandler),
]
application.py 中加载路由信息和配置信息:
# coding=utf-8
from tornado import ioloop, httpserver
from application import Application
from Config import config_base
if __name__ == ‘__main__’:
app = Application()
http_server = httpserver.HTTPServer(app)
http_server.listen(config_base.options.get(“port”))
ioloop.IOLoop.current().start()
2. 数据库连接 1)djangodjango 中使用数据库时,首先要在 settings.py 中配置数据库信息:
DATABASES = {
‘default’: {
‘ENGINE’: ‘django.db.backends.mysql’, # 数据库引擎
‘NAME’: ‘django_test’, # 你要存储数据的库名,事先要创建之
‘USER’: ‘root’, # 数据库用户名
‘PASSWORD’: ‘test’, # 密码
‘HOST’: ‘localhost’, # 主机
‘PORT’: ‘3306’, # 数据库使用的端口
}
}
然后在每个 app 下编写完 models.py 后,执行以下两个命令后,就可以使用数据库了:
python manage.py makemigrations
python manage.py migrate
可以调用模型管理器对象 objects 的相应方法,执行增删改查等操作。
2)tornado 这里说一下在 tornado 中使用 sqlalchemy 连接数据库,需要安装 sqlalchemy 和 pymysql。2.2.1)首先在 Config/config_db.conf 中配置数据库信息:
[db_user]
name = db_tornado03
port = 3306
user = root
host = 127.0.0.1
pass = test
pool_size = 3
2.2.2)然后在 Config/config_engine.py 中配置 engine:
# coding=utf-8
from sqlalchemy import create_engine
from Config.config_db_get import ConfigDBUser
# 数据库配置信息 可以配置多个 engine, 每个数据库对应一个 engine
db_user = ConfigDBUser(“db_user”)
engine_user = create_engine(
“mysql+pymysql://%s:%s@%s:%d/%s” % (
db_user.get_db_user(),
db_user.get_db_pass(),
db_user.get_db_host(),
db_user.get_db_port(),
db_user.get_db_database()
),
encoding=’utf-8′,
echo=True,
pool_size=20,
pool_recycle=100,
connect_args={“charset”: ‘utf8mb4′}
)
create_engine 用来初始化数据库连接。
2.2.3)在 Models/UserInfo/BaseClass.py 中配置连接数据库的 session 信息:
# coding=utf-8
from sqlalchemy.orm import scoped_session, sessionmaker
from Config.config_engine import engine_user
class BaseClass:
def __init__(self):
# 创建 session 对象,并且用 scoped_session 维护 session 对象
# 数据库的增删改查通过 session 对象来完成
self.engine_user = scoped_session(
sessionmaker(
bind=engine_user,
autocommit=False,
autoflush=True,
expire_on_commit=False
)
)
2.2.4)在 Models/UserInfo/UserModel.py 中配置模型信息,用于映射到数据库中对应的表:
# coding=utf-8
from sqlalchemy import Table, MetaData
from sqlalchemy.ext.declarative import declarative_base
from Config.config_engine import engine_user
BaseModel = declarative_base()
def user_model(table_name):
class UserModel(BaseModel):
__tablename__ = table_name
metadata = MetaData(engine_user)
Table(__tablename__, metadata, autoload=True)
return UserModel
配置模型信息前,需要在数据库中把表创建好,这是就需要写 sql 语句创建表了。对于熟练 sql 的同学,写 sql 语句应该不算什么;对应不熟悉 sql 的同学,可能更习惯于 django 中那种创建表的方式。
2.2.5)以上都配置好以后,就可以在视图中使用了 App/UserInfo/views.py:
# coding=utf-8
from tornado import web
from Models.UserInfo.BaseClass import BaseClass
from Models.UserInfo.UserModel import user_model
class UserInfoHandler(web.RequestHandler, BaseClass):
def get(self):
“””
获取用户信息
:return:
“””
# user_model 中的参数对应数据库中的表名
user_info = user_model(“user_info”)
# 获取参数
user_id = self.get_query_argument(“id”)
# self.engine_user 其实就是一个 session 对象;query() 方法会返回一个 query.Query 对象,通过这个对象查询数据库
user_info_obj = self.engine_user.query(user_info).filter(user_info.id==user_id).first()
self.write(user_info_obj.name)
self.finish()
2.2.6)最后配置好 url:
Urls/UserInfo.py:
# coding=utf-8
from App.UserInfo import views
user_urls = [
(r’/userinfo’, views.UserInfoHandler),
]
application.py:
# coding=utf-8
from tornado import web
from Config.config_base import settings
from Urls.UserInfo import user_urls
from Urls.Shop import shop_urls
“””
路由配置
“””
class Application(web.Application):
def __init__(self):
urls = user_urls + shop_urls
super(Application, self).__init__(urls, **settings)
启动服务后,就可以访问了。
3. 异步非阻塞请求 1)djangodjango 中可以通过 celery 来实现异步任务,也可以使用 asyncio 和 aiohttp 实现异步。下面讲一下 celery 的使用:3.1.1)首先需要安装 celery 和 django-celery,使用 pip 安装就行了;3.1.2)然后在 zsettings.py 中进行如下配置:
在 INSTALLED_APPS 中加入 djcelery。
import djcelery
# Celery 便会去查看 INSTALLD_APPS 下包含的所有 app 目录中的 tasks.py 文件,找到标记为 task 的方法,将它们注册为 celery task
djcelery.setup_loader()
BROKER_URL = ‘redis://127.0.0.1:6379/2’
CELERY_RESULT_BACKEND = ‘redis://127.0.0.1:6379/3’
# 或者使用 rabbitmq:
BROKER_URL = ‘amqp://test:test@192.168.173.1:5672/testhost’
CELERY_RESULT_BACKEND = ‘amqp://test:test@192.168.173.1:5672/testhost’
3.1.3)在需要使用异步的 app 中创建 tasks.py 文件,然后编辑该文件:
# coding=utf-8
import time
from celery import task
@task
def test(data):
“””
预处理
:param data:
:return:
“””
time.sleep(3)
return data
耗时的任务就可以放在使用 @task 修饰的函数中 3.1.4)在 views.py 中调用 tasks.py 中的函数
from rest_framework.response import Response
from .tasks import test
class CeleryTrainView(APIView):
def get(self, request):
try:
for i in range(0, 5):
ret = test.delay(str(i))
print(“ret:”, ret)
except Exception as e:
return Response(dict(msg=str(e), code=10001))
return Response(dict(msg=”OK”, code=10000))
上面的结果 ret 是一个 AsyncResult 对象,可以通过这个对象拿到保存在 CELERY_RESULT_BACKEND 中的结果。如果想立即得到结果,可以直接调用 get() 方法,但是这样就会阻塞其他请求,直到结果返回:
from rest_framework.response import Response
from .tasks import test
class CeleryTrainView(APIView):
def get(self, request):
try:
for i in range(0, 5):
ret = test.delay(str(i))
print(“ret:”, ret.get())
except Exception as e:
return Response(dict(msg=str(e), code=10001))
return Response(dict(msg=”OK”, code=10000))
3.1.5)启动 celery
#先启动服务器
python manage.py runserver
#再启动 worker
python manage.py celery worker
2)tornadotornado 中实现异步有回调和协程这两种方式,这里只举一个协程实现异步的例子:
from tornado import web
from tornado import gen
from tornado.httpclient import AsyncHTTPClient
class AsyncHandler(web.RequestHandler):
@gen.coroutine
def get(self, *args, **kwargs):
client = AsyncHTTPClient()
url = ‘http://ip.taobao.com/service/getIpInfo.php?ip=14.130.112.24’
# 根据 ip 地址获取相关信息
resp = yield client.fetch(url)
data = str(resp.body, encoding=”utf-8″)
print(“data:”, data)
self.write(data)
self.finish()
或者像下面这样,把获取 ip 信息的部分封装成一个函数:
from tornado import web
from tornado import gen
from tornado.httpclient import AsyncHTTPClient
class AsyncHandler(web.RequestHandler):
@gen.coroutine
def get(self, *args, **kwargs):
ip_info = yield self.get_ip_info()
self.write(ip_info)
self.finish()
@gen.coroutine
def get_ip_info(self):
client = AsyncHTTPClient()
url = ‘http://ip.taobao.com/service/getIpInfo.php?ip=14.130.112.24’
resp = yield client.fetch(url)
data = str(resp.body, encoding=”utf-8″)
return data
也可以同时发起多个异步请求:
from tornado import web
from tornado import gen
from tornado.httpclient import AsyncHTTPClient
class AsyncHandler(web.RequestHandler):
@gen.coroutine
def get(self, *args, **kwargs):
ips = [
“14.130.112.24”,
“14.130.112.23”,
“14.130.112.22”
]
info1, info2, info3 = yield [self.get_ip_info(ips[0]), self.get_ip_info(ips[1]), self.get_ip_info(ips[2])]
self.write(info1)
self.write(info2)
self.write(info3)
self.finish()
@gen.coroutine
def get_ip_info(self, ip):
client = AsyncHTTPClient()
url = ‘http://ip.taobao.com/service/getIpInfo.php?ip=’ + ip
resp = yield client.fetch(url)
data = str(resp.body, encoding=”utf-8″)
return data
AsyncHTTPClient 的 fetch() 方法有两种调用方式,一种是像上面那样只传入一个 url 的字符串,另一种是接收一个 HTTPRequest 对象作为参数,像下面这样:
@gen.coroutine
def get_ip_info(self, ip):
client = AsyncHTTPClient()
url = ‘http://ip.taobao.com/service/getIpInfo.php?ip=’ + ip
header = {‘Accept’: ‘application/json;charset=utf-8’,
‘Content-Type’: ‘application/x-www-form-urlencoded;charset=utf-8’}
param1 = ‘test’
http_request = HTTPRequest(url=url,
method=’POST’,
headers=header,
body=urlencode({‘param1′: param1}))
resp = yield client.fetch(http_request)
data = str(resp.body, encoding=”utf-8″)
return data
4.websocket 的使用 1)djangodjango 中使用 websocket 需要安装第三方包 dwebsocket。
2)tornadotornado 中实现 websocket 功能需要用到 tornado.websocket 模块,主要有以下几个方法:open(), write_message(), on_message(), on_close()
open(): 当 websocket 客户端连接时所做的操作
write_message(): 使用这个方法向客户端发送消息
on_message(): 接收并处理客户端的消息
on_close(): websocket 关闭连接时所作的操作
下面看一个例子:
views.py:
from tornado import websocket
class IndexHandler(web.RequestHandler):
def get(self, *args, **kwargs):
self.render(“chat.html”)
class ChatHandler(websocket.WebSocketHandler):
clients = set()
def open(self, *args, **kwargs):
self.clients.add(self)
for client in self.clients:
client.write_message(“%s 上线了 ” % self.request.remote_ip)
def on_message(self, message):
for client in self.clients:
client.write_message(“%s: %s” % (self.request.remote_ip, message))
def on_close(self):
self.clients.remove(self)
for client in self.clients:
client.write_message(“%s 下线了 ” % self.request.remote_ip)
def check_origin(self, origin):
“””
用于处理跨域问题
:param origin:
:return:
“””
return True
路由:
# coding=utf-8
from App.UserInfo import views
user_urls = [
(r’/index’, views.IndexHandler),
(r’/chat’, views.ChatHandler),
]
chat.html:
<!DOCTYPE html>
<html lang=”en”>
<head>
<meta charset=”UTF-8″>
<title> 聊天室 </title>
</head>
<body>
<div id=”content” style=”height: 500px;overflow: auto;”></div>
<div>
<textarea id=”msg”></textarea>
<a href=”javascript:;” onclick=”sendMsg()”> 发送 </a>
</div>
<script src=”{{static_url(‘js/jquery.min.js’) }}”></script>
<script type=”text/javascript”>
var ws = new WebSocket(“ws://192.168.1.104:8001/chat”);
ws.onmessage = function (data) {
$(“#content”).append(“<p>”+ data.data +”</p>”)
};
function sendMsg() {
var msg = $(“#msg”).val();
if (msg) {
ws.send(msg);
}
}
</script>
</body>
</html>
上面一个例子通过 websocket 实现了简单的聊天室功能。
以上就简单的比较了 django 和 tornado 几个方面的不同,它们各有优缺点,实际工作中可以根据不同的需求选择不同的框架进行开发。如果想了解如何在 tornado 中使用 tcpserver,可以看一下这篇博客:tornado 中 tcpserver 和 tcpclient 的使用