方案一:定时ajax寻询,不推荐
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
</head>
<body>
<h1>投票系统</h1>
<script src="{{ url_for(\'static\',filename=\'jquery-3.3.1.min.js\')}}"></script>
<script>
function getMsg() {
$.ajax({
url:\'/message\',
success:function (data) {
console.log(data)
}
})
}
setInterval(getMsg,2000)
</script>
</body>
</html>
方案二:长轮训
把请求hold住n秒,使用消息队列queue,队列内有消息立刻给所有的链接返回数据,或者时间到了返回再次发送请求。
from flask import Flask,render_template,request,session,redirect,jsonify import uuid from queue import Queue,Empty app = Flask(__name__) app.secret_key = \'xfsdfqw\' USERS = { \'1\':{\'name\':\'小明\',\'count\':0}, \'2\':{\'name\':\'小红\',\'count\':0}, \'3\':{\'name\':\'小华\',\'count\':0}, } QUEUE_DICT = { } """ { 强哥:queue() 龙哥:queue() } """ @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\')} # 为每一个人创建一个队列 QUEUE_DICT[uid] = Queue() return redirect(\'/index\') @app.route(\'/index\') def index(): return render_template(\'index.html\',users=USERS) @app.route(\'/message\') def message(): result = {\'status\':True,\'msg\':None} # 取到用户的消息队列 queue = QUEUE_DICT[session.get(\'user_info\').get(\'id\')] try: # 获取数据,如果没有阻塞住,十秒后报错 v = queue.get(timeout=10) except Empty as e: # 如果没有数据返回None v = None result[\'msg\'] = v return jsonify(result) @app.route(\'/vote\') def vote(): userid = request.args.get(\'id\') old = USERS[userid][\'count\'] new = old + 1 USERS[userid][\'count\'] = new # 为所有的队列put数据,拿到数据请求立即返回 for k,v in QUEUE_DICT.items(): v.put({\'userid\':userid,\'count\':new}) return \'投票成功\' if __name__ == \'__main__\': app.run(threaded=True)
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>Title</title> </head> <body> <h1>投票系统</h1> <ul> {% for k,v in users.items() %} <li id="user_{{k}}" ondblclick="vote(\'{{k}}\')">{{v.name}} <span>{{v.count}}</span> </li> {% endfor %} </ul> <script src="{{ url_for(\'static\',filename=\'jquery-3.3.1.min.js\')}}"></script> <script> // 加载页面时初始化 发送请求 $(function () { getMsg(); }); // 请求数据,请求被阻塞住,有数据立即返回,或十秒后返回 function getMsg() { $.ajax({ url:\'/message\', success:function (data) { // 如果有数据找到标签展示 if(data.msg){ var nid = "#user_"+data.msg.userid; $(nid).find(\'span\').text(data.msg.count); } // 收到响应后立马再发一次请求 getMsg(); } }) } // 双击投票,发送ajax请求,后台票数增加,向所有队列push数据 function vote(id) { $.ajax({ url:\'/vote\', data:{\'id\':id}, success:function (data) { console.log(\'投票成功\'); } }) } </script> </body> </html>
方案三: websocket
from flask import Flask,render_template,request,session,redirect,jsonify import uuid from geventwebsocket.handler import WebSocketHandler from gevent.pywsgi import WSGIServer import json app = Flask(__name__) app.secret_key = \'xfsdfqw\' USERS = { \'1\':{\'name\':\'王旭\',\'count\':0}, \'2\':{\'name\':\'放景洪\',\'count\':0}, \'3\':{\'name\':\'六五\',\'count\':0}, } @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=USERS) WS_DICT = { } @app.route(\'/message\') def message(): # 判断是否是websocket请求 if request.environ.get(\'wsgi.websocket\'): # 得到链接 ws = request.environ[\'wsgi.websocket\'] # 1. 刚连接成功 uid = session.get(\'user_info\').get(\'id\') WS_DICT[uid] = ws from geventwebsocket.websocket import WebSocket while True: # 2. 等待用户发送消息,并接受 message = ws.receive() # 关闭:message=None if not message: del WS_DICT[uid] break old = USERS[message][\'count\'] new = old + 1 USERS[message][\'count\'] = new data = {\'user\':message,\'count\':new} # 向所有的链接发送消息 for k,v in WS_DICT.items(): # 3. 向客户端推送消息 v.send(json.dumps(data)) return "Connected!" if __name__ == \'__main__\': http_server = WSGIServer((\'127.0.0.1\', 5000), app, handler_class=WebSocketHandler) http_server.serve_forever()
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>Title</title> </head> <body> <h1>投票系统</h1> <a onclick="closeConn();">关闭连接</a> <a onclick="createConn();">创建连接</a> <ul> {% for k,v in users.items() %} <li id="user_{{k}}" ondblclick="vote(\'{{k}}\')">{{v.name}} <span>{{v.count}}</span> </li> {% endfor %} </ul> <script src="{{ url_for(\'static\',filename=\'jquery-3.3.1.min.js\')}}"></script> <script> var socket = null; function socketInit() { socket.onopen = function () { /* 与服务器端连接成功后,自动执行 */ }; socket.onmessage = function (event) { /* 服务器端向客户端发送数据时,自动执行 */ var response = JSON.parse(event.data); // {\'user\':1,\'count\':new} var nid = \'#user_\' + response.user; $(nid).find(\'span\').text(response.count) }; socket.onclose = function (event) { /* 服务器端主动断开连接时,自动执行 */ }; } /* 我要投票 id:帅哥id */ function vote(id) { socket.send(id); } function closeConn() { socket.close() } function createConn() { socket = new WebSocket("ws://127.0.0.1:5000/message"); socketInit(); } </script> </body> </html>
WebSocket 本质
WebSocket本质
a. 浏览器:发起连接
b. 服务器:接受到连接
c. 浏览器: 发送握手信息 Sec-WebSocket-Key: dCp5MdkY90EIJ83Qdddpjw==\r\n
d. 服务器:固定方式加密并返回:base64(sha1(dCp5MdkY90EIJ83Qdddpjw== + magic string))
e. 浏览器:接收值,并在内部进行校验。
校验成功:on_open
校验失败:验证失败
f. 相互之间进行收发消息-都是加密的
浏览器发送的消息到服务器:
1. 获取第二个字节的,前7位
2. 做位运算
7位 <= 125;+0;mask_key=4;数据
7位 == 126;+2;mask_key=4;数据
7位 == 127;+8;mask_key=4;数据
3. mask_key=4;数据
服务器向浏览器推送的消息:
1. 数据头+数据
import socket import base64 import hashlib import redis # conn = redis.Redis() # conn.blpop() 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() print(\'有用户来连接了\',conn,address) data = conn.recv(8096) headers = get_headers(data) # 提取请求头信息 print(\'用户发送过来的握手信息\',headers[\'Sec-WebSocket-Key\']) magic_string = \'258EAFA5-E914-47DA-95CA-C5AB0DC85B11\' value = headers[\'Sec-WebSocket-Key\'] + magic_string ac = base64.b64encode(hashlib.sha1(value.encode(\'utf-8\')).digest()) 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://%s%s\r\n\r\n" response_str = response_tpl % (ac.decode(\'utf-8\'), headers[\'Host\'], headers[\'url\']) conn.send(bytes(response_str, encoding=\'utf-8\')) while True: info = conn.recv(8096) # 1. 获取第2个字节 content[1] & 127 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) body = body + \' sb\' send_msg(conn,body.encode(\'utf-8\'))
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>Title</title> </head> <body> <script type="text/javascript"> /* 1. 创建socket 2. 发送【握手(验证)信息】 */ var socket = new WebSocket("ws://127.0.0.1:8002/xxoo"); socket.onopen = function () { /* 与服务器端连接成功后,自动执行 */ console.log(\'服务端加密规则正确,连接成功\'); } socket.onmessage = function (event) { /* 服务器端向客户端发送数据时,自动执行 */ var response = event.data; console.log(\'获取websocekt推送的消息:\',response) }; </script> </body> </html>