• geventwebsocket模块实例


    WebSocket with Flask

    HTML5 以前,HTML 还不支持 WebSocket ,当时如果要进行实时的内容更新,要么使用 Ajax轮询(Polling)或者使用 Comet 技术。

    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 操作,从而达到部分更新页面内容的目的(而不需要整个页面刷新)。

    Ajax 轮询 可以做到接近实时的更新内容,但是因为是由客户端发起请求,即服务器处于被动的状态,这种“实时”存在缺陷: (1) 伪实时。服务器有更新的时候,只有客户端发起请求,服务器才能将更新返回到客户端。 (2) 数据更新量少的时候,容易造成浪费带宽、流量。 (3) 请求频率难以把握。太快会对服务器造成过大的压力,而太慢又不够“实时”,权衡频率需要考虑的因素很多。

    Comet 技术

    Comet 是指不需要客户端浏览器安装任何插件,仅靠浏览器和服务器之间的长 HTTP 连接实现服务器向客户端通信(服务器推)的技术。 Comet 有两种方式: Ajax长轮询 和 iframe with htmlfile stream

    iframe with htmlfile streaming

    这种技术,暂时没使用过。基本原理是使用 iframe 标签在 html 中插入一个隐藏的帧,向服务器建立长连接,服务器不断地向 iframe 输入数据。

    Ajax 长轮询

    Ajax 长轮询本质上也是 Ajax 轮询,不同的是,在服务器端做了些修改。当服务器没有更新的时候,服务器将请求阻塞,直到 有更新 或 连接超时。当请求结束之后再进行第二次的请求。

    这种方式基本上可以避开 Ajax 轮询的缺陷。 Tornado 框架中的 Asynchronous 功能就是通过阻塞请求实现异步更新。 通过 Tornado 框架提供的 Asynchronous 功能可以实现实时数据传递。欢迎参考我在学习使用 tornado 异步功能时实现的两段应用:

    1. https://github.com/shonenada/chat-in-command-line
    2. https://github.com/shonenada/guess-number // 这程序功能不完善,但实现了异步的功能。

    WebSocket

    WebSocket 是 HTML5 的新功能,它是一种 TCP 协议。当客户端和服务器完成握手,建立连接之后,ws 就如普通 socket 一样,在两者之间进行通信。

    理解了基本通信原理,就可以进行编程了。

    前面已说,WS 是一种 TCP 协议,所以是语言无关的,用任何语言都可以实现服务器端的编程。我选择了 Python,使用 _flask: http://flask.pocoo.org/ 作为框架,以 _Gevent: http://www.gevent.org/ 和 _gevent-websocket:https://pypi.python.org/pypi/gevent-websocket/ 做 HttpServer。

    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="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
  • 相关阅读:
    luogu CF804D Expected diameter of a tree |Tarjan+樹的直徑+前綴和+二分查找
    luogu CF25C Roads in Berland |最短路floyd
    自觉日志·1·再现曾经的自觉
    祝福您新年快乐!
    自觉体验十一 体验祝福,与弘誓大愿!
    自觉体验 十 体验失忆
    自觉体验 六 手语与结印
    自觉体验 一  闲来无事,闭目养神
    我将离开屏幕前的“      ”
    第五章  自觉与【转载】《大念处经》
  • 原文地址:https://www.cnblogs.com/songhuasheng/p/10250798.html
Copyright © 2020-2023  润新知