WebSocket协议!是每个Python程序员都必懂的协议!别说你不知道
2、websocket的优点 以前web server实现推送技术或者即时通讯,用的都是轮询(polling),在特点的时间间隔(比如1秒钟)由浏览器自动发出请求,将服务器的消息主动的拉回来,在这种情况下,我们需要不断的向服务器发送请求,然而HTTP request 的header是非常长的,里面包含的数据可能只是一个很小的值,这样会占用很多的带宽和服务器资源。 进群:548377875 ? 即可获取数十套PDF哦! 而最比较新的技术去做轮询的效果是Comet – 用了AJAX。但这种技术虽然可达到全双工通信,但依然需要发出请求(reuqest)。 WebSocket API最伟大之处在于服务器和客户端可以在给定的时间范围内的任意时刻,相互推送信息。 浏览器和服务器只需要要做一个握手的动作,在建立连接之后,服务器可以主动传送数据给客户端,客户端也可以随时向服务器发送数据。 此外,服务器与客户端之间交换的标头信息很小。 WebSocket并不限于以Ajax(或XHR)方式通信,因为Ajax技术需要客户端发起请求,而WebSocket服务器和客户端可以彼此相互推送信息; 因此从服务器角度来说,websocket有以下好处: 3.3基于sha加密方式的握手协议 也是目前见的最多的一种方式,这里的版本号目前是需要13以上的版本。 客户端请求: 3.4、基于sha加密的Opening Handshake(握手环节) 客户端发起连接Handshake请求 Data Framing Websocket协议通过序列化的数据帧传输数据。数据封包协议中定义了opcode、payload length、Payload data等字段。其中要求:
针对上情况,发现错误的一方可向对方发送close帧(状态码是1002,表示协议错误),以关闭连接。 Payload length Payload data的长度,占7bits,7+16bits,7+64bits:
这里的长度表示遵循一个原则,用最少的字节表示长度(尽量减少不必要的传输)。举例说,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 = conn.recv(8096) # print(info) 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) 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(' ',1) header_list = header.split(' ') 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 = conn.recv(1024) headers = get_headers(data) # 2. 对握手信息进行加密: magic_string = '258EAFA5-E914-47DA-95CA-C5AB0DC85B11' value = headers['Sec-WebSocket-Key'] + magic_string ac = base64.b64encode(hashlib.sha1(value.encode('utf-8')).digest()) # 3. 返回握手信息 response_tpl = "HTTP/1.1 101 Switching Protocols " "Upgrade:websocket " "Connection: Upgrade " "Sec-WebSocket-Accept: %s " "WebSocket-Location: ws://127.0.0.1:8002 " response_str = response_tpl % (ac.decode('utf-8'),) conn.sendall(bytes(response_str,encoding='utf-8')) # 之后,才能进行首发数据。 while True: # 对数据进行解密 # send_msg(conn,encoding='utf-8')) 二、应用: 1、Flask中应用: pip3 install gevent-websocket View Code from flask import Flask,request,render_template,session,redirect import uuid import json from geventwebsocket.handler import WebSocketHandler from gevent.pywsgi import WSGIServer app = Flask(__name__) app.secret_key = 'asdfasdf' GENTIEMAN = { '1':{'name':'钢弹','count':0}, '2':{'name':'铁锤', '3':{'name':'闫帅', } WEBSOCKET_DICT = { } @app.before_request def before_request(): if request.path == '/login': return None user_info = session.get('user_info') if user_info: return None return redirect('/login') @app.route('/login',methods=['GET','POST']) def login(): if request.method == "GET": return render_template('login.html') else: uid = str(uuid.uuid4()) session['user_info'] = {'id':uid,'name':request.form.get('user')} return redirect('/index') @app.route('/index') def index(): return render_template('index.html',users=GENTIEMAN) @app.route('/message') def message(): # 1. 判断到底是否是websocket请求? ws = request.environ.get('wsgi.websocket') if not ws: return "请使用WebSocket协议" # ----- ws连接成功 ------- current_user_id = session['user_info']['id'] WEBSOCKET_DICT[current_user_id] = ws while True: # 2. 等待用户发送消息,并接受 message = ws.receive() # 帅哥ID # 关闭:message=None if not message: del WEBSOCKET_DICT[current_user_id] break # 3. 获取用户要投票的帅哥ID,并+1 old = GENTIEMAN[message]['count'] new = old + 1 GENTIEMAN[message]['count'] = new data = {'user_id': message,'count': new,'type':'vote'} # 4. 给所有客户端推送消息 for conn in WEBSOCKET_DICT.values(): conn.send(json.dumps(data)) return 'close' @app.route('/notify') def notify(): data = {'data': "你的订单已经生成,请及时处理;",'type': 'alert'} print(WEBSOCKET_DICT) for conn in WEBSOCKET_DICT.values(): conn.send(json.dumps(data)) return '发送成功' if __name__ == '__main__': http_server = WSGIServer(('192.168.11.143',5000),app,handler_class=WebSocketHandler) http_server.serve_forever() login.html
login.html index.html
投票系统:参与投票的人
{% for k,v in users.items() %} {% endfor %} var socket = new WebSocket("ws://192.168.11.143:5000/message"); socket.onmessage = function (event) { /* 服务器端向客户端发送数据时,自动执行 */ var response = JSON.parse(event.data); // {'user':1,'count':new} if(response.type == 'vote'){ var nid = '#user_' + response.user_id; $(nid).find('span').text(response.count) }else{ alert(response.data); } }; /* 我要给某人投票 */ function vote(id) { socket.send(id); } 2、Django应用:channel 3、Tornado应用:自己有 (编辑:李大同) 【声明】本站内容均来自网络,其相关言论仅代表作者个人观点,不代表本站立场。若无意侵犯到您的权利,请及时与联系站长删除相关内容! |