前言
當你覺得你過得很舒服的時候,你肯定沒有在進步。所以我想學習新的東西,然后選擇了Tornado。因為我覺得Tornado更匹配目前的我的綜合素質。
Tornado學習筆記系列主要參考《introduction to Tornado》一書,網上有中文版,地址為
http://demo.pythoner.com/itt2zh/index.html
當然也參考了大量博客,在此鳴謝!
本系列不適合完全的0基礎小白。
簡介
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問題,這樣的設計使得其成為一個擁有非常高性能的解決方案(服務器與框架的集合體)。
Hello Word
第一個py
安裝Tornado部分略過,我們直接進入正題,PyCharm沒有新建Tornado項目的選項,我們直接新建一個py即可。
1 # -*- coding=utf-8 -*- 2 import tornado.web 3 import tornado.ioloop 4 5 6 class Index(tornado.web.RequestHandler): 7 # 封裝一個類 8 def get(self): 9 # get請求進入該方法 10 # 返回字符串 11 self.write('Hello World') 12 13 14 class Home(tornado.web.RequestHandler): 15 def get(self): 16 self.write('Home') 17 18 19 if __name__ == '__main__': 20 app = tornado.web.Application([ 21 # 相當於路由 22 (r'/', Index), 23 (r'/home', Home), 24 ]) 25 # 指定端口 26 app.listen(8000) 27 # 開啟 28 tornado.ioloop.IOLoop.current().start()
我們直接啟動即可,然后使用postman或者瀏覽器訪問(我習慣使用postman)
http://localhost:8000
我們測試一下結果
當服務器收到請求時會進入Application,進入路由順序查找匹配。匹配到進入相關class,再根據方法進行處理
如果沒有對應class報404,沒有相應的方法報405
開啟多進程
之前說過Tornado的特點便是多進程,但是上面的代碼是單進程的,我們需要修改代碼來開啟多進程
# -*- coding=utf-8 -*- import tornado.web
import tornado.httpserver import tornado.ioloop
class Index(tornado.web.RequestHandler): # 封裝一個類 def get(self): # get請求進入該方法 # 返回字符串 self.write('Hello World') class Home(tornado.web.RequestHandler): def get(self): self.write('Home') if __name__ == '__main__': app = tornado.web.Application([ # 相當於路由 (r'/', Index), (r'/home', Home), ]) # 手動生成server http_server = tornado.httpserver.HTTPServer(app) # 指定端口 http_server.bind(8000) # 開啟多進程 http_server.start(0) # 開啟 tornado.ioloop.IOLoop.current().start()
注意:指定多進程在linux上可行,在windows會報錯,因為fork這個系統命令,只在linux中才有用。所以windows請留空(默認為1)或者填1
http_server.start(num_processes=1)方法指定開啟幾個進程,參數num_processes默認值為1,即默認僅開啟一個進程;如果num_processes為None或者<=0,則自動根據機器硬件的cpu核芯數創建同等數目的子進程;如果num_processes>0,則創建num_processes個子進程。
雖然tornado給我們提供了一次開啟多個進程的方法,但是由於:
- 每個子進程都會從父進程中復制一份IOLoop實例,如過在創建子進程前我們的代碼動了IOLoop實例,那么會影響到每一個子進程,勢必會干擾到子進程IOLoop的工作;
- 所有進程是由一個命令一次開啟的,也就無法做到在不停服務的情況下更新代碼;
- 所有進程共享同一個端口,想要分別單獨監控每一個進程就很困難。
不建議使用這種多進程的方式,而是手動開啟多個進程,並且綁定不同的端口。
Tornado options組件(命令行加參數)
像端口這種易改變的配置寫在代碼里則會有解耦性的問題,這個時候我們就需要Tornado的options組件。他可以幫助我們實現全局參數的定義存儲和轉換。
tornado.options.define()
參數有
- name 選項變量名,須保證全局唯一性,否則會報“Option 'xxx' already defined in ...”的錯誤;
- default 選項變量的默認值,如不傳默認為None;
- type 選項變量的類型,從命令行或配置文件導入參數的時候tornado會根據這個類型轉換輸入的值,轉換不成功時會報錯,可以是str、float、int、datetime、timedelta中的某個,若未設置則根據default的值自動推斷,若default也未設置,那么不再進行轉換。可以通過利用設置type類型字段來過濾不正確的輸入。
- multiple 選項變量的值是否可以為多個,布爾類型,默認值為False,如果multiple為True,那么設置選項變量時值與值之間用英文逗號分隔,而選項變量則是一個list列表(若默認值和輸入均未設置,則為空列表[])。
- help 選項變量的幫助提示信息,在命令行啟動tornado時,通過加入命令行參數 --help 可以查看所有選項變量的信息(注意,代碼中需要加入tornado.options.parse_command_line())。
define("port", default=8000, help="run on the given port", type=int)
此處做到可以在啟動時輸入一個port,來指定端口,不傳默認為8000,輸入類型為int
python tdo_helloword.py --port=9000
指定端口為9000
那么代碼修改為
# -*- coding=utf-8 -*- import tornado.web import tornado.ioloop import tornado.httpserver import tornado.options # 可以多行 tornado.options.define("port", default=8000, help="run on the given port", type=int) class Index(tornado.web.RequestHandler): # 封裝一個類 def get(self): # get請求進入該方法 # 返回字符串 self.write('Hello World') class Home(tornado.web.RequestHandler): def get(self): self.write('Home') if __name__ == '__main__': # 注意加上這句 tornado.options.parse_command_line() app = tornado.web.Application([ # 相當於路由 (r'/', Index), (r'/home', Home), ]) # 手動生成server http_server = tornado.httpserver.HTTPServer(app) # 指定端口 http_server.bind(tornado.options.options.port) # 開啟多進程 http_server.start(1) # 開啟 tornado.ioloop.IOLoop.current().start()
我們在命令行加入port參數
python tdo_helloword.py --port=9999
查看效果
Tornado options組件(從配置文件導入)
配置文件格式要對
我們要更新一下代碼
# -*- coding=utf-8 -*- import tornado.web import tornado.ioloop import tornado.httpserver import tornado.options # 可以多行 tornado.options.define("port", default=8000, help="run on the given port", type=int) class Index(tornado.web.RequestHandler): # 封裝一個類 def get(self): # get請求進入該方法 # 返回字符串 self.write('Hello World') class Home(tornado.web.RequestHandler): def get(self): self.write('Home') if __name__ == '__main__': # 注意加上這句 # tornado.options.parse_command_line() # 從文件讀取配置 tornado.options.parse_config_file('./config') app = tornado.web.Application([ # 相當於路由 (r'/', Index), (r'/home', Home), ]) # 手動生成server http_server = tornado.httpserver.HTTPServer(app) # 指定端口 http_server.bind(tornado.options.options.port) # 開啟多進程 http_server.start(1) # 開啟 tornado.ioloop.IOLoop.current().start()
這樣便是讀取配置文件來啟動tornado
關閉日志
在我們訪問網站的時候,我們會發現屏幕打印了訪問和返回的記錄,我們可以將他關閉
想要關閉,我們可以在開啟時加上--logging=none
python td_helloword.py --logging=none
或者修改代碼為
1 # -*- coding=utf-8 -*- 2 import tornado.web 3 import tornado.ioloop 4 import tornado.httpserver 5 import tornado.options 6 # 可以多行 7 tornado.options.define("port", default=8000, help="run on the given port", type=int) 8 9 10 class Index(tornado.web.RequestHandler): 11 # 封裝一個類 12 def get(self): 13 # get請求進入該方法 14 # 返回字符串 15 self.write('Hello World') 16 17 18 class Home(tornado.web.RequestHandler): 19 def get(self): 20 self.write('Home') 21 22 23 if __name__ == '__main__': 24 # 不打印日志 25 tornado.options.options.logging = None 26 tornado.options.parse_command_line() 27 # 從文件讀取配置 28 # tornado.options.parse_config_file('./config') 29 app = tornado.web.Application([ 30 # 相當於路由 31 (r'/', Index), 32 (r'/home', Home), 33 ]) 34 # 手動生成server 35 http_server = tornado.httpserver.HTTPServer(app) 36 # 指定端口 37 http_server.bind(tornado.options.options.port) 38 # 開啟多進程 39 http_server.start(1) 40 # 開啟 41 tornado.ioloop.IOLoop.current().start()