1. 程式人生 > >輪詢、長輪詢和websocket

輪詢、長輪詢和websocket

取數 rec bre 自己 udp server 查看 持久性 div

一、輪詢

在一些需要進行實時查詢的場景下應用
比如投票系統:
  大家一起在一個頁面上投票
  在不刷新頁面的情況下,實時查看投票結果

1、後端代碼

from flask import Flask, render_template, request, jsonify


app = Flask(__name__)

USERS = {
    1: {name: 明凱, count: 300},
    2: {name: 廠長, count: 200},
    3: {name: 7醬, count: 600},
}


@app.route(
/) def index(): return render_template(Poll.html, users=USERS) @app.route(/vote, methods=[POST]) def vote(): # 接收uid,通過uid給打野票數 +1 # 用戶提交Json數據過來,用request.json獲取 uid = request.json.get(uid) USERS[uid][count] += 1 return "投票成功" @app.route(/get_vote
) def get_vote(): # 返回users數據 # jsonify 是flask自帶的序列化器 return jsonify(USERS) if __name__ == __main__: app.run()

2、前端代碼

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>投票系統</title>
    <link rel
="stylesheet" href="https://cdn.jsdelivr.net/npm/[email protected]/dist/css/bootstrap.min.css"> <script src="https://cdn.bootcss.com/axios/0.19.0-beta.1/axios.js"></script> </head> <style> .my-li { list-style: none; margin-bottom: 20px; font-size: 18px; } </style> <body> <div class="container"> <div class="row"> <div class="col-md-6 col-md-offset-3"> <h1>LPL第一打野投票</h1> {% for (uid, user) in users.items() %} <button class="btn btn-success" onclick="vote({{ uid }})">投票</button> <li class="list-group-item-info my-li" id="{{ uid }}">{{ user.name }}目前的票數是: {{ user.count }}</li> {% endfor %} </div> </div> </div> <script> // 投票 function vote(uid) { // 向後端發送投票請求 axios.request({ url: /vote, method: post, data: { uid: uid } }).then(function (response) { console.log(response.data); }) } // 獲取最新的投票結果 function get_vote() { axios.request({ url: /get_vote, method: get }).then(function (response) { // 獲取後端傳過來的新數據 // 重新渲染頁面 let users = response.data; for (let uid in users) { //根據uid獲取li標簽 改變innerText let liEle = document.getElementById(uid); liEle.innerText = `${users[uid][name]}目前的票數是: ${users[uid][count]}` } }); } // 頁面加載完後,立刻獲取數據 window.onload = function () { setInterval(get_vote, 2000) } </script> </body> </html>

3、輪詢

特點:每隔一段時間不斷向後端發送請求
缺點:消耗大 有延遲

二、長輪詢

由於上面的輪詢是不能實時查看到投票情況的,存在一定的延遲性
長輪詢可以實現實時查看投票情況

1、後端代碼

from flask import Flask, render_template, request, jsonify, session
import uuid
import queue


app = Flask(__name__)
app.secret_key = 切克鬧


USERS = {
    1: {name: 明凱, count: 300},
    2: {name: 廠長, count: 200},
    3: {name: 7醬, count: 600},
}


Q_DICT = {
    # uid: q對象
}


@app.route(/)
def index():
    # 模擬用戶登錄
    # 模擬用戶登錄後的唯一id
    user_id = str(uuid.uuid4())
    # 每個用戶都有自己的Q對象
    Q_DICT[user_id] = queue.Queue()
    # 把用戶的id存到session
    session[user_id] = user_id
    # 頁面展示投票的人的信息
    return render_template(longPoll.html, users=USERS)


@app.route(/vote, methods=[POST])
def vote():
    # 處理投票,給打野的票數 +1
    # 用戶提交Json數據過來,用request.json獲取
    uid = request.json.get(uid)
    USERS[uid][count] += 1
    # 投票成功後,給每個用戶的Q對象put最新的值進去
    for q in Q_DICT.values():
        q.put(USERS)
    return "投票成功"


@app.route(/get_vote)
def get_vote():
    # 請求進來,從session獲取用戶的id
    user_id = session.get(user_id)
    # 根據用戶的id 獲取用戶的Q對象
    q = Q_DICT.get(user_id)
    # try:
    #     ret = q.get(timeout=30)
    # except queue.Empty:
    #     ret = ‘‘
    ret = q.get()
    return jsonify(ret)


if __name__ == __main__:
    app.run()

2、前端代碼

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>投票系統</title>
    <link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/[email protected]/dist/css/bootstrap.min.css">
    <script src="https://cdn.bootcss.com/axios/0.19.0-beta.1/axios.js"></script>
</head>
<style>
    .my-li {
        list-style: none;
        margin-bottom: 20px;
        font-size: 18px;
    }
</style>
<body>
<div class="container">
    <div class="row">
        <div class="col-md-6 col-md-offset-3">
            <h1>LPL第一打野投票</h1>
            {% for (uid, user) in users.items() %}
                <button class="btn btn-success" onclick="vote({{ uid }})">投票</button>
                <li class="list-group-item-info my-li" id="{{ uid }}">{{ user.name }}目前的票數是: {{ user.count }}</li>
            {% endfor %}
        </div>
    </div>
</div>

