Python——Tornado框架(三)、WebSocket
参考博文:
一、WebSocket介绍
Http——socket实现,短链接。链接之后断开。只能请求响应。
WebSocket——socket实现,双工通道。想什么时候断就什么时候断。不仅请求响应,还能推送。
本质就是Socket创建链接,不断开。
知道了本质,就可以从socket入手:
二、WebSocket握手过程分析
- 服务端(socket服务端)
1、服务端开启socket,监听IP和端口
3、允许链接
*5、服务端接收到特殊值【特殊值加密,sha1加密,magic string=258EAFA5-E914-47DA-95CA-C5AB0DC85B11】(这个magic string 是固定值)
6、加密后的值发送给客户端 - 客户端(浏览器)
2、客户端发起请求(IP和端口)
*4、客户端生成一个特殊值,向服务端发送一段特殊值 “xxxxxxxxx”。切客户端自己加密这段特殊值,但不发送。
7、客户端接收到加密的的值,与自己加密的值相比较,如果一样的话,说明遵循了WebSocket协议。
收发数据
三、基于Python实现WebSocket握手过程
由于是通过socket实现的,先简单的搭建:
后端:
前端:
可以看到后端部分data是接收信息的,可以先看下接收到的信息有什么:
可以看到获取到了很多信息,可以发现很像 http 的请求头之类的东西。我们可以写一个函数对这段信息进行分隔:
def get_headers(data):
"""
将请求头格式化成字典
:param data:
:return:
"""
header_dict = {}
data = str(data, encoding='utf-8')
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
分割后的信息:
接着我们就能看到生成的随机字符串:
既然获取到这个值,就可以对它进行加密:
接着:
发送给客户端之后,就要进行对比:
效果:
可以看到成功实现,如果把maigc_srring稍微改动一点,就可以看到链接失败:
四、WebSocket数据解析过程
1、客户端向服务端发送消息
客户端发送:
服务端接收:
然后结果:
可以看到这就是我们接收到的东西。
接收到数据以后,需要通过一定的方式才能看到信息。
2、数据解析
因为一个字节8位,所以前8个数字代表一个字节。
因为info接收到这些字节,所以 Info[0],就是拿第一个字节。要计算一个字节后四位的值,可以通过 跟 00001111 进行与运算,就能知道相对应的值。
(看上图)
所以 oncode = info[0] & 15
如果我们想要 fin (上图第一个)的值,可以通过拿到第一个字节,向右移动7位,就可以拿到:
fin = info[0] >>7
可以看到payload len占7位。(如果全是1,则最大是127)
所以 payload len的值为:
payload_len = info[1] & 127
重点:payload len的长度会决定往后占多少,后面占几位,payload len说了算。
如果payload len小于126,表示:
表示刚好就这么多位置,后面放数据。
如果payload len等于126:
再往后延伸16位(两个字节)
如果payload len大于126:
往后延伸64位(8个字节)
3、masking key
数据部分的前面4个字节,是masking key,发送的数据已经进行加密,需要通过前面4个字节进行解密。
数据头—masking key—数据
4、数据解包
既然知道了原理,就可以进行解包:
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)
能分割开了,就能去解码了(原理):
后端:
然后再次看效果:
就能发现成功接收到数据。如果字节是一个一个解,则只能解码英文。因为中文是三个三个字节。中文就不要直接解成字符串,中文先解成字节,最后全部都是字节,再统一转换成字符串。所以这里不仅能解码英文,还能解码中文:
5、客户端服务端互发消息
如果我们加上一个循环,就能不断接收:
后端:
然后服务端发送消息:
代码:
前端:
效果: