轉自:https://blog.shonenada.com/post/websocket-with-flask/javascript
HTML5 之前,HTML 還不支持 WebSocket ,當時若是要進行實時的內容更新,要麼使用 Ajax輪詢(Polling)或者使用 Comet 技術。css
在 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 操做,從而達到部分更新頁面內容的目的(而不須要整個頁面刷新)。html
Ajax 輪詢 能夠作到接近實時的更新內容,可是由於是由客戶端發起請求,即服務器處於被動的狀態,這種「實時」存在缺陷: (1) 僞實時。服務器有更新的時候,只有客戶端發起請求,服務器才能將更新返回到客戶端。 (2) 數據更新量少的時候,容易形成浪費帶寬、流量。 (3) 請求頻率難以把握。太快會對服務器形成過大的壓力,而太慢又不夠「實時」,權衡頻率須要考慮的因素不少。java
Comet 是指不須要客戶端瀏覽器安裝任何插件,僅靠瀏覽器和服務器之間的長 HTTP 鏈接實現服務器向客戶端通訊(服務器推)的技術。 Comet 有兩種方式: Ajax長輪詢 和 iframe with htmlfile streampython
這種技術,暫時沒使用過。基本原理是使用 iframe 標籤在 html 中插入一個隱藏的幀,向服務器創建長鏈接,服務器不斷地向 iframe 輸入數據。jquery
Ajax 長輪詢本質上也是 Ajax 輪詢,不一樣的是,在服務器端作了些修改。當服務器沒有更新的時候,服務器將請求阻塞,直到 有更新 或 鏈接超時。當請求結束以後再進行第二次的請求。git
這種方式基本上能夠避開 Ajax 輪詢的缺陷。 Tornado 框架中的 Asynchronous 功能就是經過阻塞請求實現異步更新。 經過 Tornado 框架提供的 Asynchronous 功能能夠實現實時數據傳遞。歡迎參考我在學習使用 tornado 異步功能時實現的兩段應用:github
WebSocket 是 HTML5 的新功能,它是一種 TCP 協議。當客戶端和服務器完成握手,創建鏈接以後,ws 就如普通 socket 同樣,在二者之間進行通訊。web
理解了基本通訊原理,就能夠進行編程了。ajax
前面已說,WS 是一種 TCP 協議,因此是語言無關的,用任何語言均可以實現服務器端的編程。我選擇了 Python,使用 _flask: http://flask.pocoo.org/ 做爲框架,以 _Gevent: http://www.gevent.org/ 和 _gevent-websocket:https://pypi.python.org/pypi/gevent-websocket/ 作 HttpServer。
實時更新基本的實現思路:
具體實現代碼:
# 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