轉自:https://blog.shonenada.com/post/websocket-with-flask/WebSocket with FlaskHTML5 以前,HTML 還不支持WebSocket,當時如果要進行實時的內容更新,要麼使用Ajax輪詢(Polling)或者使用Comet技術。...
轉自:https://blog.shonenada.com/post/websocket-with-flask/
WebSocket with Flask
HTML5 以前,HTML 還不支持 WebSocket ,當時如果要進行實時的內容更新,要麼使用 Ajax輪詢(Polling)或者使用 Comet 技術。
Non-Websocket
Ajax 輪詢
在 2005 年, Jesse James Garrett 提出 Ajax (Asynchronous JavaScript and XML, 非同步 Javascript 和 XML)。具體請看Ajax: A New Approach to Web Applications 。並且從那時開始流行使用 Ajax 進行非同步處理客戶端請求。【關於非同步處理請求的歷史,可以看 http://en.wikipedia.org/wiki/Ajax_(programming) 中相關的介紹】。 XMLHttpRequest 在後臺對伺服器發起 request ,當收到 response 的時候,進行 DOM 操作,從而達到部分更新頁面內容的目的(而不需要整個頁面刷新)。
Ajax 輪詢 可以做到接近實時的更新內容,但是因為是由客戶端發起請求,即伺服器處於被動的狀態,這種“實時”存在缺陷: (1) 偽實時。伺服器有更新的時候,只有客戶端發起請求,伺服器才能將更新返回到客戶端。 (2) 數據更新量少的時候,容易造成浪費帶寬、流量。 (3) 請求頻率難以把握。太快會對伺服器造成過大的壓力,而太慢又不夠“實時”,權衡頻率需要考慮的因素很多。
Comet 技術
Comet 是指不需要客戶端瀏覽器安裝任何插件,僅靠瀏覽器和伺服器之間的長 HTTP 連接實現伺服器向客戶端通信(伺服器推)的技術。 Comet 有兩種方式: Ajax長輪詢 和 iframe with htmlfile stream
iframe with htmlfile streaming
這種技術,暫時沒使用過。基本原理是使用 iframe 標簽在 html 中插入一個隱藏的幀,向伺服器建立長連接,伺服器不斷地向 iframe 輸入數據。
Ajax 長輪詢
Ajax 長輪詢本質上也是 Ajax 輪詢,不同的是,在伺服器端做了些修改。當伺服器沒有更新的時候,伺服器將請求阻塞,直到 有更新 或 連接超時。當請求結束之後再進行第二次的請求。
這種方式基本上可以避開 Ajax 輪詢的缺陷。 Tornado 框架中的 Asynchronous 功能就是通過阻塞請求實現非同步更新。 通過 Tornado 框架提供的 Asynchronous 功能可以實現實時數據傳遞。歡迎參考我在學習使用 tornado 非同步功能時實現的兩段應用:
- https://github.com/shonenada/chat-in-command-line
- https://github.com/shonenada/guess-number // 這程式功能不完善,但實現了非同步的功能。
WebSocket
WebSocket 是 HTML5 的新功能,它是一種 TCP 協議。當客戶端和伺服器完成握手,建立連接之後,ws 就如普通 socket 一樣,在兩者之間進行通信。
理解了基本通信原理,就可以進行編程了。
前面已說,WS 是一種 TCP 協議,所以是語言無關的,用任何語言都可以實現伺服器端的編程。我選擇了 Python,使用 _flask: http://flask.pocoo.org/ 作為框架,以 _Gevent: http://www.gevent.org/ 和 _gevent-websocket:https://pypi.python.org/pypi/gevent-websocket/ 做 HttpServer。
實時更新基本的實現思路:
- 客戶端發起 ws 連接請求
- 伺服器響應,並且把 ws 加入到 observer 數組中。
- 當某一 ws 向伺服器發送信息時,伺服器遍歷 observers 數組向每一個元素髮送信息。
- ws 斷開連接時,從 observer 中剔除。
具體實現代碼:
# manage.py
from geventwebsocket.handler import WebSocketHandler
from gevent.pywsgi import WSGIServer
from flask import Flask, request, render_template, abort
import message
msgsrv = message.MessageServer()
app = Flask(__name__)
@app.route('/')
def index():
return render_template('message.html')
@app.route('/message/')
def message():
if request.environ.get('wsgi.websocket'):
ws = request.environ['wsgi.websocket']
msgsrv.observers.append(ws)
while True:
if ws.socket:
message = ws.receive()
if message:
msgsrv.add_message("%s" % message)
else:
abort(404)
return "Connected!"
if __name__ == '__main__':
http_server = WSGIServer(('', 5000), app, handler_class=WebSocketHandler)
http_server.serve_forever()
# message.py
from geventwebsocket import WebSocketError
class MessageServer(object):
def __init__(self):
self.observers = []
def add_message(self, msg):
for ws in self.observers:
try:
ws.send(msg)
except WebSocketError:
self.observers.pop(self.observers.index(ws))
print ws, 'is closed'
continue
<!-- templates/message.html -->
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
<html xmlns="http://www.w3.org/1999/xhtml">
<head>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
<meta http-equiv="Content-Language" content="zh-CN"/>
<title>實時消息</title>
<link rel="stylesheet" href="http://getbootstrap.com/2.3.2/assets/css/bootstrap.css">
<script type="text/javascript" src="http://ajax.googleapis.com/ajax/libs/jquery/1.10.2/jquery.min.js"></script>
<script type="text/javascript" charset="utf-8">
$(document).ready(function(){
$('form').submit(function(event){
ws.send($(this).serialize());
return false;
});
if ("WebSocket" in window) {
ws = new WebSocket("ws://" + document.domain + ":5000/message/");
ws.onmessage = function (msg) {
console.log(msg.data);
};
} else {
alert("WebSocket not supported");
}
window.onbeforeunload = function() {
ws.onclose = function () {
console.log('unlodad')
};
ws.close()
};
});
</script>
</head>
<body>
<div class="header container">
<h1>實時消息</h1>
<ul class="tabs">
<li class="active">
<a href="/">DEMO</a>
</li>
</ul>
</div>
<div class