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="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

 

posted @ 2019-01-10 16:31  一只待宰的程序猿  阅读(1137)  评论(1编辑  收藏  举报