• 轮询、websocket(重点)


    长轮询

    轮询:客户端定时向服务器发送Ajax请求,服务器接到请求后马上返回响应信息并关闭连接。 缺点:有延迟,浪费服务器资源。

    长轮询:客户端向服务器发送Ajax请求,服务器接到请求后夯住连接,直到有新消息才返回响应信息并关闭连接,客户端处理完响应信息后再向服务器发送新的请求。

    首先需要为每个用户维护一个队列,用户浏览器会通过js递归向后端自己的队列获取数据,自己队列没有数据,会将请求夯住(去队列中获取数据),夯一段时间之后再返回。
    注意:一旦有数据立即获取,获取到数据之后会再发送请求。
    

    优点: 在无消息的情况下不会频繁的请求,耗费资源小。 WebQQ

    缺点:服务器夯住连接会消耗资源,返回数据顺序无保证,难于管理维护。

    基于长轮询简单实现聊天:

    views.py

    from django.shortcuts import render,HttpResponse
    from django.http import JsonResponse
    import queue
    
    QUEUE_DICT = {}
    
    def index(request):
        username = request.GET.get('username')
        if not username:
            return HttpResponse('请输入名字')
        QUEUE_DICT[username] = queue.Queue()	# 为每个请求用户开一个队列
        return render(request,'index.html',{'username':username})
    
    def send_msg(request):
        """
        接受用户发来的消息
        :param request:
        :return:
        """
        text = request.POST.get('text')
        for k,v in QUEUE_DICT.items():
            v.put(text)
        return HttpResponse('ok')
    
    def get_msg(request):
        """
        想要来获取消息
        :param request:
        :return:
        """
        ret = {'status':True,'data':None}
    
        username = request.GET.get('user')
        user_queue = QUEUE_DICT.get(username)
    
        try:
            message = user_queue.get(timeout=10)
            ret['data'] = message
        except queue.Empty:
            ret['status'] = False
        return JsonResponse(ret)
    

    index.html

    <!DOCTYPE html>
    <html lang="en">
    <head>
        <meta charset="UTF-8">
        <title>Title</title>
    </head>
    <body>
        <h1>聊天室({{ username }})</h1>
        <div class="form">
            <input id="txt" type="text" placeholder="请输入文字">
            <input id="btn" type="button" value="发送">
        </div>
        <div id="content">
    
        </div>
    
        <script src="https://cdn.bootcss.com/jquery/3.4.1/jquery.min.js"></script>
        <script>
    
            $(function () {
                $('#btn').click(function () {
                    var text = $("#txt").val();
                    $.ajax({
                        url:'/send/msg/',
                        type:'POST',
                        data: {text:text},
                        success:function (arg) {
                            console.log(arg);
                        }
                    })
                 });
    
                getMessage();
            });
    
            function getMessage() {
                $.ajax({
                    url:'/get/msg/',
                    type:'GET',
                    data:{user:"{{ username }}" },
                    dataType:"JSON",
                    success:function (info) {
                        console.log(info);
                        if(info.status){
                            var tag = document.createElement('div');
                            tag.innerHTML = info.data;
                            $('#content').append(tag);
                        }
                        getMessage();
                    }
                })
            }
        </script>
    </body>
    </html>
    

    websocket

    基于http的一个协议。是用http协议规定传递。协议规定了浏览器和服务端创建连接之后,不断开,保持连接。相互之间可以基于连接进行主动的收发消息。

    原理:

    ​ 关键字:协议,ws,魔法字符串magic_string,payload, mask

    ​ magic_string = '258EAFAA5-E914-47DA-95CA-C5AB0DC85B11’ 全球唯一的魔法字符串。

    1. websocket握手环节:

      - 客户端向服务端发送随机字符串,在http的请求头 Sec-WebSocket-Key 中;
      - 服务端接受到到随机字符串,将这个字符串与魔法字符串拼接,然后进行sha1、base64加密;放在响应头Sec-WebSocket-Accept中,返回给浏览器;
      - 浏览器进行校验,校验不通过,说明服务端不支持websocket协议;
      - 校验成功,会建立连接,服务端与浏览器能够进行收发消息,传输的数据都是加密的。
      
    2. 数据解密:

      - 获取第二个字节的后7位,称为payload_len
      - 判断payload_len的值:
      	=127 : 2字节 + 8字节 + 4字节masking key + 数据
      	=126 : 2字节 + 2字节 + 4字节masking key + 数据
      	<=125: 2字节 + 4字节masking key +数据
      描述:	
      	127:在8个字节后时数据部分
      	126:在2个字节后时数据部分
      	<=125:后面就是数据部分
      	数据部分的前4个字节是 masking key 掩码,后面的数据会与其进行按位与运算进行数据的解密。
      

    手动创建支持websocket的服务端

    • 服务端

      import socket
      import hashlib
      import base64
      
      
      def get_headers(data):
          """
          将请求头格式化成字典
          :param data:
          :return:
          """
          header_dict = {}
          data = str(data, encoding='utf-8')
          header, body = data.split('
      
      ', 1)
          header_list = header.split('
      ')
          for i in range(0, len(header_list)):
              if i == 0:
                  if len(header_list[i].split(' ')) == 3:
                      header_dict['method'], header_dict['url'], header_dict['protocol'] = header_list[i].split(' ')
              else:
                  k, v = header_list[i].split(':', 1)
                  header_dict[k] = v.strip()
          return header_dict
      
      
      def get_data(info):
          """
          进行数据的解密
          :param data:
          :return:
          """
          payload_len = info[1] & 127
          if payload_len == 126:
              extend_payload_len = info[2:4]
              mask = info[4:8]
              decoded = info[8:]
          elif payload_len == 127:
              extend_payload_len = info[2:10]
              mask = info[10:14]
              decoded = info[14:]
          else:
              extend_payload_len = None
              mask = info[2:6]
              decoded = info[6:]
      
          bytes_list = bytearray()
          for i in range(len(decoded)):
              chunk = decoded[i] ^ mask[i % 4]
              bytes_list.append(chunk)
          body = str(bytes_list, encoding='utf-8')
          return body
      
      
      # 创建socket
      sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
      sock.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
      sock.bind(('127.0.0.1', 8002))
      sock.listen(5)
      
      
      # 等待用户连接
      conn, address = sock.accept()
      # 握手环节
      header_dict = get_headers(conn.recv(1024))
      magic_string = '258EAFA5-E914-47DA-95CA-C5AB0DC85B11'	# 魔法字符串
      random_string = header_dict['Sec-WebSocket-Key']	# 获取随机字符串
      value = random_string + magic_string
      ac = base64.b64encode(hashlib.sha1(value.encode('utf-8')).digest())	# bytes类型
      
      response = "HTTP/1.1 101 Switching Protocols
      " 
            "Upgrade:websocket
      " 
            "Connection: Upgrade
      " 
            "Sec-WebSocket-Accept: %s
      " 
            "WebSocket-Location: ws://127.0.0.1:8002
      
      "		# ws开头
      
      response = response %ac.decode('utf-8')
      # print(response)
      conn.send(response.encode('utf-8'))
      
      # 接受数据
      while True:
          data = conn.recv(1024)
          msg = get_data(data)	# 进行数据解密
          print(msg)
      
      
    • html

      <!DOCTYPE html>
      <html lang="en">
      <head>
          <meta charset="UTF-8">
          <title>Title</title>
      </head>
      <body>
          <input type="button" value="开始" onclick="startConnect();">
      
      <script>
          var ws = null;
          function startConnect() {
              // 1. 内部会先发送随机字符串
              // 2. 内部会校验加密字符串
              ws = new WebSocket('ws://127.0.0.1:8002')
          }
      </script>
      </body>
      </html>
      

    Django实现websocket

    django和flask框架,内部基于wsgi做的socket,默认都不支持websocket协议,只支持http协议。

    • flask中应用:

      pip3 install gevent-websocket 
      
    • django中应用:

      pip3 install channels
      

      在django中使用,是将 wsgi(wsgiref) 替换成 asgi(daphne) ,asgi支持 http和 websocket 协议。channel layer 可以实现多个人发送消息。

    单对单实现通信

    setting.py配置

    INSTALLED_APPS = [
        'django.contrib.admin',
        'django.contrib.auth',
        'django.contrib.contenttypes',
        'django.contrib.sessions',
        'django.contrib.messages',
        'django.contrib.staticfiles',
        'channels',
    ]
    
    ASGI_APPLICATION = "django_channels_demo.routing.application"	# 添加ASGI_APPLICATION支持websocket
    

    urls.py路由:

    from django.conf.urls import url
    from django.contrib import admin
    from app01 import views
    urlpatterns = [
        url(r'^admin/', admin.site.urls),
        url(r'^index/', views.index),
    ]
    

    routing.py路由:

    from channels.routing import ProtocolTypeRouter, URLRouter
    from django.conf.urls import url
    from app01 import consumers
    
    application = ProtocolTypeRouter({
        'websocket': URLRouter([
            url(r'^chat/$', consumers.ChatConsumer),
        ])
    })
    

    consumers.py 应用

    from channels.exceptions import StopConsumer
    from channels.generic.websocket import WebsocketConsumer
    
    
    class ChatConsumer(WebsocketConsumer):
        def websocket_connect(self, message):
            """ websocket连接到来时,自动执行 """
            print('有人来了')	# 可以在连接之前,做一些操作
            self.accept()	# 连接成功
    
        def websocket_receive(self, message):
            """ websocket浏览器给发消息时,自动触发此方法 """
            print('接收到消息', message)
    
            self.send(text_data='收到了')	# 发送数据。内部会进行数据的加密
    
            # self.close()	# 可自动关闭
    
        def websocket_disconnect(self, message):
            print('客户端主动断开连接了')
            raise StopConsumer()
    

    index.html

    <!DOCTYPE html>
    <html lang="en">
    <head>
        <meta charset="UTF-8">
        <title>Title</title>
    </head>
    <body>
        <h1>Web聊天室:<span id="tips"></span></h1>
        <div class="form">
            <input id="txt" type="text" placeholder="请输入文字">
            <input id="btn" type="button" value="发送" onclick="sendMessage();">
        </div>
        <div id="content">
    
        </div>
    
        <script src="https://cdn.bootcss.com/jquery/3.4.1/jquery.min.js"></script>
        <script>
            var ws;
    
            $(function () {
                initWebSocket();
            });
    
            function initWebSocket() {
                ws = new WebSocket("ws://127.0.0.1:8000/chat/");
                ws.onopen = function(){
                    $('#tips').text('连接成功');
                };
                ws.onmessage = function (arg) {
                    var tag = document.createElement('div');
                    tag.innerHTML = arg.data; //接收返回的数据
                    $('#content').append(tag);
                };
                ws.onclose = function () {
                    ws.close();
                }
            }
    		// 发送数据
            function sendMessage() {
                ws.send($('#txt').val());
            }
        </script>
    </body>
    </html>
    

    多人实现通信 -- channel_layer

    基于内存的channel_layer。

    配置channel_layer

    CHANNEL_LAYERS = {
        "default": {
            "BACKEND": "channels.layers.InMemoryChannelLayer",
        }
    }
    

    consumers.py 逻辑

    方式一:

    from channels.exceptions import StopConsumer
    from channels.generic.websocket import WebsocketConsumer
    from asgiref.sync import async_to_sync
    
    
    class ChatConsumer(WebsocketConsumer):
        def websocket_connect(self, message):
            """ websocket连接到来时,自动执行 """
            print('有人来了')
            
            # 将过来连接的self.channel_layer加入group_add到'222'的组中,channel_name是一个随机字符串,相当于用户
            async_to_sync(self.channel_layer.group_add)('222', self.channel_name)
    
            self.accept()
    
        def websocket_receive(self, message):
            """ websocket浏览器给发消息时,自动触发此方法 """
            print('接收到消息', message)
    
            async_to_sync(self.channel_layer.group_send)('222', {
                'type': 'xxx.ooo',
                'message': message['text']
            })
    		# type定义回调函数,发送消息
            
        def xxx_ooo(self, event):
            message = event['message']
            self.send(message)	
    
        def websocket_disconnect(self, message):
            """ 断开连接 """
            print('客户端主动断开连接了')
            async_to_sync(self.channel_layer.group_discard)('222', self.channel_name)
            raise StopConsumer()
    

    方式二:

    from channels.exceptions import StopConsumer
    from channels.generic.websocket import WebsocketConsumer
    from asgiref.sync import async_to_sync
    
    class ChatConsumer(WebsocketConsumer):
        def connect(self):
            print('有人来了')
            async_to_sync(self.channel_layer.group_add)('22922192', self.channel_name)
            self.accept()
    
        def receive(self, text_data=None, bytes_data=None):
            print('接收到消息', text_data)
    
            async_to_sync(self.channel_layer.group_send)('22922192', {
                'type': 'xxx.ooo',
                'message': text_data
            })
            # type定义回调函数,发送消息
        def xxx_ooo(self, event):
            # 发消息
            message = event['message']
            self.send(message)
    
        def disconnect(self, code):
            print('客户端主动断开连接了')
            async_to_sync(self.channel_layer.group_discard)('22922192', self.channel_name)
    
    

    上面两个方式本质上是一样的,第二种较简单。

    基于redis的 channel layer

    # 下载组件
    pip3 install channels-redis
    

    配置:

    CHANNEL_LAYERS = {
        "default": {
            "BACKEND": "channels_redis.core.RedisChannelLayer",
            "CONFIG": {
                "hosts": [('10.211.55.25', 6379)]
            },
        },
    }
    

    consumers.py 逻辑

    from channels.generic.websocket import WebsocketConsumer
    from asgiref.sync import async_to_sync
    
    
    class ChatConsumer(WebsocketConsumer):
    
        def connect(self):
            async_to_sync(self.channel_layer.group_add)('x1', self.channel_name)
            self.accept()
    
        def receive(self, text_data=None, bytes_data=None):
            async_to_sync(self.channel_layer.group_send)('x1', {
                'type': 'xxx.ooo',
                'message': text_data
            })
    
        def xxx_ooo(self, event):
            # 回调函数,真正的发送
            message = event['message']
            self.send(message)
    
        def disconnect(self, code):
            async_to_sync(self.channel_layer.group_discard)('x1', self.channel_name)
    

    session

    websocket 后端可以通过 self.scope获取请求的数据,全都放在scope中。如果需要保存session,必须save()

    self.scope['session']['键']		# 获取
    self.scope['session']['user'] = 'xxx'		# 设置session,默认不保存
    self.scope['session'].save()	# 保存
    

    routing.py路由:

    from channels.routing import ProtocolTypeRouter, URLRouter
    from django.conf.urls import url
    from channels.sessions import CookieMiddleware,SessionMiddlewareStack
    from apps.web import consumers
    
    
    application = ProtocolTypeRouter({
        'websocket': SessionMiddlewareStack(URLRouter([
            # 支持session
            url(r'^deploy/(?P<task_id>d+)/$', consumers.DeployConsumer),
        ]))
    })
    
  • 相关阅读:
    【环境巡检】使用jmeter+ant+Jenkins+企业微信自动化巡检_测试报告中有接口失败才通知(4)
    【环境巡检】使用jmeter+ant+Jenkins+企业微信自动化巡检_集成Jenkins定时发送企业微信+邮件(3)
    【环境巡检】使用jmeter+ant+Jenkins+企业微信自动化巡检_ant调用jmeter脚本并生成报告(2)
    【CI/CD】Jenkins查询及自定义工作空间(自由风格项目、maven项目)
    【环境巡检】使用jmeter+ant+Jenkins+企业微信自动化巡检_jmeter实现脚本及响应超时、失败重试(1)
    day 18 面向对象-类与类之间的关系
    day 17 面向对象-成员
    day16 面向对象
    day14 内置函数2
    day013 内置函数
  • 原文地址:https://www.cnblogs.com/yzm1017/p/13570794.html
Copyright © 2020-2023  润新知