WebSocket小記


what's the WebSocket

  WebSocket 是一種通信協議,區別於 HTTP 協議,HTTP協議只能實現客戶端請求,服務端響應的這種單項通信。而 WebSocket 可以實現客戶端與服務端的雙向通訊,最大也是最明顯的區別就是可以做到服務端主動將消息推送給客戶端。

WebSocket 和 普通 HTTP 請求不同點

WebSocket 由服務端主動推送數據到客戶端,普通 HTTP 請求需要客戶端每次項服務端發送請求后才能得到響應

  1. 長連接:只需要建立一次握手請求后就可以一直得到服務端推送的數據
  2. 數據格式輕量,性能開銷小。客戶端與服務端進行數據交換時,服務端到客戶端的數據包頭只有2到10字節,客戶端到服務端需要加上另外4字節的掩碼。HTTP每次都需要攜帶完整頭部。
  3. 更好的二進制支持,可以發送文本,和二進制數據
  4. 沒有同源限制,客戶端可以與任意服務器通信
  5. 協議標識符是 ws(如果加密,則是wss),請求的地址就是后端支持 websocket 的 API。

 

實現 WebSocket

WebSocket 連接過程

  客戶端發起 HTTP 握手,告訴服務端進行 WebSocket 協議通訊,並告知 WebSocket 協議版本。服務端確認協議版本,升級為 WebSocket 協議。之后如果有數據需要推送,會主動推送給客戶端。

WebSocket 握手時的請求頭和響應頭

Accept-Encoding: gzip, deflate, br
Accept-Language: zh,zh-TW;q=0.9,en-US;q=0.8,en;q=0.7,zh-CN;q=0.6
Cache-Control: no-cache
Connection: Upgrade  # 表示要升級協議
Host: 127.0.0.1:3000
Origin: http://localhost:3000
Pragma: no-cache
Sec-WebSocket-Extensions: permessage-deflate; client_max_window_bits
Sec-WebSocket-Key: bwb9SFiJONXhQ/A4pLaXIg==  # 對應服務端響應頭的Sec-WebSocket-Accept,由於沒有同源限制...客戶端隨機生成
Sec-WebSocket-Version: 13  # 表示websocket的版本。如果服務端不支持該版本,需要返回一個Sec-WebSocket-Versionheader,里面包含服務端支持的版本號。
Upgrade: websocket  # 要升級協議到websocket協議
請求頭字段
Connection: Upgrade
Sec-WebSocket-Accept: 2jrbCWSCPlzPtxarlGTp4Y8XD20= # 用來告知服務器願意發起一個websocket連接, 值根據客戶端請求頭的Sec-WebSocket-Key計算出來
Upgrade: websocket
響應頭字段

 

WebSocket 連接過程參數

  • WebSocket.onopen: 連接成功后的回調
  • WebSocket.onclose: 連接關閉后的回調
  • WebSocket.onerror: 連接失敗后的回調
  • WebSocket.onmessage: 客戶端接收到服務端數據的回調
  • webSocket.bufferedAmount: 未發送至服務器的二進制字節數
  • WebSocket.binaryType: 使用二進制的數據類型連接
  • WebSocket.protocol : 服務器選擇的下屬協議
  • WebSocket.url : WebSocket 的絕對路徑
  • WebSocket.readyState: 當前連接狀態,對應的四個常量

 

Python 簡單實現 WebSocket 服務端

# -*- coding: utf-8 -*-
import base64
import copy
import hashlib
import socket
import struct
import time

from threading import Thread



