一、什么是websocket
WebSocket是HTML5開始提供的一種在單個 TCP 連接上進行全雙工通訊的協議。
在WebSocket API中,瀏覽器和服務器只需要做一個握手的動作,然后,瀏覽器和服務器之間就形成了一條快速通道。兩者之間就直接可以數據互相傳送。
瀏覽器通過 JavaScript 向服務器發出建立 WebSocket 連接的請求,連接建立以后,客戶端和服務器端就可以通過 TCP 連接直接交換數據。
當你獲取 Web Socket 連接后,你可以通過 send() 方法來向服務器發送數據,並通過 onmessage 事件來接收服務器返回的數據。 ----------------- 出自菜鳥教程
二、客戶端請求報文
客戶端請求的鏈接:ws://localhost:8080
和傳統http報文不同的地方:
Connection: Upgrade
Upgrade: websocket ----- 表示發起的是websocket協議
Sec-WebSocket-Key: TD7emWUct4iW4vddYWbMqQ== ------ 由瀏覽器隨機生成,提供基本的防護
Sec-WebSocket-Extensions: permessage-deflate; client_max_window_bits ---- 協議的擴展
Sec-WebSocket-Version: 13 ---- 版本號
三、服務器接收請求報文
服務器收到請求報文后,會發起tcp的三次握手,和客戶端建立鏈接,這個地方和tcpsocket基本一樣。
# 創建基於tcp的服務器 serverSocket = socket.socket(socket.AF_INET, socket.SOCK_STREAM) serverSocket.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1) host = (HOST, PORT) serverSocket.bind(host) serverSocket.listen(128) print("服務器運行, 等待用戶鏈接") while True: # print("getting connection") clientSocket, addressInfo = serverSocket.accept() # print("get connected") request = clientSocket.recv(2048) print(request.decode()) # 獲取Sec-WebSocket-Key ret = re.search(r"Sec-WebSocket-Key: (.*==)", str(request.decode())) if ret: key = ret.group(1) else: return Sec_WebSocket_Key = key + MAGIC_STRING # print("key ", Sec_WebSocket_Key) # 將Sec-WebSocket-Key先進行sha1加密,轉成二進制后在使用base64加密 response_key = base64.b64encode(hashlib.sha1(bytes(Sec_WebSocket_Key, encoding="utf8")).digest()) response_key_str = str(response_key) response_key_str = response_key_str[2:30] # print(response_key_str) # 構建websocket返回數據 response = HANDSHAKE_STRING.replace("{1}", response_key_str).replace("{2}", HOST + ":" + str(PORT)) clientSocket.send(response.encode()) # print("send the hand shake data")
四、因為websocket是基於tcp的全雙工通信協議,所以,他可以一邊接收,一邊發送
1、接收並解析websocket報文
b'\x81\x84\xa3l\xcf\x10\x92^\xfc$'
客戶端發送到server的websocket的報文分為四個部分:
a、固定部分‘\81’
b、報文內容長度
c、掩碼 b'\xa3l\xcf\x10'
d、報文內容 b'\x92^\xfc$'
def recv_data(clientSocket): try: info = clientSocket.recv(2048) if not info: return except: return else: code_len = info[1] & 0x7f if code_len == 0x7e: extend_payload_len = info[2:4] mask = info[4:8] decoded = info[8:] elif code_len == 0x7f: extend_payload_len = info[2:10] mask = info[10:14] decoded = info[14:] else: extend_payload_len = None mask = info[2:6] decoded = info[6:] bytes_list = bytearray() for i in range(len(decoded)): chunk = decoded[i] ^ mask[i % 4] bytes_list.append(chunk) raw_str = str(bytes_list, encoding="utf-8") print(raw_str)
2、server端發送數據
server端發送數據分為三個部分
a、固定部分‘\81’
b、報文長度
c、報文內容
struct用法:
Format C Type Python type Standard size Notes x pad byte no value c char bytes of length 1 1 b signed char integer 1 (1),(3) B unsigned char integer 1 (3) ? _Bool bool 1 (1) h short integer 2 (3) H unsigned short integer 2 (3) i int integer 4 (3) I unsigned int integer 4 (3) l long integer 4 (3) L unsigned long integer 4 (3) q long long integer 8 (2), (3) Q unsigned long long integer 8 (2), (3) n ssize_t integer (4) N size_t integer (4) e (7) float 2 (5) f float float 4 (5) d double float 8 (5) s char[] bytes p char[] bytes P void * integer (6)
Character Byte order Size Alignment @ native native native = native standard none < little-endian standard none > big-endian standard none ! network (= big-endian) standard none
服務端發送數據代碼:
def send_data(clientSocket): data = "need to send messages中文" token = b'\x81' length = len(data.encode()) if length<=125: token += struct.pack('B', length) elif length <= 0xFFFF: token += struct.pack('!BH', 126, length) else: token += struct.pack('!BQ', 127, length) data = token + data.encode() clientSocket.send(data)
全部代碼:
py:
import socket import base64 import hashlib import re import threading import struct HOST = "localhost" PORT = 8080 MAGIC_STRING = '258EAFA5-E914-47DA-95CA-C5AB0DC85B11' HANDSHAKE_STRING = "HTTP/1.1 101 Switching Protocols\r\n" \ "Upgrade:websocket\r\n" \ "Connection: Upgrade\r\n" \ "Sec-WebSocket-Accept: {1}\r\n" \ "WebSocket-Location: ws://{2}/chat\r\n" \ "WebSocket-Protocol:chat\r\n\r\n" def recv_data(clientSocket): try: info = clientSocket.recv(2048) if not info: return except: return else: print(info) code_len = info[1] & 0x7f if code_len == 0x7e: extend_payload_len = info[2:4] mask = info[4:8] decoded = info[8:] elif code_len == 0x7f: extend_payload_len = info[2:10] mask = info[10:14] decoded = info[14:] else: extend_payload_len = None mask = info[2:6] decoded = info[6:] bytes_list = bytearray() print(mask) print(decoded) for i in range(len(decoded)): chunk = decoded[i] ^ mask[i % 4] bytes_list.append(chunk) raw_str = str(bytes_list, encoding="utf-8") print(raw_str) def send_data(clientSocket): data = "need to send messages中文" token = b'\x81' length = len(data.encode()) if length<=125: token += struct.pack('B', length) elif length <= 0xFFFF: token += struct.pack('!BH', 126, length) else: token += struct.pack('!BQ', 127, length) data = token + data.encode() clientSocket.send(data) def handshake(serverSocket): while True: # print("getting connection") clientSocket, addressInfo = serverSocket.accept() # print("get connected") request = clientSocket.recv(2048) print(request.decode()) # 獲取Sec-WebSocket-Key ret = re.search(r"Sec-WebSocket-Key: (.*==)", str(request.decode())) if ret: key = ret.group(1) else: return Sec_WebSocket_Key = key + MAGIC_STRING # print("key ", Sec_WebSocket_Key) # 將Sec-WebSocket-Key先進行sha1加密,轉成二進制后在使用base64加密 response_key = base64.b64encode(hashlib.sha1(bytes(Sec_WebSocket_Key, encoding="utf8")).digest()) response_key_str = str(response_key) response_key_str = response_key_str[2:30] # print(response_key_str) # 構建websocket返回數據 response = HANDSHAKE_STRING.replace("{1}", response_key_str).replace("{2}", HOST + ":" + str(PORT)) clientSocket.send(response.encode()) # print("send the hand shake data") t1 = threading.Thread(target = recv_data, args = (clientSocket,)) t1.start() t2 = threading.Thread(target = send_data, args = (clientSocket,)) t2.start() def main(): # 創建基於tcp的服務器 serverSocket = socket.socket(socket.AF_INET, socket.SOCK_STREAM) serverSocket.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1) host = (HOST, PORT) serverSocket.bind(host) serverSocket.listen(128) print("服務器運行, 等待用戶鏈接") # 調用監聽 handshake(serverSocket) if __name__ == "__main__": main()
html:
<!DOCTYPE html> <html> <head> <title>w</title> <html> <head lang="en"> <meta charset="utf-8"> <title></title> </head> <body> <div> <input type="text" id="txt"/> <input type="button" id="btn" value="提交" onclick="sendMsg();"/> <input type="button" id="close" value="關閉連接" onclick="closeConn();"/> </div> <div id="content"></div> <script type="text/javascript"> var socket = new WebSocket("ws://127.0.0.1:8080"); socket.onopen = function () { /* 與服務器端連接成功后,自動執行 */ var newTag = document.createElement('div'); newTag.innerHTML = "【連接成功】"; document.getElementById('content').appendChild(newTag); }; socket.onmessage = function (event) { /* 服務器端向客戶端發送數據時,自動執行 */ var response = event.data; var newTag = document.createElement('div'); newTag.innerHTML = response; document.getElementById('content').appendChild(newTag); }; socket.onclose = function (event) { /* 服務器端主動斷開連接時,自動執行 */ var newTag = document.createElement('div'); newTag.innerHTML = "【關閉連接】"; document.getElementById('content').appendChild(newTag); }; function sendMsg() { var txt = document.getElementById('txt'); socket.send(txt.value); txt.value = ""; } function closeConn() { socket.close(); var newTag = document.createElement('div'); newTag.innerHTML = "【關閉連接】"; document.getElementById('content').appendChild(newTag); } </script> </body> </html>