轮询,长轮询,Websocket

轮询

原理: 利用Ajax定时朝后端发送请求,比如每隔五秒钟发一次请求,那么你的数据延迟就可能会高达五秒

特点: 数据延迟,消耗资源过大,请求次数太多

长轮询

原理: 利用Ajax + 队列  定时朝后端发送请求, 如果没有数据则会阻塞但是不会一直阻塞, 比如阻塞你30秒,还没有数据则返回,然后让客户端浏览器再次发送请求数据的请求

特点: 相对于轮询基本是没有消息延迟的,请求次数降低了很多

Websocket

WebSocket 的最大特点就是,浏览器与服务端建立链接之后默认不再断开,服务器可以主动向客户端推送信息,客户端也可以主动向服务器发送信息

原理

  • 握手环节:主要验证服务端是否支持websocket通信

第一次访问服务端的时候(基于http协议) 浏览器会自动生成一个随机字符串放在请求头中带给服务端然后自己也保留一份

Sec-WebSocket-Key: ePW8kp1XqLNWbJxE/Q38SA==

服务端和客户端都对随机字符串做下面的操作:

1.随机字符串 + magic string拼接

2.然后再将拼接好的结果进行加密处理(sha1/base64)的到密文

服务端将产生的密文通过响应头再次发送给客户端浏览器进行对比,假设比对上了 建立websocket链接 基于该链接收发消息

  • 收发数据

由于websocket是加密传输数据的,基于网络传输的数据都是二进制格式 对应到我们python中就是bytes类型

进行如下解密:

1.先读取第2个字节后7位(payload) 针对后七位的位数做不同的处理机制

=127:再往后读取8个字节

=126:再往后读取2个字节

<=125:不再往后读取

基于上述操作之后 所有的情况都继续往后读取4个字节(masking-key),基于该masking-key依据解密公式解密出真实数据

补充:

前端关键代码

<script>
	var ws = new WebSocket('ws://127.0.0.1:22/')
</script>
    // 上述代码做的事
    // 1.自动生产随机字符串并发送给服务端
    // 2.自动对随机字符串继续一系列操作 magic string  加密
    // 3.自动对比服务端加密字符串、

<!--通过ws对象点send方法即可实现websocket的数据交互-->

Django实现Websocket

不是所有的服务端都支持Websocket,

Django 默认不支持,只支持HTTP协议

Flask 默认不支持

Tronado 默认支持

channles

在django中如果想要基于websocket开发项目 你需要安装模块:channles

安装

pip3 install channels==2.3

注意:
版本不要使用最新的,如果安装最新的可能会自动把你的django版本升级到最新版
对应的解释器环境建议使用3.6

channels模块内部已经帮我们封装好了 握手/加密/解密的工作

使用

1.需要在配置文件settings.py中注册channles应用

INSTALLED_APPS = [
    ...
    # 1.需要先注册channels
    'channels'
]

2.还需要在配置文件settings.py中配置以下参数

ASGI_APPLICATION = 'channels_demo.routing.application'
# '与项目名同名的文件夹名.routing文件名.文件内的变量名application'
# 这里的channels_demo是项目名

3.在项目文件夹下创建routing.py文件,文件内写一下内容

from channels.routing import ProtocolTypeRouter,URLRouter

application = ProtocolTypeRouter({
    'websocket':URLRouter([
        # 路由与视图函数对应关系
        url(r'^chat/',consumers.ChatConsumer)  
        # consumers是处理Websocket请求的视图文件
        # ChatConsumer是视图类
    ])
})

上述配置完成后,启动django项目会发现

django由原来默认的wsgiref启动变成asgi启动

注意配置完成后,django就会即支持http协议也支持websocket协议

兼容http协议源码:

if "http" not in self.application_mapping:
  self.application_mapping["http"] = AsgiHandler

注意:

正常的http协议还是按照之前的写法 在urls中写路由与视图函数对应关系
而针对websocket协议则在当前文件内书写路由与视图函数对应关系

4.在视图文件consumer.py中书写websocket请求的逻辑