class WsServer:
    def __init__(self):
        self.users = set() # 用於存放連接的客戶端

    def get_headers(self, data):
        '''將請求頭轉換為字典'''
        header_dict = {}
        data = str(data, encoding="utf-8")
        header, body = data.split("\r\n\r\n", 1)
        header_list = header.split("\r\n")
        # print("---" * 22, body)
        for idx, v in enumerate(header_list):
            if idx == 0:
                if len(v.split(" ")) == 3:
                    header_dict['method'], header_dict['url'], header_dict['protocol'] = v.split(" ")
            else:
                key, value = v.split(":", 1)
                header_dict[key] = value.strip()
        return header_dict

    # 等待用戶連接
    def acce(self):
        conn, addr = sock.accept()
        # print("conn from ", conn, addr)
        self.users.add(conn)
        # 獲取握手消息,magic string ,sha1加密
        # 發送給客戶端
        data = conn.recv(1024)
        print("websocket client data: %s" % data)
        headers = self.get_headers(data)

        # 對請求頭中的sec-websocket-key進行加密
        response_tpl = "HTTP/1.1 101 Switching Protocols\r\n" \
                       "Upgrade:websocket\r\n" \
                       "Connection: Upgrade\r\n" \
                       "Sec-WebSocket-Accept: %s\r\n" \
                       "WebSocket-Location: ws://%s%s\r\n\r\n"

        magic_string = '258EAFA5-E914-47DA-95CA-C5AB0DC85B11'
        value = headers['Sec-WebSocket-Key'] + magic_string
        ac = base64.b64encode(hashlib.sha1(value.encode('utf-8')).digest())
        response_str = response_tpl % (ac.decode('utf-8'), headers['Host'], headers['url'])

        # 此處可加入校驗
        
        conn.send(bytes(response_str, encoding='utf-8'), )
        logger.info('Websocket shake hand success: %s' % conn)


    def send_msg(self, msg_bytes):
        """
        WebSocket服務端向客戶端發送消息
        :param conn: 客戶端連接到服務器端的socket對象,即: conn,address = socket.accept()
        :param msg_bytes: 向客戶端發送的字節
        :return:
        """
        token = b"\x81"  # 接收的第一字節,一般都是x81不變
        length = len(msg_bytes)
        if length < 126:
            token += struct.pack("B", length)
        elif length <= 0xFFFF:
            token += struct.pack("!BH", 126, length)
        else:
            token += struct.pack("!BQ", 127, length)

        msg = token + msg_bytes

        for conn in self.users:
            # 如果出錯就是客戶端斷開連接
            try:
                conn.send(msg)
            except Exception as e:
                print("%s 連接關閉: %s" % (conn, e))
                # 刪除斷開連接的記錄
                self.users.remove(conn)


    # 循環等待客戶端建立連接
    def th(self):
        while True:
            self.acce()



sock = socket.socket()
sock.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
sock.bind(("0.0.0.0", 3002)) # 監聽3002端口
sock.listen(5)

WS = WsServer()

Thread(target=WS.th).start()
View Code

 

HTML 中使用 JS 實現 WebSocket 客戶端

<!DOCTYPE HTML>
<html>
   <head>
   <meta charset="utf-8">
   <title>websocket通信客戶端</title>
       <script type="text/javascript">
         function WebSocketTest()
         {
            if ("WebSocket" in window)
            {
               // 打開一個 web socket
               var ws = new WebSocket("ws://10.10.6.91:5678");
               
               // 連接建立后的回調函數
               ws.onopen = function()
               {
               // Web Socket 已連接上,使用 send() 方法發送數據
                  ws.send("admin:123456");
                  alert("正在發送:admin:123456");
               };
               
               // 接收到服務器消息后的回調函數
               ws.onmessage = function (evt) 
               { 
                  var received_msg = evt.data;
                  if (received_msg.indexOf("sorry") == -1) {
                    alert("收到消息:"+received_msg);
                  }
                  
               };
               
               // 連接關閉后的回調函數
               ws.onclose = function()
               { 
                  // 關閉 websocket
                  alert("連接已關閉..."); 
               };
            }
            else
            {
               // 瀏覽器不支持 WebSocket
               alert("您的瀏覽器不支持 WebSocket!");
            }
         }
         
      </script>
   </head>

   <body onload="WebSocketTest()">

   </body>
</html>
View Code

 

WebSocket 添加握手自定義參數

  由於 WebSocket 沒有修改請求頭的方法,所以可在 url 后面接問號然后添加自定義參數

舉個例子

var token='dcvuahsdnfajw12kjfasfsdf34'
var  ws = new WebSocket("ws://" + url?token + "/webSocketServer");

 

心跳

  待更新...

 

 

                


免責聲明!

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



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