websocket知识汇总以及websocket在Django中的实现(转载)

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

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

一.Ajax轮询

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

  1. var getting = {
  2. url:"{% url '...' %}",
  3. type:'GET',
  4. dataType: 'text',
  5. success: function(data) {
  6. $("#output").val(data);
  7.                         //sleep(1000);
  8. $.ajax(getting)
  9. }
  10. }
  11. };
  12. $.ajax(getting);

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

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

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

二.弃用ajax轮询,选择websocket

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

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

                          

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

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

  1. var socket = new WebSocket("ws://" + window.location.host + "{% url '...' %}");
  2. window.s = socket;
  3. window.s.onopen = function () {
  4.                 ...
  5. console.log('websocket conneted!')
  6. };
  7. window.s.onmessage = function (event) {
  8.                     ...
  9. }
  10. };
  11.             window.s.onclose = function(){
  12.                 ...
  13.             }
        这样,你就创建了一个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的两个属性: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方法则该属性将一直增长。

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

  1. @method_decorator(accept_websocket)
  2. def get(self, request):
  3. if request.is_websocket():
  4. for message in request.websocket:
  5. if ...:
  6.                     ...
  7. else:
  8. request.websocket.send(...)
  9. else:
  10.             .....
  11. return HttpResponse('点个赞')

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

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

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

四.websocket心跳包机制

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

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

  1. function keepalive(ws) {
  2. var time = new Date();
  3. if ((time.getTime() - last_health > 35000)) {
  4. //ws.close();
  5. //clearInterval(window.heartbeat_timer)
  6. } else {
  7. if (ws.bufferedAmount == 0 && ws.readyState == 1) {
  8. ws.send('ping')
  9. }
  10. }
  11. }

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

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

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

五.后记

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

        



















原文链接

posted @ 2020-04-15 10:59  import*  阅读(526)  评论(0编辑  收藏  举报