from channels.generic.websocket import WebsocketConsumer

class ChatConsumer(WebsocketConsumer):
    def websocket_connect(self, message):
        """
        客户端发来链接请求之后就会自动触发
        """
    def websocket_receive(self, message):
        """
        客户端向服务端发送消息就会自动触发
        """
    def websocket_disconnect(self, message):
        """
        客户端主动断开链接之后自动触发
        """

案例

基于channels来实现我们的多人聊天室

前端代码

  • index.html
<body>
<h1>聊天室</h1>
<div>
    <input type="text" id="d1" name="content">
    <input type="button" value="发送" onclick="sendMsg()">
    <input type="button" value="断开链接" onclick="closeLink()">
</div>

<h1>聊天纪录</h1>
<div id="content"></div>

<script>
    // 验证服务端是否支持websocket
    var ws = new WebSocket('ws://127.0.0.1:8000/chat/');

    // 3 链接成功之后自动触发
    ws.onopen = function () {
        alert('验证成功')
    };

    // 1 给服务端发送消息
    function sendMsg() {
        ws.send($('#d1').val())  // 将用户输入的内容发送给后端
    }
    // 2 一旦服务端有消息 会自动触发
    ws.onmessage = function (event) {  // event是数据对象 真正的数据在data属性内
        {#alert(event.data)  // 服务端返回的真实数据#}
        // 将消息渲染到html页面上
        var pEle = $('<p>');
        pEle.text(event.data);
        $('#content').append(pEle)

    };
    // 3 断开链接
    function closeLink() {
        ws.close()
    }

</script>
</body>

后端代码

  • urls.py
urlpatterns = [
    url(r'^admin/', admin.site.urls),
    # 默认只支持http协议
    url(r'^index/',views.index)
]
  • views.py
def index(request):
    return render(request,'index.html')
  • routing.py
application = ProtocolTypeRouter({
    'websocket':URLRouter([
        url(r'^chat/',consumers.ChatConsumer)
    ])
})
  • consumers.py
rom channels.generic.websocket import WebsocketConsumer
from channels.exceptions import StopConsumer


consumer_object_list = []

class ChatConsumer(WebsocketConsumer):
    def websocket_connect(self, message):
        """
        客户端发来链接请求之后就会自动触发
        :param message:
        :return:
        """
        # print('验证')
        self.accept()  # 向服务端发送加密字符串
        # self就是每一个客户端对象
        # 链接成功 我就将当前对象放入全局的列表中
        consumer_object_list.append(self)

    def websocket_receive(self, message):
        """
        客户端向服务端发送消息就会自动触发
        :param message:内部包含客户端给你发送的消息  {'type': 'websocket.receive', 'text': '大宝贝'}
        :return:
        """
        print(message)
        # 给客户端回消息
        # self.send(text_data=message.get('text'))

        # 给列表中所有的对象都发送消息
        for obj in consumer_object_list:
            obj.send(text_data=message.get('text'))


    def websocket_disconnect(self, message):
        """
        客户端主动断开链接之后自动触发
        :param message:
        :return:
        """
        print('断开链接了')
        # 服务端断开链接 就去列表中删除对应的客户端对象
        consumer_object_list.remove(self)
        raise StopConsumer

总结

前端四个方法

var ws = new WebSocket('ws://127.0.0.1:8000/chat/');
ws.onopen
ws.send()
ws.onmessage
ws.close()

后端三个方法

websocket_connect
websocket_receive
websocket_disconnect

案例:websocket + celery + redis发布订阅 实现服务端不断向web端推送数据

from asgiref.sync import async_to_sync
from channels.generic.websocket import WebsocketConsumer
class DomainEvent(WebsocketConsumer):
    # websocket连接时会执行  为了实现线程高可用,上生产应使用celery+supervisor
    def connect(self):
        # 这里会取到url中传入的report_id
        # self.report_name = self.scope['url_route']['kwargs']['report_id']
        # self.report_group_name = 'chat_%s' % self.report_name
        self.group_name = 'channel'

        # group_add 加入一个小组
        # channel_layer下面的方法属于通道层方法是异步的,因此使用async_to_sync进行包装
        async_to_sync(self.channel_layer.group_add)(
            self.group_name,
            self.channel_name
        )
        # accept表示建立连接,否则可以使用close
        self.accept()

    # 断开websocket连接时会执行
    def disconnect(self, close_code):
        # 断开与小组的连接 group_discard
        async_to_sync(self.channel_layer.group_discard)(
            self.group_name,
            self.channel_name
        )
        logger.info('websocket close success')

    # 接收到消息会执行
    def receive(self, text_data):
        # text_data 接收到的消息
        # recv = json.loads(text_data)
        logger.info(text_data)

        # group_send 向小组发送消息
        # self.report_group_name 指小组的名称
        # 'type': 'chat_message'  会调用chat_message发送消息
        async_to_sync(self.channel_layer.group_send)(
            self.group_name,
            {
                'type': 'push_message',
                'message': text_data
            }
        )

    # group_send被调用时,会执行指定选择的方法
    def push_message(self, event):
        message = event['message']
        # message['msg'] = message['msg'].decode('unicode_escape')
        self.send(text_data=json.dumps({
            'message': message
        }))

celery task:

@app.task(queue='default')
def usb_policy(devices, arg):
    public = RedisHelper()
    ......
    signal = {'event_type':'usb_policy'}
    public.public(json.dumps(signal), chan='policy')  # 向redis队列中发布消息

另起线程监控redis订阅队列,并将消息通过channel_layer发送web:

def WebSocketEvent(subscribes):
    def push(channel, msg):
        channel_layer = get_channel_layer()
        async_to_sync(channel_layer.group_send)(
            channel,
            {"type": "push_message", "message": msg}
        )

    redis_sub = subscribes.subscribe(chan='policy')
    for recv in redis_sub.listen():
        data = json.loads(recv.get('data').decode('utf-8'))
        push('channel', data)


if __name__ == '__main__':
    redis_helper = RedisHelper()

    websocket_channel_worker_p = Process(target=WebSocketEvent, args=(redis_helper,))
    websocket_channel_worker_p.daemon = True
    websocket_channel_worker_p.start()

    websocket_channel_worker_p.join()

socket与websocket区别

channels Layers

django + drf + channles

django + channles + 部署

Django channels+Celery实战

dwebsocket

 Django使用Channels实现WebSocket--上篇 - ops-coffee - 博客园

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
前端使用WebSocket可以实现轮询请求数据。WebSocket是一种在单个 TCP 连接上进行全双工通信的协议。相比于传统的轮询方式,WebSocket具有以下优点: 1. 实时性高:WebSocket建立起久的连接后,可以实时推送数据,避免了频繁发送请求的开销。 2. 减少网络负载:WebSocket的连接是双向的,可以同时进行数据的发送和接收,不需要每次请求都带上HTTP头。 3. 较低的延迟:WebSocket使用的是单个TCP连接,可以减少网络延迟,提升性能。 相比之下,轮询请求数据的方式存在一些缺点: 1. 服务端资源消耗较大:轮询方式中,服务端需要持续hold住客户端的请求,这会导致占用服务器资源。 2. 数据更新频繁时效率低:如果数据更新频繁,每次都需要创建和重建连接,这会带来较大的开销。 因此,前端使用WebSocket可以更好地实现轮询请求数据的需求,提高实时性和性能。<span class="em">1</span><span class="em">2</span><span class="em">3</span> #### 引用[.reference_title] - *1* [前端和后台进行WebSocket连接和axios轮询的方法(vue框架)](https://blog.csdn.net/weixin_43216105/article/details/89555480)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v93^chatsearchT3_1"}}] [.reference_item style="max-width: 50%"] - *2* *3* [数据实时更新解决方案(轮询以及WebSocket)](https://blog.csdn.net/qq_43456687/article/details/128133498)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v93^chatsearchT3_1"}}] [.reference_item style="max-width: 50%"] [ .reference_list ]
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值