Websocket 學習
一、含義
WebSocket 是一種在單個TCP連接上進行全雙工通訊的協議。
WebSocket 使得客戶端和服務器之間的數據交換變得更加簡單,允許服務端主動向客戶端推送數據。在WebSocket API中,瀏覽器和服務器只需要完成一次握手,兩者之間就直接可以創建持久性的連接,並進行雙向數據傳輸。
二、Websocket 產生背景
很多網站為了實現推送技術,所用的技術都是輪詢。
輪詢是在特定的的時間間隔(如每1秒),由瀏覽器對服務器發出HTTP請求,然後由服務器返回最新的數據給客戶端的瀏覽器。
傳統的模式的缺點,即瀏覽器需要不斷的向服務器發出請求,然而HTTP請求可能包含較長的頭部,其中真正有效的數據可能只是很小的一部分,顯然這樣會浪費很多的帶寬等資源。
比較新的技術去做輪詢的效果是Comet。
Comet 是一種用於web的推送技術,能使服務器實時地將更新的信息傳送到客戶端,而無須客戶端發出請求,目前有兩種實現方式,長輪詢和iframe流。
長輪詢 是在打開一條連接以後保持,等待服務器推送來數據再關閉的方式。
iframe流 方式是在頁面中插入一個隱藏的iframe,利用其src屬性在服務器和客戶端之間創建一條長鏈接,服務器向iframe傳輸數據(通常是HTML,內有負責插入信息的javascript),來實時更新頁面。
Comet技術雖然可以雙向通信,但依然需要反復發出請求。
Comet中,普遍采用的長鏈接,也會消耗服務器資源。
Websocket使用和 HTTP 相同的 TCP 端口,可以繞過大多數防火墻的限制。
默認情況下,Websocket協議使用80端口;運行在TLS之上時,默認使用443端口。
WebSocket 是獨立的、創建在 TCP 上的協議。
Websocket 通過 HTTP/1.1 協議的101狀態碼進行握手。
為了創建Websocket連接,需要通過瀏覽器發出請求,之後服務器進行回應,這個過程通常稱為“握手”(handshaking)。
三、Python編寫Socket服務端
1.客戶端
客戶端:瀏覽器(必須要有socket包或者類庫,一般自帶,個別瀏覽器沒有,所以websocket有局限性) <script type="text/javascript"> # 創建連接 # 發送消息 # 接收驗證消息 下面的一句話做了上面的三件事 var socket = new WebSocket("ws://127.0.0.1:8002/xxoo"); # 與服務器端連接成功後,自動執行 socket.onopen = function () { }; # 服務器端向客戶端發送數據時,自動執行 socket.onmessage = function (event) { }; </script>
2.服務端
import socket sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM) sock.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1) sock.bind((‘127.0.0.1‘, 8002)) sock.listen(5) # 獲取客戶端socket對象 conn, address = sock.accept() # 獲取客戶端的【握手】信息 data = conn.recv(1024) ... ... ... conn.send(‘響應【握手】信息‘)
請求握手時,瀏覽器發來的請求信息
GET /chatsocket HTTP/1.1 Host: 127.0.0.1:8002 Connection: Upgrade Pragma: no-cache Cache-Control: no-cache Upgrade: websocket Origin: http://localhost:63342 Sec-WebSocket-Version: 13 Sec-WebSocket-Key: mnwFxiOlctXFN/DeMt1Amg== Sec-WebSocket-Extensions: permessage-deflate; client_max_window_bits
請求和響應的【握手】信息需要遵循規則:
- 從請求【握手】信息中提取 Sec-WebSocket-Key
- 利用magic_string 和 Sec-WebSocket-Key 進行hmac1加密,再進行base64加密
- 將加密結果響應給客戶端
註:magic string為:258EAFA5-E914-47DA-95CA-C5AB0DC85B11
其中 Sec
-
WebSocket
-
Key: mnwFxiOlctXFN
/
DeMt1Amg
=
= 是瀏覽器給服務端的一個隨機字符串(
mnwFxiOlctXFN
/
DeMt1Amg
=
=
),服務端需要給這個隨機字符串進行加密,然後在給瀏覽器send數據的時候帶上這個加密後的隨機字符串,如果瀏覽器可以解密,那麽就是說服務端支持websocket,進而進行建立連接通信。
提取Sec-WebSocket-Key值並加密:
import socket import base64 import hashlib def get_headers(data): """ 將請求頭格式化成字典 :param data: :return: """ header_dict = {} data = str(data, encoding=‘utf-8‘) for i in data.split(‘\r\n‘): print(i) header, body = data.split(‘\r\n\r\n‘, 1) header_list = header.split(‘\r\n‘) for i in range(0, len(header_list)): if i == 0: if len(header_list[i].split(‘ ‘)) == 3: header_dict[‘method‘], header_dict[‘url‘], header_dict[‘protocol‘] = header_list[i].split(‘ ‘) else: k, v = header_list[i].split(‘:‘, 1) header_dict[k] = v.strip() return header_dict sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM) sock.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1) sock.bind((‘127.0.0.1‘, 8002)) sock.listen(5) conn, address = sock.accept() data = conn.recv(1024) headers = 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‘)) ... ... ...
此外還有服務端需要實現封包解包的功能,而客戶端瀏覽器的JS已經幫我們實現了封包解包了。
封包解包詳見:武沛齊
Websocket 學習