欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页

websocket知识汇总以及websocket在Django中的实现

程序员文章站 2022-05-21 15:07:10
...

        最近在完成项目中需要用到实时技术,项目需求是将后端的一个文件内容实时读取然后发送到前端.这里主要涉及到两个技术.一个是后端如何实时读取一直在更新中的数据,另一点是如何保证web前后端的通讯,能将读取到的数据实时传送给前端.

        由于主要是进行后端开发,前端涉及的少,趁这个机会刚好学习了一下前端的一些知识.

一.Ajax轮询

        最开始解决实时通讯Google后使用了ajax的轮询技术,如果说从要求上来看基本满足要求,代码也十分简单,核心代码段如下:

                var getting = {
                    url:"{% url '...' %}",
                    type:'GET',
                    dataType: 'text',
                    success: function(data) {
                        $("#output").val(data);
                        //sleep(1000);
                        $.ajax(getting)
                        }
                    }
                };
                $.ajax(getting);

        也就是ajax异步请求成功后再次调用自己发送get请求,这样可以满足"实时"获取到后端一直在更新的文件的最新内容.但是,有个很严重的问题就是前端一直在get请求,导致极大的占用带宽,占用服务器的处理资源.后来加入修改,将每次发送的请求间隔1s,发现仍然是十分浪费带宽资源.

        后来发现有ajax长轮询技术,就是在每次发送请求后,如果后台有数据,则将数据返回,前端拿到后继续发送请求,如果后台没有数据,就不会response.可以很好的优化ajax的轮询缺陷,但是感觉该方法还是不够好.

        传统的request必须有一个response,对于实时通讯,这样是很差劲的体验.服务器端在建立连接后更新到新数据时无法主动给前端发送response,不够理想.

二.弃用ajax轮询,选择websocket

        后来Google到一个稍微新一点的技术,websocket,刚开始以为是socket的孪生表弟,后来才发现它俩关系跟java和JavaScript一样.有一篇文章讲的很好,入门了解一看便知,websocket基础

        websocket相比较传统http的优势很明显,借阮老师的一张图来看:

                          websocket知识汇总以及websocket在Django中的实现

一张图就明白了它的优势有多大.接下来就是开干

        先看看前端主要部分代码:

            var socket = new WebSocket("ws://" + window.location.host + "{% url '...' %}");
            window.s = socket;

            window.s.onopen = function () {
                ...
                console.log('websocket conneted!')
            };

            window.s.onmessage = function (event) {
                    ...
                }
            };
            window.s.onclose = function(){
                ...
            }
        这样,你就创建了一个websocket连接,浏览器是否支持需要你写个判断,我用的Chrome,直接支持websocket.其他具体说明请问度娘或谷大哥.

        后台也需要接受,由于我用的是Django,后来了解到Django channels也可以实现实时通讯,同时也发现了一个dwebsocket第三方库也是可以实现实时通讯,其实都是用了websocket技术而已...看你个人喜好,我使用了dwebsocket,因为Django channels安装的不少,还需要加到INSTALLED_APPS中,我只是需要用到websocket技术而已,dwebsocket相比较之下非常轻便,直接pip install dwebsocket就可以用,符合人生苦短,我用python的编程思想.其实我是懒

三.使用dwebsocket     

    一.使用方法   

        使用上很方便,如果为一个单独的视图函数处理一个websocklet连接可以使用accept_websocket装饰器,它会将标准的HTTP请求路由到视图中。使用require_websocke装饰器只允许使用WebSocket连接,会拒绝正常的HTTP请求。
        在设置中添加设置MIDDLEWARE_CLASSES=dwebsocket.middleware.WebSocketMiddleware这样会拒绝单独的视图实用websocket,必须加上accept_websocket 装饰器。设置WEBSOCKET_ACCEPT_ALL=True可以允许每一个单独的视图实用websockets.....当然,在settings中这样做完全没必要,因为我就是一个视图函数来处理请求的,无需复杂化.直接在视图函数处引入提供的装饰器accept_websocket即可.

    二.一些属性和方法

        1.request.is_websocket()
        如果是个websocket请求返回True,如果是个普通的http请求返回False,可以用这个方法区分它们。

        2.request.websocket
        在一个websocket请求建立之后,这个请求将会有一个websocket属性,用来给客户端提供一个简单的api通讯,如果request.is_websocket()是False,这个属性将是None。

        3.WebSocket.wait()
        返回一个客户端发送的信息,在客户端关闭连接之前他不会返回任何值,这种情况下,方法将返回None

        4.WebSocket.read()
         如果没有从客户端接收到新的消息,read方法会返回一个新的消息,如果没有,就不返回。这是一个替代wait的非阻塞方法

        5.WebSocket.count_messages()
         返回消息队列数量

        6.WebSocket.has_messages()
         如果有新消息返回True,否则返回False

        7.WebSocket.send(message)
         向客户端发送消息

        8.WebSocket.__iter__()
         websocket迭代器

必须要学习了解才知道我们需要用到什么.当然了,客户端的也需要了解一下:

                                     websocket知识汇总以及websocket在Django中的实现

        这是客户端的一些说明,在客户端,websocket的两个属性:readyState和bufferedAmount,区别和说明如下:

                根据readyState属性可以判断webSocket的连接状态,该属性的值可以是下面几种:
                0 :对应常量CONNECTING (numeric value 0),
                 正在建立连接连接,还没有完成。The connection has not yet been established.
                 1 :对应常量OPEN (numeric value 1),
                 连接成功建立,可以进行通信。The WebSocket connection is established and communication is possible.
                2 :对应常量CLOSING (numeric value 2)
                 连接正在进行关闭握手,即将关闭。The connection is going through the closing handshake.
                3 : 对应常量CLOSED (numeric value 3)
                 连接已经关闭或者根本没有建立。The connection has been closed or could not be opened.

            根据bufferedAmount可以知道有多少字节的数据等待发送,若websocket已经调用了close方法则该属性将一直增长。

        好了,其实也就这点内容,接下来就开始逻辑实现了,我在服务器端部分代码如下:

 @method_decorator(accept_websocket)
    def get(self, request):
        if request.is_websocket():
 
            for message in request.websocket:
                if ...:
                    ...
                else:
                    request.websocket.send(...)

        else:
            .....
            return HttpResponse('点个赞')

        这里我用到了method_decorator,这个可以忽略,因为我这个是视通函数,不是的话直接用@accept_websocket就行

        注意这里request.websocket.send发送的内容格式需要为bytes,而不是str..同时需要判断请求,用request.is_websocket()来进行判断.这里用for message in request.websocket比较好,前端发来的信息都会在里面,分析源码后感觉比wait()方法要好用,源码如下:

        while True:
            message = self.wait()
            yield message
            if message is None:
                break
        会处理多条message.具体后台的业务逻辑就可以根据需求添加了

四.websocket心跳包机制

        接下来还缺一个需求,就是如何判断前后端是否是连接状态,否则一方端口另一方会无法察觉,而继续发送,以至于报错.分析了一下这种长连接是哪方进行心跳包发送比较好,理论上来说都应该间隔性主动发送,但是,考虑到服务器端的带宽资源性问题,以及重要性问题,还是由客户端主动定期发送比较合适.同时发现,当客户端断开连接,刷新退出浏览器时,for message in request.websocket中的message会返回None,根据message返回的值来处理逻辑问题.那样,就剩下客户端需要加入心跳包了

        废话少说,主要代码如下:

    function keepalive(ws) {
        var time = new Date();
        if ((time.getTime() - last_health > 35000)) {
            //ws.close();
            //clearInterval(window.heartbeat_timer)
        } else {
            if (ws.bufferedAmount == 0 && ws.readyState == 1) {
                ws.send('ping')
            }
        }
    }

        time.getTime() - last_health > 35000表示现在时间与上次接受到服务器端数据时间相差大于35s,就ws.close()断开连接,或者重连或者实现其他业务逻辑都可...否则,判断ws.bufferedAmount == 0 && ws.readyState == 1,表示没有拔网线,即无阻塞,且是联通状态,发送心跳包,ping,服务器收到后会回复个pong,随个人喜好.在onopen中就需要加入间隔性发送的代码:

            window.s.onopen = function () {
                window.heartbeat_timer = setInterval(function () {keepalive(window.s)}, 30000);

        这里将心跳包间隔设置为30s,别忘了在onmessage中每次收到服务器消息都要刷新收到消息的时间.这样,就实现了客户端和服务器之间的心跳包重连.

五.后记

        对于如何用python进行文件的实时读取并发送前端有时间会记录下来.