tornado框架的學習與應用
簡單介紹一下所使用的高併發框架tornado,它是一個用python編寫的可擴充套件的非阻塞式web伺服器及其相關工具的開源框架,在處理嚴峻的網路流量時表現得足夠強健,但卻在建立和編寫時有著足夠的輕量級,並能夠被用在大量的應用和工具中。
先簡單介紹一下
用tornado實現的經典helloworld程式如下:
import tornado.ioloop
import tornado.web
class MainHandler(tornado.web.RequestHandler):
def get(self):
self.write("Hello, world" )
application = tornado.web.Application([
(r"/", MainHandler),
])
if __name__ == "__main__":
application.listen(8880)
tornado.ioloop.IOLoop.instance().start()
伺服器程式執行之後,電腦訪問本地迴環地址即可看見:
另一個全功能的helloworld程式如下:
import tornado.httpserver
import tornado.ioloop
import tornado.options
import tornado.web
from tornado.options import define, options
define("port", default=8000, help="run on the given port", type=int)
class IndexHandler(tornado.web.RequestHandler):
def get(self):
greeting = self.get_argument('greeting', 'Hello')
self.write(greeting + ', friendly user!')
if __name__ == "__main__":
tornado.options.parse_command_line()
app = tornado.web.Application(handlers=[(r"/", IndexHandler)])
http_server = tornado.httpserver.HTTPServer(app)
http_server.listen(options.port)
tornado.ioloop.IOLoop.instance().start()
命令列執行:
$ python hello.py --port=8000
即可在指定埠啟動伺服器
同樣,無參訪問伺服器地址會顯示預設引數:
攜帶引數訪問,如輸入:localhost:8000/?dairen會顯示如下:
下面看一個處理字串的伺服器程式:
import textwrap
import tornado.httpserver
import tornado.ioloop
import tornado.options
import tornado.web
from tornado.options import define, options
define("port", default=8000, help="run on the given port", type=int)
class ReverseHandler(tornado.web.RequestHandler):
def get(self, input):
self.write(input[::-1])
class WrapHandler(tornado.web.RequestHandler):
def post(self):
text = self.get_argument('text')
width = self.get_argument('width', 40)
self.write(textwrap.fill(text, int(width)))
if __name__ == "__main__":
tornado.options.parse_command_line()
app = tornado.web.Application(
handlers=[
(r"/reverse/(\w+)", ReverseHandler),
(r"/wrap", WrapHandler)
]
)
http_server = tornado.httpserver.HTTPServer(app)
http_server.listen(options.port)
tornado.ioloop.IOLoop.instance().start()
這個程式有2個handler,一個是反轉輸入的字串,引數input將包含匹配處理函式正則表示式第一個括號裡的字串。一個是以指定的寬度裝飾文字(預設40),並將結果字串寫回到HTTP響應中。
同樣執行伺服器:
$ python string_service.py --port=8000
可以用2種方式訪問:
$ curl http://localhost:8000/reverse/dairen
neriad
$ http://localhost:8000/wrap -d text=Lorem+ipsum+dolor+sit+amet,+consectetuer+adipiscing+elit.
Lorem ipsum dolor sit amet, consectetuer
adipiscing elit.
tornado的狀態碼:
你可以使用RequestHandler類的set_status()方法顯式地設定HTTP狀態碼。然而,你需要記住在某些情況下,Tornado會自動地設定HTTP狀態碼。下面是一個常用情況的綱要:
404 Not Found
Tornado會在HTTP請求的路徑無法匹配任何RequestHandler類相對應的模式時返回404(Not Found)響應碼。
400 Bad Request
如果你呼叫了一個沒有預設值的get_argument函式,並且沒有發現給定名稱的引數,Tornado將自動返回一個400(Bad Request)響應碼。
405 Method Not Allowed
如果傳入的請求使用了RequestHandler中沒有定義的HTTP方法(比如,一個POST請求,但是處理函式中只有定義了get方法),Tornado將返回一個405(Methos Not Allowed)響應碼。
500 Internal Server Error
當程式遇到任何不能讓其退出的錯誤時,Tornado將返回500(Internal Server Error)響應碼。你程式碼中任何沒有捕獲的異常也會導致500響應碼。
200 OK
如果響應成功,並且沒有其他返回碼被設定,Tornado將預設返回一個200(OK)響應碼。
》》》》》》》》》》》》》》》》》》》》》》》》》》》》》》》》》》》
下面介紹我的基於tornado的伺服器專案:qq聊天群的實現
專案程式碼分為主要是2部分:
後臺伺服器python實現+前端(html靜態檔案+css格式+JavaScript客戶端程式碼)
首先來看伺服器的實現:
首先基於tornado.web.RequestHandler類實現自己的第一個請求處理類,如下:
class MainHandler(tornado.web.RequestHandler):
def get(self):
self.render("index.html", messages=ChatSocketHandler.cache, clients=ChatSocketHandler.waiters, username= "遊客%d" % ChatSocketHandler.client_id)
其中用render方法引入模板index.html ,不僅僅是要引用模板網頁,還要向這個網頁傳遞一些資料,包括:
歷史訊息:messages
線上使用者:clients
分配給當前訪問的使用者的使用者名稱:username
由於想要實現的qq聊天群需要雙向web通訊,也就是不僅僅伺服器響應客戶端,客戶端也能響應伺服器傳送的訊息,所以又基於tornado.websocket.WebSocketHandler類實現了自己的websocket處理類,
Tornado在websocket模組中提供了一個WebSocketHandler類。這個類提供了和已連線的客戶端通訊的WebSocket事件和方法的鉤子。當一個新的WebSocket連線開啟時,open方法被呼叫,而on_message和on_close方法分別在連線接收到新的訊息和客戶端關閉時被呼叫。
此外,WebSocketHandler類還提供了write_message方法用於向客戶端傳送訊息,close方法用於關閉連線。
在這裡我把使用者的訊息型別分為3種,一種是上線:online,一種是下線:offline 一種是使用者發訊息了:message
每當有新的使用者連線時,給其分配使用者名稱,並把該使用者加入到已連線使用者列表中:
def open(self):
self.client_id = ChatSocketHandler.client_id
ChatSocketHandler.client_id = ChatSocketHandler.client_id + 1
self.username = "遊客%d" % self.client_id
ChatSocketHandler.waiters.add(self)
然後封裝一個使用者上線的訊息字典,專門去處理:
chat = {
"id": str(uuid.uuid4()),
"type": "online",
"client_id": self.client_id,
"username": self.username,
"datetime": datetime.datetime.now().strftime("%Y-%m-%d %H:%M:%S")
}
ChatSocketHandler.send_updates(chat)
同樣,使用者下線:刪除使用者,廣播下線訊息
使用者發訊息:更新歷史訊息列表,廣播訊息
使用者傳送的訊息會以json的格式收到,所以要先解析,然後提取其中的資訊,包裝成我們自己的chat字典,然後群發:
parsed = tornado.escape.json_decode(message)
self.username = parsed["username"]
chat = {
"id": str(uuid.uuid4()),
"body": parsed["body"],
"type": "message",
"client_id": self.client_id,
"username": self.username,
"datetime": datetime.datetime.now().strftime("%Y-%m-%d %H:%M:%S")
}
其中廣播訊息也就是向線上的每一個使用者,使用websocket的功能傳送一次訊息。
寫好了open on_close on_message類之後,下面考慮我想要實現的歷史記錄的實現,可以快取1000條訊息記錄放在一個列表中(列表中每一項是一個chat的字典型別),每次新使用者訪問就把這個列表[]發給他,讓他也能看到歷史記錄,每當記錄滿了,那就擷取最後1000條儲存下來:
if len(cls.cache) > cls.cache_size:
cls.cache = cls.cache[-cls.cache_size:]
這裡的cache_size我定義為1000,也就是快取1000條訊息記錄。
另外,為了完善對伺服器執行過程的掌控,加入python日誌模組,也就是匯入logging模組,
logging.info("sending message to %d waiters", len(cls.waiters))
這樣控制檯就可以觀察到執行的一些操作了,當然也可以把日誌寫進檔案中以後查閱。
至此,我們伺服器的大致內容設計的差不多了,下面考慮前端顯示問題,以及基於websocket通訊的JavaScript客戶端程式設計問題
首先想一想聊天室主頁的顯示情況,首先要有使用者列表,實時顯示線上的使用者,還要有歷史訊息列表,顯示我們快取的1000條聊天記錄,另外就是像qq群一樣的聊天介面,你一句我一句,要有使用者名稱,訊息體,時間,然後最下面還要有我們自己的聊天資訊輸入框與傳送的地方,差不多就這些吧
先分析主頁模板html檔案怎麼寫吧,使用者列表:
<div id="users">
線上使用者:<p/>
<ul id="user_list">
{% for client in clients %}
<li id={{client.client_id}}>{{client.username}}</li>
{% end %}
</ul>
</div>
這樣就會顯示伺服器傳給html檔案裡的clients 的所有線上使用者。再看我們聊天介面顯示的每一條訊息,顯然按格式包裝起來會更方便,所以每條訊息就以一個格式包裝與顯示:
<div class="message" id="m{{ message["id"] }}">{{message["username"]}} 說:{% module linkify(message["body"]) %} ({{message["datetime"]}})</div>
再看歷史訊息的顯示格式,歷史訊息也是新使用者上線的時候伺服器在渲染模板html的時候加入的引數,html模板中大致以如下格式進行渲染:
<div id="inbox">
{% for message in messages %}
{% include "message.html" %}
{% end %}
</div>
最後就是我們的訊息內容輸入與傳送,這一部分需要伺服器JavaScript程式碼把我們使用者輸入的訊息接收處理,然後發給我們的伺服器,伺服器進行解析後給與響應,html格式大致如下:
<form action="/a/message/new" method="post" id="messageform">
<table>
<tr>
<td >使用者名稱:</td>
<td><input name="username" id="username" style="width:100px" value="{{username}}"></td>
</tr>
<tr>
<td>輸入訊息:</td>
<td><input name="body" id="message" style="width:500px"></td>
</tr>
<tr>
<td style="padding-left:5px">
<input type="submit" value="提交">
<input type="hidden" name="next" value="{{ request.path }}">
{% module xsrf_form_html() %}
</td>
</tr>
</table>
</form>
這裡使用了HTML表單,表單用於客戶端收集使用者在瀏覽器的輸入,是實現客戶端與伺服器互動的核心方法,注意:
{% module xsrf_form_html() %}
這裡我使用了tornado的防止跨站攻擊的手段,非常簡單,要麼在例項化tornado.web.Application的時候傳入引數:xsrf_cookies=True,要麼在每個具有HTML表單的模板檔案中,為所有HTML表單新增xsrf_form_html()函式標籤,而{% module xsrf_form_html() %}這句話起到了為表單新增隱藏元素,以防止跨站請求的作用。
這一句話作用挺大,也可以看出tornado為開發者減輕了很多的負擔,加快了開發效率,不然就要自己考慮很多細節性的工作。
至此,我們關於主頁模板的內容基本介紹完畢,下面就是介紹客戶端的JavaScript部分,我們的qq聊天室是基於websocket的全雙工的,而websocket是html5的標準之一,所以主流的瀏覽器的JavaScript程式語言是支援websocket程式設計的,方法也很簡單,就是圍繞著websocket物件,使用:
socket = new WebSocket(url);
就可根據url,新建一個websocket連線,然後與之通訊,然後響應websocket的如下事件:
websocket.onopen(),
websocket.onmessage(),
websocket.onerror(),
websocket.onclose(),被動關閉連線
然後websocket還有2種主動事件型別:
websocket.send()
websocket.close():主動關閉連線
下面開始我的客戶端部分的設計,網頁被載入的時候,瀏覽器會建立頁面文件物件模型DOM樹,JavaScript中幾乎所有的內容都是基於DOM樹來進行的,而DOM樹的根節點是document,JavaScript通過操作DOM樹,進行 增刪改查,實現對html的操作,在本專案的JavaScript程式碼中,我使用了最優秀的js客戶端框架庫jQuery,jQuery能方便的處理html,響應事件,實現動畫效果,可以把它認為是html css JavaScript的封裝,使用jQuery可以讓開發者更輕鬆地寫JavaScript程式碼。
我這裡直接在html原始檔中引用internet上的jQuery庫連線,更輕量:
<script src="{{ static_url("jquery.min.js") }}" type="text/javascript"></script>
而我們在JavaScript程式碼中引用jQuery庫也很方便,就是一個美元的符號:$
形如:
$(selector).action()
這裡selector是選擇器,action就是要進行的操作。jQuery中的選擇器與css中的選擇器很類似,就是按標記名,id等進行選擇,另外還可以根據特定的屬性,根據標記相對於父標記的位置,元素內容等進行選擇。
下面直奔主題,首先要處理我們的:文件全部載入完成的事件,也就是:
$(document).ready(function()
{
}
jQuery中把html事件中的on都給去掉了,譬如onclick事件,在jQuery中就是click事件,
這個函式,首先要實現傳送表單:messageform的submit事件:
$("#messageform").live("submit", function() {
newMessage($(this));
return false;
});
一旦有message提交了,立馬執行newMessage函式,也就是給伺服器發訊息
下面同樣的作用,只不過是監控keyCode == 13的按鍵,也就是我們鍵盤上的enter鍵
$("#messageform").live("keypress", function(e) {
if (e.keyCode == 13) {
newMessage($(this));
return false;
}
});
其中的newmessage函式實現如下:
function newMessage(form) {
var message = form.formToDict();
updater.socket.send(JSON.stringify(message));
form.find("input[type=text]").val("").select();
}
就是向伺服器以字典的格式,傳送一個新的訊息,其中的formToDict 函式實現如下:
formToDict = function() {
var fields = this.serializeArray();
var json = {}
for (var i = 0; i < fields.length; i++) {
json[fields[i].name] = fields[i].value;
}
if (json.next) delete json.next;
return json;
};
作用是把表單中所有的輸入儲存到json物件中去,最後返回客戶端要發給伺服器的訊息字典,也是一個json物件。
另外我這裡設定了一個,一旦選中了我們之前html檔案裡定義的id為message編輯框的控制元件,就開始發起一個連線,動作如下:
$("#message").select();
updater.start();
其中,start()內容大致為:
function()
{
var url = "ws://" + location.host + "/chatsocket";
updater.socket = new WebSocket(url);
updater.socket.onmessage = function(event) {
updater.showMessage(JSON.parse(event.data));
}
}
這裡一旦選中了編輯框,客戶端js程式碼就開始新建一個websocket連線,其中url 就是我們伺服器的地址,並且設定了我們的onmessage()函式,也就是響應伺服器訊息的函式,其內容大致如下:
showMessage: function(message) {
del(message.client_id);
if (message.type!="offline")
{
add(message.client_id, message.username);
if (message.body=="") return;
var existing = $("#m" + message.id);
if (existing.length > 0) return;
var node = $(message.html);
node.hide();
$("#inbox").append(node);
node.slideDown();
}
}
當收到一個伺服器的訊息,先去分析訊息的型別:online offline 還是 message型別,分別做不同的響應,我的實現是先不管訊息型別,先把當前的使用者給刪了,呼叫del函式來動態刪除線上使用者列表,del函式如下:
動態刪除:
function del(id){
$('#'+id).remove();
}
然後再來分析訊息型別是不是offline,如果是offline,那就結束了
如果不是offline,那就是上線或者發訊息啦,所以再把剛剛刪的使用者新增到使用者名稱列表中,呼叫add函式,如下:
動態新增:
function add(id,txt) {
var ul=$('#user_list');
var li= document.createElement("li");
li.innerHTML=txt;
li.id=id;
ul.append(li);
}
其中$(‘#user_list’)就是使用jQuery的查詢器進行元素定位,查詢id為user_list的HTML標籤,之後就可以通過append(),remove來動態的新增刪除列表元素了。
接著判斷訊息是不是無效,也就是訊息體為空,那就直接return,這樣就實現了
使用者下線:列表中刪掉使用者
使用者上線(一旦動了編輯框就會上線),但沒發訊息:先刪後加,重新整理了一下
然後再實現使用者發訊息的過程,那就比較簡單了,就是:
var node = $(message.html);
node.hide();
$("#inbox").append(node);
node.slideDown();
先選中了訊息,為新收到的訊息建立一個新的節點node,先隱藏訊息(因為傳送出去要清空的啊),然後把訊息新增到我們的html檔案中的inbox標籤的尾部,進行顯示,inbox標籤內容如下:
<div id="inbox">
{% for message in messages %}
{% include "message.html" %}
{% end %}
</div>
然後把聊天介面下拉到最下面,至此就可以動態的處理:顯示伺服器傳送的訊息了,也就是所有的客戶端都能正確顯示伺服器廣播的訊息了。
最後就是把我們的伺服器JavaScript程式碼嵌入到主頁的HTML中,一般的嵌入方式有內部嵌入和外部連結兩種方式,我這裡把JavaScript程式碼單獨放在了一個檔案裡,使用了外部連結的方式嵌入到了html檔案中:
<script src="{{ static_url("chat.js") }}" type="text/javascript"></script>
這裡chat.js就是我的js程式碼檔案。
還有別忘了在嵌入js之前,嵌入我們使用的jQuery庫,所以應該是這個樣子:
<script src="{{ static_url("jquery.min.js") }}" type="text/javascript"></script>
<script src="{{ static_url("chat.js") }}" type="text/javascript"></script>
至此,我的qq群的前端+後臺已經基本實現,
python mychat.py --port=8880
在8880埠執行伺服器之後,在本地用迴環地址localhost:8880訪問:
或者別的主機使用
ip:port的形式,都能訪問該聊天群,伺服器所在ip地址,也就是我的電腦ip是:115.156.245.122
我這裡使用師兄的電腦登入了一下隨便傳送了一條訊息:
同時由於我們伺服器加入了logging日誌模組,所以在控制檯可以看到我想要列印的訊息,例如:
》》》》》》》》》》》》》》》》》》》》》》》》》》》》》》》》》》》》》
至此,該專案以完成我想要的一個qq聊天群的所有基本功能了。但是專案改進的餘地還很大,我總結一下,
不足主要有3點:
1、由於沒有使用者持久化的訪問需求,都是立馬連立馬走,所以沒有使用資料庫,如果設計一個qq好友系統。可以考慮加入資料庫,把使用者以及使用者關係,歷史訊息,使用者名稱密碼等等都儲存在資料庫中
2、由於前端知識只是略懂,所以介面略微簡陋。。。有需要可以深入學習前端的知識
3、功能可以更加豐富化,可以加入群許可權控制,使得有類似於管理員一樣的存在,還可以加入群檔案共享,群公告,群照片等等。。。
相關推薦
tornado框架的學習與應用
簡單介紹一下所使用的高併發框架tornado,它是一個用python編寫的可擴充套件的非阻塞式web伺服器及其相關工具的開源框架,在處理嚴峻的網路流量時表現得足夠強健,但卻在建立和編寫時有著足夠的輕量級,並能夠被用在大量的應用和工具中。 先簡單介紹一下 用t
深度學習框架Tensorflow學習與應用(5到8)
五. 03-1 迴歸 # coding: utf-8 # In[2]: import tensorflow as tf import numpy as np import matplotlib.pyplot as plt # In[3]: #使用numpy生成200個隨機點 x_
10、深度學習框架Caffe學習與應用--訓練結果影象分析
一、觀察損失曲線:學習率 橫軸:輪。 縱軸:損失。 黃色:學習率太高; 藍色:學習率太低; 綠色:學習率高了; 紅色:學習率最好; 二、放大損失曲線:學習率、batch大小 沒有呈現線性:說明學習率低了。 下降太慢:說明學習率太高。 寬度
深度學習框架Tensorflow學習與應用(八 儲存和載入模型,使用Google的影象識別網路inception-v3進行影象識別)
一 模型的儲存 [email protected]:~/tensorflow$ cat 8-1saver_save.py # coding: utf-8 # In[1]: import tensorflow as tf from tensorflow.examples.tutorials
深度學習框架tensorflow學習與應用10(MNSIT卷積神經網路實現)
import tensorflow as tf from tensorflow.examples.tutorials.mnist import input_data mnist = input_data.read_data_sets('F:/PY/MNIST_data/',
深度學習框架tensorflow學習與應用9(tensorboard視覺化)
import tensorflow as tf from tensorflow.examples.tutorials.mnist import input_data from tensorflow.contrib.tensorboard.plugins import projector # In
深度學習框架tensorflow學習與應用8(tensorboard網路結構)
#載入資料集 mnist = input_data.read_data_sets("MNIST_data",one_hot=True) #如果沒有就下載,然後以獨熱碼的形式載入,有的話就不下載 #每個批次的大小 batch_size =100 #計算一共有多少個批次 n_batch = mnis
深度學習框架tensorflow學習與應用7(改變模型和優化器提升準確率)
#訓練 train_step = tf.train.AdamOptimizer(lr).minimize(loss) 原來的程式碼: # coding: utf-8 # In[ ]: import tensorflow as tf from tensorflow.examples.tu
深度學習框架tensorflow學習與應用6(優化器SGD、ADAM、Adadelta、Momentum、RMSProp比較)
看到一個圖片,就是那個表情包,大家都知道: Adadelta 》 NAG 》 Momentum》 Remsprop 》Adagrad 》SGD 但是我覺得看情況而定,比如有http://blog.51cto.com/12568470/1898367常見優化演
深度學習框架tensorflow學習與應用6(防止過擬合dropout,keep_prob =tf.placeholder(tf.float32))
import tensorflow as tf from tensorflow.examples.tutorials.mnist import input_data # In[3]: # 載入資料集 mnist = input_data.read_data_sets("MNIST_data",
深度學習框架tensorflow學習與應用5(softmax函式+交叉熵代價函式和二次代價函式的比較)
import tensorflow as tf from tensorflow.examples.tutorials.mnist import input_data # In[3]: #載入資料集 mnist = input_data.read_data_sets("MNIST_data",o
深度學習框架tensorflow學習與應用4(MNIST資料集分類的簡單版本示例)
資料集 我們要訓練機器學習, 那麼就要用到訓練資料. 這次我們使用MNIST_data資料集 在程式中要匯入該資料集, 語句:mnist = input_data.read_data_sets("MNIST_data", one_hot=True)one_hot 意思是把資料集變成[
深度學習框架tensorflow學習與應用3(非線性迴歸訓練示例)
在瞭解了迴歸演算法中的正向傳播和反向傳播之後, 我們可以用梯度下降法來進行一個非線性迴歸的示例. 此次示例中, 我們設定輸入樣本神經元只有一個, 中間神經元有10個, 輸出神經元1個. &
深度學習框架tensorflow學習與應用2(fetch and feed和訓練一元一次方程擬合示例 )
Fetch Fetch操作是指TensorFlow的session可以一次run多個op 語法: 將多個op放入陣列中然後傳給run方法 Feed Feed操作是指首先建立佔位符, 然後把佔位符放入op中. 在run op的時候, 再把要op的值傳進去,
深度學習框架TensorFlow學習與應用(三)——使用交叉熵作為代價函式
二次代價函式(quadratic cost): 其中,C表示代價函式,x表示樣本,y表示實際值,a表示輸出值,n表示樣本的總數。 例如: 假如我們使用梯度下降法(Gradient descent)來調整權值引數的大小,權值w和偏置b的梯
【深度學習框架Caffe學習與應用】 第十一課
1.車輛檢測實踐:使用Caffe訓練的深度學習模型做目標檢測——以車輛檢測為例 有關檔案都放在以下資料夾中: 對vehicle_detetc.cpp進行編譯: 編譯之前,需要修改一下程式碼中的檔
【深度學習框架Caffe學習與應用】第三課 將圖片資料轉化為LMDB資料``
1.將圖片資料轉化為LMDB資料 第一步:建立圖片檔案列表清單,一般為一個txt檔案,一行一張圖片 我在caffe/data/目錄下新建一個test_data的資料夾,裡面放訓練集及資料集
深度學習框架TensorFlow學習與應用(五)——TensorBoard結構與視覺化
一、TensorBoard網路結構 舉例: import tensorflow as tf from tensorflow.examples.tutorials.mnist import input_data #載入資料集 mnist=input_dat
【深度學習框架Caffe學習與應用】第四課 Caffe視覺化工具
1.首先準備pycaffe環境 輸入一下命令: 2.網路視覺化的工具 2.1在caffe中,有一個專門用於畫網路結構圖的py檔案:caffe/tools/draw_net.py 2
深度學習框架TensorFlow學習與應用(四)——擬合問題、優化器
一、擬合 1)迴歸問題: 過擬合儘量去通過每一個樣本點,誤差為零。假如有一個新的樣本點: 會發現過擬合的偏差會很大。 2)分類問題: 同樣的當得到新的樣本點後,過擬合的錯誤率可能會提高。 3)防止過擬合: 1.增加資料集 2.正則化方