一、HTTP協議
傳統的HTTP協議是無狀態的,每次請求(request)都要由客戶端(如 瀏覽器)主動發起,服務端進行處理后返回response結果,而服務端很難主動向客戶端發送數據;
這種客戶端是主動方,服務端是被動方的傳統Web模式。 對於信息變化不頻繁的Web應用來說造成的麻煩較小,而對於涉及實時信息的Web應用卻帶來了很大的不便,
如帶有即時通信、實時數據、訂閱推送等功能的應 用。在WebSocket規范提出之前,開發人員若要實現這些實時性較強的功能,經常會使用折衷的解決方法:
輪詢(polling)和Comet技術。其實后者本質上也是一種輪詢,只不過有所改進。
輪詢是最原始的實現實時Web應用的解決方案。輪詢技術要求客戶端以設定的時間間隔周期性地向服務端發送請求,頻繁地查詢是否有新的數據改動。明顯地,這種方法會導致過多不必要的請求,浪費流量和服務器資源。輪詢是在特定的的時間間隔(如每1秒),由瀏覽器對服務器發出HTTP請求,然后由服務器返回最新的數據給客戶端的瀏覽器。這種傳統的模式帶來很明顯的缺點,即瀏覽器需要不斷的向服務器發出請求,然而HTTP請求可能包含較長的頭部,其中真正有效的數據可能只是很小的一部分,顯然這樣會浪費很多的帶寬等資源。
Comet技術又可以分為長輪詢和流技術。長輪詢改進了上述的輪詢技術,減小了無用的請求。它會為某些數據設定過期時間,當數據過期后才會向服務端發送請求;這種機制適合數據的改動不是特別頻繁的情況。流技術通常是指客戶端使用一個隱藏的窗口與服務端建立一個HTTP長連接,服務端會不斷更新連接狀態以保持HTTP長連接存活;這樣的話,服務端就可以通過這條長連接主動將數據發送給客戶端;流技術在大並發環境下,可能會考驗到服務端的性能。
這兩種技術都是基於請求-應答模式,都不算是真正意義上的實時技術;它們的每一次請求、應答,都浪費了一定流量在相同的頭部信息上,並且開發復雜度也較大。
二、WebSocket
WebSocket 是 HTML5 開始提供的一種在單個 TCP 連接上進行全雙工通訊的協議。
WebSocket 使得客戶端和服務器之間的數據交換變得更加簡單,允許服務端主動向客戶端推送數據。在 WebSocket API 中,瀏覽器和服務器只需要完成一次握手,兩者之間就直接可以創建持久性的連接,並進行雙向數據傳輸。
在 WebSocket API 中,瀏覽器和服務器只需要做一個握手的動作,然后,瀏覽器和服務器之間就形成了一條快速通道。兩者之間就直接可以數據互相傳送。
HTML5 定義的 WebSocket 協議,能更好的節省服務器資源和帶寬,並且能夠更實時地進行通訊。

瀏覽器通過 JavaScript 向服務器發出建立 WebSocket 連接的請求,連接建立以后,客戶端和服務器端就可以通過 TCP 連接直接交換數據。
當你獲取 Web Socket 連接后,你可以通過 send() 方法來向服務器發送數據,並通過 onmessage 事件來接收服務器返回的數據。
為客戶端推送時間消息的 Tornado WebSocket程序:
import tornado.ioloop import tornado.web import tornado.websocket import threading import time import datetime import asyncio from tornado.options import define, options, parse_command_line define("port", default=8888, help="run on the given port", type=int) clients = dict() # 客戶端Session 字典 class IndexHandler(tornado.web.RequestHandler): async def get(self): self.render("index.html") class MyWebSocketHandler(tornado.websocket.WebSocketHandler): def open(self, *args): # 有新鏈接時被調用 self.id = self.get_argument("Id") # 保存Session 到 clients字典中 clients[self.id] = {"id": self.id, "object": self} print("open function", str(clients)) def on_message(self, message): # 接受消息時被調用 print("Client %s received a message : %s" % (self.id, message)) def on_close(self): # 關閉鏈接時被調用 if self.id in clients: del clients[self.id] print("Client %s is closed" % self.id) def check_origin(self, origin): return True app = tornado.web.Application([(r'/', IndexHandler),(r'/websocket', MyWebSocketHandler)]) # 啟動單獨的線程運行此函數, 每隔一秒向所有的客戶端推送當前時間 def sendTime(): asyncio.set_event_loop(asyncio.new_event_loop()) # 啟動異步 event loop while True: for key in clients.keys(): msg = str(datetime.datetime.now()) clients[key]["object"].write_message(msg) print("write to client %s: %s" % (key, msg)) time.sleep(1) if __name__ == "__main__": threading.Thread(target=sendTime).start() parse_command_line() app.listen(options.port) tornado.ioloop.IOLoop.instance().start() # 掛起運行
##############################################################################################
- 定義了全局變量字典clients, 用於保存所有與服務器建立起WebSocket鏈接的客戶端信息。字典的鍵是客戶端的id, 值是一個由id與相應的WebSocketHandler實例構成的元組;
- IndexHandler 是一個普通的頁面處理器,用於向客戶端渲染主頁, 該頁面中包含了 Websocket 的客戶端程序;
- MyWebSockerHandler 是本例的核心處理器,繼承 tornado.web.WebSocketHandler. 其中 open() 函數將所有客戶端鏈接保存到 clients字典中;
- on_message() 函數用於顯示客戶端發來的消息; on_close()函數用於將已經關閉的 WebSocket鏈接從 clients 字典中移除。
- 函數 sendTime() 運行在單獨的線程中,每隔一秒輪詢 clients 中的所有客戶端並通過 MyWebSocketHandler.write_message() 函數向客戶端推送時間消息。
- 所有 Tornado 線程中必須有一個 event_loop, 該項要求通過 sendTime() 函數中的第一行代碼被滿足。
- 本例的 tornado.web.Application 實例中只配置了兩個路由,分別指向 IndexHandler 和 MyWebSocketHandler, 仍然由 Tornado IOLoop 啟動並運行。
index.html
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>Python后端WebSocket的實現</title> </head> <body> <a href="javascript:WebSocketTest()">Run Websocket</a> <div id="messages" style="height:200px; background:black; color: white;"></div> <script type="text/javascript"> var messageContainer = document.getElementById("messages"); function WebSocketTest(){ // 判斷當前瀏覽器是否支持WebSocket if("WebSocket" in window){ messageContainer.innerHTML = "WebSocket is supported by your browser!"; let ws = new WebSocket("ws://localhost:8888/websocket?Id=666888"); //連接成功建立的回調方法 ws.onopen = function(){ ws.send("Message to send"); }; //接收到消息的回調方法 ws.onmessage = function(evt){ var received_msg = evt.data; messageContainer.innerHTML += "<br/>Message is received: " + received_msg; }; //連接關閉的回調方法 ws.onclose = function(){ messageContainer.innerHTML += "<br/>Connection is closed ..."; }; }else{ messageContainer.innerHTML = "WebSocket Not Supported by your Browser!"; } } </script> </body> </html>
WebSocket程序運行效果:

