之前在协助建军做自动通知前端刷新和最近分片上传大文件的时候都使用到了websocket,对websocket做了一些调研,这篇文章就跟大家一起分享一下。
websocket的使用场景:
一般我们做聊天室这种需要保持通讯状态的功能,如果是用HTTP协议去做的话,需要客户端一直做长轮询去查询服务端是否由新消息,再显示在客户端上,这是因为HTTP本身是一个短连接,当我们客户端发起请求时,服务器端响应完请求将结果返回给客户端,这个连接就自动断开了,这个过程http服务端是被动的,而websocket不仅客户端可以做请求响应,服务器端也可以做请求响应,即它可以实现往客户端主动推送消息,这样做聊天室就非常方便了。
websocket它实现了浏览器与服务器全双工(full-duplex)通信。其本质是保持TCP连接,在浏览器和服务端通过Socket进行通信。而HTTP只支持单工通信,http自动断开,websocket会一直保持长连接,除非你主动请求断开
WebSocket 目前在各大主流浏览器的支持都比较好
websockect的握手过程:
websockect实际只有一次HTTP的握手,服务端就能一直与客户端保持通信,直到关闭连接,接下来就来解析这一次握手的细节
1,服务器开启socket,监听某个端口,等待请求
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) # 等待用户连接 conn, address = sock.accept() |
2,客户端有比较完善的Websocket类库供我们使用,于是我们在客户端直接建立连接,并开始握手
var socket = new WebSocket("ws://127.0.0.1:8002/"); |
前端的Websocket类实例化后为我们干了一些事:
a. 不仅连接还会向服务器端发送握手信息,握手信息是byte类型,一个HTTP协议报文,里面还包含有Sec-WebSocket-Key 这个随机字符串
b. 负责接收服务器端返回的加密数据并进行验证,从而完成握手
这里是握手信息:
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 |
3,服务器端接收到客户端的信息,并进行utf-8将byte转码,并解析里面的http请求报文,将 Sec-WebSocket-Key 这个字符串提取出来,将这个字符串和magic string进行拼接,(注:magic string是固定的,值为258EAFA5-E914-47DA-95CA-C5AB0DC85B11),然后将拼接好的字符串用base64进行加密,(这些操作都是websocket协议定义死的,包括magic string值)将加密好的字符串发送给前端,前端将自己加密的和服务器发送过的对比,一致则握手成功,否则失败报错。
magic_string = '258EAFA5-E914-47DA-95CA-C5AB0DC85B11' value = headers['Sec-WebSocket-Key'] + magic_string ac = base64.b64encode(hashlib.sha1(value.encode('utf-8')).digest()) |
websockect的数据收发过程:
一次握手成功后客户端和服务端就可以进行数据的收发了。数据的收发实质就是解包和封包的过程
1,获取客户端发送的数据【解包】
info = conn.recv(8096) payload_len = info[1] & 127 if payload_len == 126: extend_payload_len = info[2:4] mask = info[4:8] decoded = info[8:] elif payload_len == 127: 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) body = str(bytes_list, encoding='utf-8') print(body) |
解包过程
0
1
2
3
0
1
2
3
4
5
6
7
8
9
0
1
2
3
4
5
6
7
8
9
0
1
2
3
4
5
6
7
8
9
0
1
+
-
+
-
+
-
+
-
+
-
-
-
-
-
-
-
+
-
+
-
-
-
-
-
-
-
-
-
-
-
-
-
+
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
+
|F|R|R|R| opcode|M| Payload
len
| Extended payload length |
|I|S|S|S| (
4
) |A| (
7
) | (
16
/
64
) |
|N|V|V|V| |S| | (
if
payload
len
=
=
126
/
127
) |
| |
1
|
2
|
3
| |K| | |
+
-
+
-
+
-
+
-
+
-
-
-
-
-
-
-
+
-
+
-
-
-
-
-
-
-
-
-
-
-
-
-
+
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
+
| Extended payload length continued,
if
payload
len
=
=
127
|
+
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
+
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
+
| |Masking
-
key,
if
MASK
set
to
1
|
+
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
+
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
+
| Masking
-
key (continued) | Payload Data |
+
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
+
: Payload Data continued ... :
+
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
+
| Payload Data continued ... |
+
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
+
2,向客户端发送数据【封包】
def send_msg(conn, msg_bytes): """ WebSocket服务端向客户端发送消息 :param conn: 客户端连接到服务器端的socket对象,即: conn,address = socket.accept() :param msg_bytes: 向客户端发送的字节 :return: """ import struct token = b"\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 conn.send(msg) return True |
附:下面摘抄网上一段客户端和服务端的对话,形象的帮助我们理解websocket和ajax轮询的区别:
ajax轮询
ajax轮询的原理非常简单,让浏览器隔个几秒就发送一次请求,询问服务器是否有新信息。
场景再现:
客户端:啦啦啦,有没有新信息(Request)
服务端:没有(Response)
客户端:啦啦啦,有没有新信息(Request)
服务端:没有。。(Response)
客户端:啦啦啦,有没有新信息(Request)
服务端:你好烦啊,没有啊。。(Response)
客户端:啦啦啦,有没有新消息(Request)
服务端:好啦好啦,有啦给你。(Response)
客户端:啦啦啦,有没有新消息(Request)
服务端:。。。。。没。。。。没。。。没有(Response) —- loop
Websocket
客户端:啦啦啦,我要建立Websocket协议,需要的服务:chat,Websocket协议版本:17(HTTP Request)
服务端:ok,确认,已升级为Websocket协议(HTTP Protocols Switched)
客户端:麻烦你有信息的时候推送给我噢。。
服务端:ok,有的时候会告诉你的。
服务端:balabalabalabala
服务端:balabalabalabala
服务端:哈哈哈哈哈啊哈哈哈哈
服务端:笑死我了哈哈哈哈哈哈哈
就变成了这样,只需要经过一次HTTP请求,就可以做到源源不断的信息传送了。(在程序设计中,这种设计叫做回调,即:你有信息了再来通知我,而不是我傻乎乎的每次跑来问你 )