1、HTTP協議javascript
傳統的HTTP協議是無狀態的,每次請求(request)都要由客戶端(如 瀏覽器)主動發起,服務端進行處理後返回response結果,而服務端很難主動向客戶端發送數據;html
這種客戶端是主動方,服務端是被動方的傳統Web模式。 對於信息變化不頻繁的Web應用來講形成的麻煩較小,而對於涉及實時信息的Web應用卻帶來了很大的不便,java
如帶有即時通訊、實時數據、訂閱推送等功能的應 用。在WebSocket規範提出以前,開發人員若要實現這些實時性較強的功能,常常會使用折衷的解決方法:web
輪詢(polling)和Comet技術。其實後者本質上也是一種輪詢,只不過有所改進。後端
輪詢是最原始的實現實時Web應用的解決方案。輪詢技術要求客戶端以設定的時間間隔週期性地向服務端發送請求,頻繁地查詢是否有新的數據改動。明顯地,這種方法會致使過多沒必要要的請求,浪費流量和服務器資源。輪詢是在特定的的時間間隔(如每1秒),由瀏覽器對服務器發出HTTP請求,而後由服務器返回最新的數據給客戶端的瀏覽器。這種傳統的模式帶來很明顯的缺點,即瀏覽器須要不斷的向服務器發出請求,然而HTTP請求可能包含較長的頭部,其中真正有效的數據可能只是很小的一部分,顯然這樣會浪費不少的帶寬等資源。瀏覽器
Comet技術又能夠分爲長輪詢和流技術。長輪詢改進了上述的輪詢技術,減少了無用的請求。它會爲某些數據設定過時時間,當數據過時後纔會向服務端發送請求;這種機制適合數據的改動不是特別頻繁的狀況。流技術一般是指客戶端使用一個隱藏的窗口與服務端創建一個HTTP長鏈接,服務端會不斷更新鏈接狀態以保持HTTP長鏈接存活;這樣的話,服務端就能夠經過這條長鏈接主動將數據發送給客戶端;流技術在大併發環境下,可能會考驗到服務端的性能。服務器
這兩種技術都是基於請求-應答模式,都不算是真正意義上的實時技術;它們的每一次請求、應答,都浪費了必定流量在相同的頭部信息上,而且開發複雜度也較大。websocket
2、WebSocket併發
WebSocket 是 HTML5 開始提供的一種在單個 TCP 鏈接上進行全雙工通信的協議。app
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() # 掛起運行
##############################################################################################
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程序運行效果: