geventwebsocket模塊實例

WebSocket with Flask

HTML5 之前,HTML 還不支持 WebSocket ,當時若是要進行實時的內容更新,要麼使用 Ajax輪詢(Polling)或者使用 Comet 技術。javascript

Non-Websocket

Ajax 輪詢

在 2005 年, Jesse James Garrett 提出 Ajax (Asynchronous JavaScript and XML, 異步 Javascript 和 XML)。具體請看Ajax: A New Approach to Web Applications 。而且從那時開始流行使用 Ajax 進行異步處理客戶端請求。【關於異步處理請求的歷史,能夠看 http://en.wikipedia.org/wiki/Ajax_(programming) 中相關的介紹】。 XMLHttpRequest 在後臺對服務器發起 request ,當收到 response 的時候,進行 DOM 操做,從而達到部分更新頁面內容的目的(而不須要整個頁面刷新)。html

Ajax 輪詢 能夠作到接近實時的更新內容,可是由於是由客戶端發起請求,即服務器處於被動的狀態,這種「實時」存在缺陷: (1) 僞實時。服務器有更新的時候,只有客戶端發起請求,服務器才能將更新返回到客戶端。 (2) 數據更新量少的時候,容易形成浪費帶寬、流量。 (3) 請求頻率難以把握。太快會對服務器形成過大的壓力,而太慢又不夠「實時」,權衡頻率須要考慮的因素不少。java

Comet 技術

Comet 是指不須要客戶端瀏覽器安裝任何插件,僅靠瀏覽器和服務器之間的長 HTTP 鏈接實現服務器向客戶端通訊(服務器推)的技術。 Comet 有兩種方式: Ajax長輪詢 和 iframe with htmlfile streampython

iframe with htmlfile streaming

這種技術,暫時沒使用過。基本原理是使用 iframe 標籤在 html 中插入一個隱藏的幀,向服務器創建長鏈接,服務器不斷地向 iframe 輸入數據。git

Ajax 長輪詢

Ajax 長輪詢本質上也是 Ajax 輪詢,不一樣的是,在服務器端作了些修改。當服務器沒有更新的時候,服務器將請求阻塞,直到 有更新 或 鏈接超時。當請求結束以後再進行第二次的請求。github

這種方式基本上能夠避開 Ajax 輪詢的缺陷。 Tornado 框架中的 Asynchronous 功能就是經過阻塞請求實現異步更新。 經過 Tornado 框架提供的 Asynchronous 功能能夠實現實時數據傳遞。歡迎參考我在學習使用 tornado 異步功能時實現的兩段應用:web

  1. https://github.com/shonenada/chat-in-command-line
  2. https://github.com/shonenada/guess-number // 這程序功能不完善,但實現了異步的功能。

WebSocket

WebSocket 是 HTML5 的新功能,它是一種 TCP 協議。當客戶端和服務器完成握手,創建鏈接以後,ws 就如普通 socket 同樣,在二者之間進行通訊。ajax

理解了基本通訊原理,就能夠進行編程了。編程

前面已說,WS 是一種 TCP 協議,因此是語言無關的,用任何語言均可以實現服務器端的編程。我選擇了 Python,使用 _flask: http://flask.pocoo.org/ 做爲框架,以 _Gevent: http://www.gevent.org/ 和 _gevent-websocket:https://pypi.python.org/pypi/gevent-websocket/ 作 HttpServer。json

geventwebsocket安裝方法:

pip3 install geventwebsocket

實時更新基本的實現思路:

  1. 客戶端發起 ws 鏈接請求
  2. 服務器響應,而且把 ws 加入到 observer 數組中。
  3. 當某一 ws 向服務器發送信息時,服務器遍歷 observers 數組向每個元素髮送信息。
  4. ws 斷開鏈接時,從 observer 中剔除。

具體實現代碼:

from flask import Flask,render_template,request
from geventwebsocket.handler import WebSocketHandler
from geventwebsocket.websocket import WebSocket
from gevent.pywsgi import WSGIServer
import json

app=Flask(__name__)

user_socket_list = []

user_socket_dict = {

}

@app.route("/ws/<username>")
def ws(username):
    user_socket = request.environ.get("wsgi.websocket")#type:WebSocket
    if user_socket:
        user_socket_dict[username] = user_socket
    print(len(user_socket_dict),user_socket_dict)
    while 1:
        msg = user_socket.receive()
        msg_dict = json.load(msg)
        msg_dict["from_user"] = username
        to_user = msg_dict.get("to_user")
        # chat= msg_dict.get("msg")
        u_socket = user_socket_dict.get(to_user) #type:WebSocket
        u_socket.send(json.dumps(msg_dict))

        # for u_socket in user_socket_list:
        #     if u_socket.send(json.dumps(msg_dict))
        #
        #     for u_socket in user_socket_list:
        #         if u_socket == user_socket:
        #             continue
        #         try:
        #             u_socket.send(msg)
        #         except:
        #             continue

@app.route("/")
def index():
    return render_template("ws.html")

if __name__ == '__main__':
    app.run("0.0.0.0",9527,debug=True)
    http_serv = WSGIServer(("0.0.0.0",9527),app,handle_class=WebSocketHandler)
    http_serv.serve_forever()
後端實現
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
    <meta name="viewport" content="width=device-width,initial-scale=1">
</head>
<body>
<input type="text" id="username"> <button onclick="login()">登陸聊天室</button><input type="text" id="to_user">發送:<input type="text" id="msg"> <button onclick="send_msg()">發送</button>
<div id="chat_list" style="width:500px;height: 500px;"></div>

</body>
<script type="application/javascript">
    var ws = null;
    function login() {
        var username = document.getElementById("username").value;
        ws = new WebSocket("ws://192.168.13.209:9527/ws/"+username);
        ws.onmessage = function (data) {
            console.log(data.data);
            var recv_msg = JSON.parse(data.data);
            var ptag = document.createElement("p");
            ptag.innerText = recv_msg.from_user + ":" + recv_msg.msg;
            document.getElementById("chat_list").appendChild(ptag);
        }
    }

    function send_msg() {
        var to_user = document.getElementById("to_user").value;
        var msg = document.getElementById("msg").value;
        var send_str = {
            "to_user":to_user,
            "msg":msg
        };
        ws.send(JSON.stringify(send_str));
    }
</script>
</html>
templates/ws.html
相關文章
相關標籤/搜索