HTML5 WebSocket


一、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程序運行效果:


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM