what's the WebSocket
WebSocket 是一種通信協議,區別於 HTTP 協議,HTTP協議只能實現客戶端請求,服務端響應的這種單項通信。而 WebSocket 可以實現客戶端與服務端的雙向通訊,最大也是最明顯的區別就是可以做到服務端主動將消息推送給客戶端。
WebSocket 和 普通 HTTP 請求不同點
WebSocket 由服務端主動推送數據到客戶端,普通 HTTP 請求需要客戶端每次項服務端發送請求后才能得到響應
- 長連接:只需要建立一次握手請求后就可以一直得到服務端推送的數據
- 數據格式輕量,性能開銷小。客戶端與服務端進行數據交換時,服務端到客戶端的數據包頭只有2到10字節,客戶端到服務端需要加上另外4字節的掩碼。HTTP每次都需要攜帶完整頭部。
- 更好的二進制支持,可以發送文本,和二進制數據
- 沒有同源限制,客戶端可以與任意服務器通信
- 協議標識符是 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()
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>
WebSocket 添加握手自定義參數
由於 WebSocket 沒有修改請求頭的方法,所以可在 url 后面接問號然后添加自定義參數
舉個例子
var token='dcvuahsdnfajw12kjfasfsdf34' var ws = new WebSocket("ws://" + url?token + "/webSocketServer");
心跳
待更新...