python之WebSocket协议
python之WebSocket协议
⼀、WebSocket理论部分
1、websocket是什么
Websocket是html5提出的⼀个协议规范,参考rfc6455。
websocket约定了⼀个通信的规范,通过⼀个握⼿的机制,客户端(浏览器)和服务器(webserver)之间能建⽴⼀个类似tcp的连接,从⽽⽅便c-s之间的通信。在websocket出现之前,web交互⼀般是基于http协议的短连接或者长连接。
WebSocket是为解决客户端与服务端实时通信⽽产⽣的技术。websocket协议本质上是⼀个基于tcp的协议,是先通过HTTP/HTTPS协议发起⼀条特殊的http请求进⾏握⼿后创建⼀个⽤于交换数据的TCP连接,此后服务端与客户端通过此TCP连接进⾏实时通信。
注意:此时不再需要原HTTP协议的参与了。
2、websocket的优点
以前web server实现推送技术或者即时通讯,⽤的都是轮询(polling),在特点的时间间隔(⽐如1秒钟)由浏览器⾃动发出请求,将服务器的消息主动的拉回来,在这种情况下,我们需要不断的向服务器发送请求,然⽽HTTP request 的header是⾮常长的,⾥⾯包含的数据可能只是⼀个很⼩的值,这样会占⽤很多的带宽和服务器资源。
⽽最⽐较新的技术去做轮询的效果是Comet – ⽤了AJAX。但这种技术虽然可达到全双⼯通信,但依然需要发出请求(reuqest)。
WebSocket API最伟⼤之处在于服务器和客户端可以在给定的时间范围内的任意时刻,相互推送信息。浏览器和服务器只需要要做⼀个握⼿的动作,在建⽴连接之后,服务器可以主动传送数据给客户端,客户端也可以随时向服务器发送数据。此外,服务器与客户端之间交换的标头信息很⼩。
WebSocket并不限于以Ajax(或XHR)⽅式通信,因为Ajax技术需要客户端发起请求,⽽WebSocket服务器和客户端可以彼此相互推送信息;因此从服务器⾓度来说,websocket有以下好处:
1. 节省每次请求的header
http的header⼀般有⼏⼗字节
2. Server Push
沈阳公租房申请条件服务器可以主动传送数据给客户端
3、websocket的协议规范
1.基于flash的握⼿协议
使⽤场景是IE的多数版本,因为IE的多数版本不都不⽀持WebSocket协议,以及FF、CHROME等浏览器的低版本,还没有原⽣的⽀持WebSocket。此处,server唯⼀要做的,就是准备⼀个WebSocket-Location域给client,没有加密,可靠性很差。
2.基于md5加密⽅式的握⼿协议
其中 Sec-WebSocket-Key1,Sec-WebSocket-Key2 和 [8-byte security key] 这⼏个头信息是web server⽤来⽣成应答信息的来源,依据draft-hixie-thewebsocketprotocol-76 草案的定义。
web server基于以下的算法来产⽣正确的应答信息:
1. 逐个字符读取 Sec-WebSocket-Key1 头信息中的值,将数值型字符连接到⼀起放到⼀个临时字符串⾥,同时统计所有空格的数量;
2. 将在第(1)步⾥⽣成的数字字符串转换成⼀个整型数字,然后除以第(1)步⾥统计出来的空格数量,将得到的浮点数转换成整数型;
3. 将第(2)步⾥⽣成的整型值转换为符合⽹络传输的⽹络字节数组;
4. 对 Sec-WebSocket-Key2 头信息同样进⾏第(1)到第(3)步的操作,得到另外⼀个⽹络字节数组;
5. 将 [8-byte security key] 和在第(3)、(4)步⾥⽣成的⽹络字节数组合并成⼀个16字节的数组;
6. 对第(5)步⽣成的字节数组使⽤MD5算法⽣成⼀个哈希值,这个哈希值就作为安全密钥返回给客户端,以表明服务器端获取了客户端的请求,同意创建websocket连接3.基于sha加密⽅式的握⼿协议
也是⽬前见的最多的⼀种⽅式,这⾥的版本号⽬前是需要13以上的版本。
客户端请求:
GET /ls HTTP/1.1
Upgrade: websocket
Connection: Upgrade
Host: www.qixing318
Sec-WebSocket-Origin:
Sec-WebSocket-Key: 2SCVXUeP9cTjV+0mWB8J6A==
Sec-WebSocket-Version: 13
服务器返回:
HTTP/1.1 101 Switching Protocols
Upgrade: websocket Connection:
Upgrade Sec-WebSocket-Accept: mLDKNeBNWz6T9SxU+o0Fy/HgeSw=
其中 server就是把客户端上报的key拼上⼀段GUID( “258EAFA5-E914-47DA-95CA-C5AB0DC85B11″),拿这个字符串做SHA-1 hash计算,然后再把得到的结果通过base64加密,最后再返回给客户端。重庆银行贷款
-格式:\r\n
-创建链接之后默认不断开
4、基于sha加密的Opening Handshake(握⼿环节)
客户端发起连接Handshake请求
GET /chat HTTP/1.1
Host: ample
Upgrade: websocket
Connection: Upgrade
Sec-WebSocket-Key: dGhlIHNhbXBsZSBub25jZQ==
Origin: example
Sec-WebSocket-Protocol: chat, superchat
Sec-WebSocket-Version: 13
服务器端响应:
HTTP/1.1 101 Switching Protocols
Upgrade: websocket
Connection: Upgrade
Sec-WebSocket-Accept: s3pPLMBiTxaQ9kYGzzhZRbK+xOo=
Sec-WebSocket-Protocol: chat
Upgrade:WebSocket
表⽰这是⼀个特殊的 HTTP 请求,请求的⽬的就是要将客户端和服务器端的通讯协议从 HTTP 协议升级到 WebSocket 协议。
Sec-WebSocket-Key
是⼀段浏览器base64加密的密钥,server端收到后需要提取Sec-WebSocket-Key 信息,然后加密。
Sec-WebSocket-Accept
服务器端在接收到的Sec-WebSocket-Key密钥后追加⼀段神奇字符串“258EAFA5-E914-47DA-95CA-C5AB0DC85B11”,并将结果进⾏sha-1哈希,然后再进⾏base64加密返回给客户端(就是Sec-WebSocket-Key)。⽐如:
function encry($req)
{
$key = $this->getKey($req);
$mask = "258EAFA5-E914-47DA-95CA-C5AB0DC85B11";
# 将 SHA-1 加密后的字符串再进⾏⼀次 base64 加密
return base64_encode(sha1($key . '258EAFA5-E914-47DA-95CA-C5AB0DC85B11', true));
}
如果加密算法错误,客户端在进⾏校检的时候会直接报错。如果握⼿成功,则客户端侧会出发onopen
事件。
Sec-WebSocket-Protocol
表⽰客户端请求提供的可供选择的⼦协议,及服务器端选中的⽀持的⼦协议,“Origin”服务器端⽤于区分未授权的websocket浏览器
Sec-WebSocket-Version: 13
客户端在握⼿时的请求中携带,这样的版本标识,表⽰这个是⼀个升级版本,现在的浏览器都是使⽤的这个版本。
HTTP/1.1 101 Switching Protocols
101为服务器返回的状态码,所有⾮101的状态码都表⽰handshake并未完成。
Data Framing
Websocket协议通过序列化的数据帧传输数据。数据封包协议中定义了opcode、payload length、Payload data等字段。其中要求:
1. 客户端向服务器传输的数据帧必须进⾏掩码处理:服务器若接收到未经过掩码处理的数据帧,则必须主动关闭连接。
2. 服务器向客户端传输的数据帧⼀定不能进⾏掩码处理。客户端若接收到经过掩码处理的数据帧,则必须主动关闭连接。
针对上情况,发现错误的⼀⽅可向对⽅发送close帧(状态码是1002,表⽰协议错误),以关闭连接。
具体数据帧格式如下图所⽰:
FIN
标识是否为此消息的最后⼀个数据包,占 1 bit
RSV1, RSV2, RSV3: ⽤于扩展协议,⼀般为0,各占1bit
Opcode
数据包类型(frame type),占4bits
0x0:标识⼀个中间数据包
0x1:标识⼀个text类型数据包
0x2:标识⼀个binary类型数据包
0x3-7:保留
0x8:标识⼀个断开连接类型数据包
0x9:标识⼀个ping类型数据包
0xA:表⽰⼀个pong类型数据包
0xB-F:保留
MASK:占1bits
⽤于标识PayloadData是否经过掩码处理。如果是1,Masking-key域的数据即是掩码密钥,⽤于解码PayloadData。客户端发出的数据帧需要进⾏掩码处理,所以此位是1。
Payload length
Payload data的长度,占7bits,7+16bits,7+64bits:
如果其值在0-125,则是payload的真实长度。
如果值是126,则后⾯2个字节形成的16bits⽆符号整型数的值是payload的真实长度。注意,⽹络字节序,需要转换。
如果值是127,则后⾯8个字节形成的64bits⽆符号整型数的值是payload的真实长度。注意,⽹络字节序,需要转换。
这⾥的长度表⽰遵循⼀个原则,⽤最少的字节表⽰长度(尽量减少不必要的传输)。举例说,payload真实长度是124,在0-125之间,必须⽤前7位表⽰;不允许长度1是126或127,然后长度2是124,这样违反原则。
Payload data
应⽤层数据
server解析client端的数据
接收到客户端数据后的解析规则如下:
1byte
1bit: frame-fin,x0表⽰该message后续还有frame;x1表⽰是message的最后⼀个frame
3bit: 分别是frame-rsv1、frame-rsv2和frame-rsv3,通常都是x0
4bit: frame-opcode,x0表⽰是延续frame;x1表⽰⽂本frame;x2表⽰⼆进制frame;x3-7保留给⾮控制frame;x8表⽰关闭
连接;x9表⽰ping;xA表⽰pong;xB-F保留给控制frame
2byte
1bit: Mask,1表⽰该frame包含掩码;0表⽰⽆掩码
7bit、7bit+2byte、7bit+8byte: 7bit取整数值,若在0-125之间,则是负载数据长度;若是126表⽰,后两个byte取⽆符号16
位整数值,是负载长度;127表⽰后8个 byte,取64位⽆符号整数值,是负载长度
3-6byte: 这⾥假定负载长度在0-125之间,并且Mask为1,则这4个byte是掩码
7-end byte: 长度是上⾯取出的负载长度,包括扩展数据和应⽤数据两部分,通常没有扩展数据;若Mask为1,则此数据需
要解码,解码规则为- 1-4byte掩码循环和数据byte做异或操作。
⽰例代码:
while True:
# 对数据进⾏解密
# send_msg(conn, bytes('alex', encoding='utf-8'))
# send_msg(conn, bytes('SB', encoding='utf-8'))
# info = v(8096)
# print(info)
info = v(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)
msg = str(bytes_list, encoding='utf-8')
rep = msg + 'sb'
send_msg(conn,bytes(rep,encoding='utf-8'))
5、原理代码:
import socket
import hashlib
import base64
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
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
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()
# WebSocket发来的连接
# 1. 获取握⼿数据
data = v(1024)
headers = get_headers(data)
# 2. 对握⼿信息进⾏加密:
magic_string = '258EAFA5-E914-47DA-95CA-C5AB0DC85B11'
value = headers['Sec-WebSocket-Key'] + magic_string
ac = base64.b64encode(hashlib.de('utf-8')).digest())
# 3. 返回握⼿信息
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://127.0.0.1:8002\r\n\r\n"
response_str = response_tpl % (ac.decode('utf-8'),)
conn.sendall(bytes(response_str, encoding='utf-8'))
# 之后,才能进⾏⾸发数据。
while True:
# 对数据进⾏解密
# send_msg(conn, bytes('alex', encoding='utf-8'))
# send_msg(conn, bytes('SB', encoding='utf-8'))
# info = v(8096)
# print(info)
info = v(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)
msg = str(bytes_list, encoding='utf-8')
rep = msg + 'sb'
send_msg(conn,bytes(rep,encoding='utf-8'))
后端
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
</head>
<body>
<h1>WebSocket协议学习</h1>
<script type="text/javascript">
// 向 127.0.0.1:8002 发送⼀个WebSocket请求
var socket = new WebSocket("ws://127.0.0.1:8002");
/* 服务器端向客户端发送数据时,⾃动执⾏ */
var response = event.data;
console.log(response);
};
</script>
</body>
</html>
前端
⼆、应⽤:
1、Flask中应⽤: pip3 install gevent-websocket from flask import Flask,request,render_template,session,redirect import uuid
import json
海贼王d的意志from geventwebsocket.handler import WebSocketHandler
北京两日游from gevent.pywsgi import WSGIServer
app = Flask(__name__)
app.secret_key = 'asdfasdf'
GENTIEMAN = {
'1':{'name':'钢弹','count':0},
'2':{'name':'铁锤','count':0},
'3':{'name':'闫帅','count':0},
}
WEBSOCKET_DICT = {
}
@app.before_request
def before_request():
if request.path == '/login':
return None
user_info = ('user_info')
if user_info:
return None
return redirect('/login')
@ute('/login',methods=['GET','POST'])

版权声明:本站内容均来自互联网,仅供演示用,请勿用于商业和其他非法用途。如果侵犯了您的权益请与我们联系QQ:729038198,我们将在24小时内删除。