<script>
    // 投票
    function vote(uid) {
        // 向後端發送投票請求
        axios.request({
            url: /vote,
            method: post,
            data: {
                uid: uid
            }
        }).then(function (response) {
            console.log(response.data);
        })
    }

    // 獲取最新的投票結果
    function get_vote() {
        axios.request({
            url: /get_vote,
            method: get
        }).then(function (response) {
            // 判斷後端的數據是否為空
            if (response.data != ‘‘) {
                // 獲取到最新的數據
                let users = response.data;
                for (uid in users) {
                    // 根據uid找到每個li標簽
                    let liEle = document.getElementById(uid);
                    // 給每個li標簽設置最新的數據
                    liEle.innerText = `${users[uid][name]}目前的票數是: ${users[uid][count]}`
                }
            }
            // 獲取完數據後,再發送請求,看還有沒有人投票,有的話再去獲取最新的數據
            get_vote()
        });
    }

    // 頁面加載完後,立刻獲取數據
    window.onload = function () {
        get_vote()
    }

</script>

</body>
</html>

3、長輪詢

特點:滿足實時更新
缺點:消耗大
實現:
  利用queue對象實現請求夯住
  每個請求進來都要生成一個q對象
  如果有人投票 給所有的q對象put數據
  拿數據請求從自己的q對象get數據

三、websocket

1、介紹

http協議
  短連接 無狀態 基於TCP/UDP協議進行傳輸數據(TCP/UDP: 傳輸協議)

socket
  socket不是傳輸協議 跟websocket是兩個完全不一樣的東西 socket是套接字 API接口

websocket
  H5出的新協議 瀏覽器和服務器只需要完成一次握手,兩者之間就直接可以創建持久性的連接,並進行雙向數據傳輸
  解決輪詢問題
  特點:
    1. 握手 基於HTTP進行握手(因此websocket與Http有一定的交集,但不是同一個東西)
    2. 發送數據加密
    3. 保持連接不斷開

2、在Flask中使用websocket

1. Flask沒有websocket,需要安裝包  pip install gevent-websocket


2. 後端怎樣建立一個支持websocket協議連接
from geventwebsocket.handler import WebSocketHandler
from gevent.pywsgi import WSGIServer

# 拿到websocket對象
ws = request.environ.get("wsgi.websocket")
# 後端發送數據
ws.send(xxx)
# 後端接收數據
ws.receive()

if __name__ == __main__:
    # app.run()
    # 即支持HTTP 也支持websocket
    http_server = WSGIServer((0.0.0.0, 5000), app, handler_class=WebSocketHandler)
    http_server.serve_forever()


3. 前端怎麽發起websocket連接
let ws = new WebSocket("ws://127.0.0.1:5000")
# 前端發送數據
ws.send("xxx")
# 前端接收數據
ws.onmessage = function(event){
    # 註意數據數據類型的轉換        
    let data = event.data
}


4. 收發消息
1, 前端發送數據給後端
    前端發送:ws.send(數據)
    後端接收:ws.receive()
    

2, 後端發送數據給前端
    後端發送:ws.send(數據)
    前端接收:ws.onmessage = function(event){
        let data = event.data
    }
    

3、Demo

1.後端
from flask import Flask, render_template, request
from geventwebsocket.handler import WebSocketHandler
from gevent.pywsgi import WSGIServer
import json


app = Flask(__name__)

USERS = {
    1: {name: 明凱, count: 300},
    2: {name: 廠長, count: 200},
    3: {name: 7醬, count: 600},
}

WEBSOCKET_LIST = []


@app.route(/)
def index():
    return render_template(websocket.html, users=USERS)


@app.route(/vote)
def vote():
    # 處理websocket
    # 判斷是什麽類型的請求,HTTP還是websocket
    # 看能否獲取得到websocket的對象
    ws = request.environ.get("wsgi.websocket")
    if not ws:
        return "這是HTTP協議的請求"
    # 把所有用戶的ws對象存到一個列表
    WEBSOCKET_LIST.append(ws)
    while True:
        # 獲取前端傳過來的uid,給打野票數 +1
        uid = ws.receive()
        # 如果前端主動斷開連接
        # 那麽後端也關閉與前端的連接
        if not uid:
            WEBSOCKET_LIST.remove(ws)
            ws.close()
            break
        uid = int(uid)
        USERS[uid]["count"] += 1
        data = {
            "uid": uid,
            "name": USERS[uid]["name"],
            "count": USERS[uid]["count"]
        }
        for ws in WEBSOCKET_LIST:
            # 給前端發送新的數據
            ws.send(json.dumps(data))


if __name__ == __main__:
    # app.run()
    # 這樣啟服務的意思是:即支持HTTP協議,也支持websocket協議
    http_server = WSGIServer((127.0.0.1, 5000), app, handler_class=WebSocketHandler)
    http_server.serve_forever()

2.前端
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>投票系統</title>
    <link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/[email protected]/dist/css/bootstrap.min.css">
    <script src="https://cdn.bootcss.com/axios/0.19.0-beta.1/axios.js"></script>
</head>
<style>
    .my-li {
        list-style: none;
        margin-bottom: 20px;
        font-size: 18px;
    }
</style>
<body>
<div class="container">
    <div class="row">
        <div class="col-md-6 col-md-offset-3">
            <h1>LPL第一打野投票</h1>
            {% for (uid, user) in users.items() %}
                <button class="btn btn-success" onclick="vote({{ uid }})">投票</button>
                <li class="list-group-item-info my-li" id="{{ uid }}">{{ user.name }}目前的票數是: {{ user.count }}</li>
            {% endfor %}
        </div>
    </div>
</div>

<script>
    // 向後端發送一個websocket連接請求
    let ws = new WebSocket(ws://127.0.0.1:5000/vote);
    function vote(uid) {
        // 向後端發數據
        ws.send(uid)
    }
    ws.onmessage = function (event) {
        let data = JSON.parse(event.data);
        let liEle = document.getElementById(data.uid);
        liEle.innerText = `${data.name}目前的票數是: ${data.count}`
    }
</script>

</body>
</html>

輪詢、長輪詢和websocket