一、什么是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'x81x84xa3lxcfx10x92^xfc$'
客户端发送到server的websocket的报文分为四个部分:
a、固定部分‘81’
b、报文内容长度
c、掩码 b'xa3lxcfx10'
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 " "Upgrade:websocket " "Connection: Upgrade " "Sec-WebSocket-Accept: {1} " "WebSocket-Location: ws://{2}/chat " "WebSocket-Protocol:chat " 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>