python中經常使用的幾個web框架有django, tornado, flask等,今天來總結一下django和tornado的不一樣。工做中django和tornado都用過,使用django相對更多一些。我的感受django雖然好用,有搭建項目快、自帶ORM、自動生成路由、自帶管理後臺等優點;但若實際工做中選擇,我仍是會偏向於使用tornado框架,由於torndo使用更加靈活,而且支持websocket,tcp等通訊協議,最重要的是tornado是異步非阻塞的web框架;而在django中要實現websocket、異步非阻塞等功能則須要引入dwebsocket、celery等第三方模塊。javascript
本文使用的環境是python3.6, django2.0, tornado5.1。php
下面主要從如下幾個方面介紹一下這兩個框架的不一樣:
1.建立項目的方式
2.數據庫鏈接
3.異步非阻塞請求
4.websocket的使用html
1.項目建立方式
1)django
django主要是經過下面兩個命令建立項目:java
django-admin startproject Test # 建立項目,名稱爲Test django-admin startpapp Test01 # 建立app,名稱爲Test01
執行完成後,會生成以下的目錄結構:python
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項目的基本結構就是這樣。mysql
2)tornado
tornado項目的建立比較靈活,沒有什麼項目名稱和app的概念,全靠本身組織項目,就是建立一個個python文件和python package。能夠像下面同樣來組織tornado項目:jquery
├── 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中,以下:web
# 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中,以下:redis
# coding=utf-8 from App.UserInfo import views user_urls = [ (r'/user/', views.IndexHandler), ]
application.py中加載路由信息和配置信息:sql
# 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)django
django中使用數據庫時,首先要在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)django
django中能夠經過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)tornado
tornado中實現異步有回調和協程這兩種方式,這裏只舉一個協程實現異步的例子:
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)django
django中使用websocket須要安裝第三方包dwebsocket。
2)tornado
tornado中實現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的使用