django-channels(websocket)

官文:

https://channels.readthedocs.io/en/latest/tutorial/part_3.html

安装:

pip install channels, channels-redis
uvicorn==0.22.0
channels==3.0.5
channels-redis==4.1.0

配置

# settings.py中添加

INSTALLED_APPS = [
    ...,
    'channels',
]
ASGI_APPLICATION = '项目名.asgi.application'





注:推荐使用版本大于django3.0(配置)
asgi.py中添加

import os
from channels.routing import ProtocolTypeRouter
from django.core.asgi import get_asgi_application

os.environ.setdefault('DJANGO_SETTINGS_MODULE', '项目名.settings')

application = ProtocolTypeRouter({
    "http": get_asgi_application(),
    # Just HTTP for now. (We can add other protocols later.)
})



django2.2配置(因为没asgi文件,自建一个asgi.py文件)

import os
import django
from channels.http import AsgiHandler
from channels.routing import ProtocolTypeRouter

os.environ.setdefault('DJANGO_SETTINGS_MODULE', '项目名.settings')
django.setup()

application = ProtocolTypeRouter({
  "http": AsgiHandler(),
  # Just HTTP for now. (We can add other protocols later.)
})

启动服务,展示下面日志便启动成功(ASGI)
在这里插入图片描述

此时已经配置成功

单向通讯(实操)

单向通讯不用redis即可实现

在自己项目下新建consumers.py

# front_end/consumer.py

import json
from channels.generic.websocket import WebsocketConsumer


class ChatConsumer(WebsocketConsumer):
    def connect(self):
        self.accept()

    def disconnect(self, close_code):
        pass

    def receive(self, text_data):
        text_data_json = json.loads(text_data)
        message = text_data_json['message']

        self.send(text_data=json.dumps({
            'message': message
        }))

在自己项目下新建routing.py

from django.urls import path
from front_end import consumers

websocket_urlpatterns = [
    path(r'ws/chat/', consumers.ChatConsumer.as_asgi()),
]

asgi.py中注册url

2.2版本
import os

import django
from channels.http import AsgiHandler
from channels.auth import AuthMiddlewareStack
from channels.routing import ProtocolTypeRouter, URLRouter
from channels.security.websocket import AllowedHostsOriginValidator

from front_end import routing

os.environ.setdefault('DJANGO_SETTINGS_MODULE', '项目名.settings')
django.setup()

application = ProtocolTypeRouter({
    "http": AsgiHandler(),
    # Just HTTP for now. (We can add other protocols later.)
    "websocket": AllowedHostsOriginValidator(    # 新增部分
            AuthMiddlewareStack(                         
                URLRouter(
                    routing.websocket_urlpatterns
                )
            )
        ),
})




3.0及以上
# 项目名/asgi.py
import os

from channels.auth import AuthMiddlewareStack
from channels.routing import ProtocolTypeRouter, URLRouter
from channels.security.websocket import AllowedHostsOriginValidator
from django.core.asgi import get_asgi_application

os.environ.setdefault("DJANGO_SETTINGS_MODULE", "项目名.settings")
django_asgi_app = get_asgi_application()

import chat.routing

application = ProtocolTypeRouter({
  "http": django_asgi_app,
  "websocket": AllowedHostsOriginValidator(
        AuthMiddlewareStack(
            URLRouter(
                routing.websocket_urlpatterns
            )
        )
    ),
})

websocket在线测试
http://www.websocket-test.com/
http://www.jsons.cn/websocket/【简洁】

ws://127.0.0.1:8000/ws/chat/
发送: {"message": "测试发送一下"}

双向通道(多人聊天配置)

settings.py中配置redis

CHANNEL_LAYERS = {
    'default': {
        'BACKEND': 'channels_redis.core.RedisChannelLayer',
        'CONFIG': {
          "hosts": [('127.0.0.1', 6379)], # 不带密码
          注: 不带密码在未关掉redis安全保护的情况不能访问公网redis
			(redis.conf注释掉bind 127.0.0.1, 找到 protected-mode yes 把 yes 改为 no)
		
           #  "hosts": ["redis://:密码@IP:端口/1"],  带密码配置
        },
    },
}

配置自定义时间后主动断开连接

在Django Channels中实现超时断开连接可以通过使用timeout参数和threading.Timer来实现。下面是一个示例:

from channels.generic.websocket import WebsocketConsumer
import threading

class MyConsumer(WebsocketConsumer):
    def connect(self):
        # 连接建立时设置超时
        self.timeout = 60  # 设置超时时间(以秒为单位)
        self.timer = threading.Timer(self.timeout, self.disconnect)
        self.timer.start()

        # 连接逻辑...

    def receive(self, text_data):
        # 收到消息时重置超时计时器
        self.timer.cancel()
        self.timer = threading.Timer(self.timeout, self.disconnect)
        self.timer.start()

        # 处理消息...

    def disconnect(self, code=None):
        # 断开连接逻辑...
        self.close(code)

在这个示例中,我们定义了一个MyConsumer类,它继承自WebsocketConsumer。在connect方法中,我们使用threading.Timer设置一个计时器,当超过指定时间后调用disconnect方法断开连接。

然后,在receive方法中,每当收到消息时,我们重置计时器,以保持连接的活跃状态。

最后,在disconnect方法中,我们断开连接的逻辑可以自行定义,比如通过调用self.close(code)来主动关闭连接。

请注意,这只是一个简单的示例,你可以根据你的具体需求和业务逻辑进行修改和扩展。

在多线程编程中,cancel 方法用于请求取消一个线程的执行。它是 Thread 类的一个方法,可以用来中断正在运行的线程,
或者阻止一个已经被请求中断的线程继续执行。

当调用 cancel 方法时,它会将线程的中断状态设置为 true。这通常会让线程在适当的时候退出自己的执行。

然而,需要注意的是 cancel 方法仅仅是请求线程取消,具体是否取消还取决于线程的具体实现。线程可以通过检查是否发生
了中断来自行决定如何处理中断请求。一些常见的线程实现,如使用循环或阻塞方法的线程,通常会检查中断状态并相应地停止执行。

在使用 cancel 方法时需要注意以下几点:
1. 调用 cancel 方法并不一定会导致线程立即停止执行,具体取决于线程的实现代码。
2. 在需要取消线程的情况下,通常需要在适当的地方检查线程的中断状态,并编写相应的代码逻辑来处理中断请求。
3. 对于一些长时间运行的任务,可以在任务内部的循环中通过调用 Thread.currentThread().isInterrupted()
来检查中断状态,并在需要时通过 return 或抛出 InterruptedException 来停止任务。

总之,cancel 方法是用来请求取消线程执行的,但具体的取消逻辑需要线程的具体实现来决定。

重复连接问题

在Django Channels中,你可以使用一个signal来处理用户连接和断开连接的事件。通过拦截连接事件,你可以检查用户是否已经连接过,并避免多次连接。

首先,在你的consumers.py文件中,定义一个signal处理函数。该函数会在用户连接和断开连接时被调用:

from channels.db import database_sync_to_async

connected_users = set()

@database_sync_to_async
def update_connected_users(user_id, add=True):
    if add:
        connected_users.add(user_id)
    else:
        connected_users.remove(user_id)

async def handle_connect(user_id):
    await update_connected_users(user_id, add=True)

async def handle_disconnect(user_id):
    await update_connected_users(user_id, add=False)

接下来,在你的consumer中,可以使用上述定义的signal处理函数来检查用户是否已连接,并避免多次连接和加入组:

from channels.generic.websocket import AsyncWebsocketConsumer
from django.contrib.auth.models import User
from django.dispatch import receiver
from channels.layers import get_channel_layer
from asgiref.sync import async_to_sync

@receiver(user_logged_in)
def on_user_login(sender, request, user, **kwargs):
    channel_layer = get_channel_layer()
    async_to_sync(channel_layer.group_send)(
        "connected_users",  # 你可以定义一个组名,用于管理连接的用户
        {
            'type': 'handle_connect',
            'user_id': user.id,
        }
    )

@receiver(user_logged_out)
def on_user_logout(sender, request, user, **kwargs):
    channel_layer = get_channel_layer()
    async_to_sync(channel_layer.group_send)(
        "connected_users",
        {
            'type': 'handle_disconnect',
            'user_id': user.id,
        }
    )

class MyConsumer(AsyncWebsocketConsumer):
    async def connect(self):
        user_id = self.scope['user'].id
        if user_id not in connected_users:
            await self.channel_layer.group_add(
                "connected_users",
                self.channel_name
            )
            await handle_connect(user_id)

        await self.accept()

    async def disconnect(self, close_code):
        user_id = self.scope['user'].id
        await self.channel_layer.group_discard(
            "connected_users",
            self.channel_name
        )
        await handle_disconnect(user_id)

    async def receive(self, text_data):
        # 处理接收到的数据
        pass

此处使用了Django的信号量机制,通过拦截用户登录和退出事件,来更新连接的用户集合。当用户连接时,会检查该用户是否已经存在于集合中,只有当用户不存在时,才会加入用户组并调用handle_connect函数。同样,当用户断开连接时,会从用户组中移除该用户,并调用handle_disconnect函数。

这样,你就可以避免同一个用户多次连接并加入组了。同时,你可以通过connected_users集合来管理当前连接的用户。

归档

安装

pip3 install channels

快速上手

  • 在settings中添加配置

      INSTALLED_APPS = [
      'django.contrib.admin',
      'django.contrib.auth',
      'django.contrib.contenttypes',
      'django.contrib.sessions',
      'django.contrib.messages',
      'django.contrib.staticfiles',
      'channels',
      ]
    
      ASGI_APPLICATION = '项目名.asgi.application'
    
  • 创建websocket应用和路由

      #!/usr/bin/env python
      # -*- coding:utf-8 -*-
      from channels.routing import ProtocolTypeRouter, URLRouter
      from django.conf.urls import url
      from chat import consumers
      
      
      application = ProtocolTypeRouter({
          'websocket': URLRouter([
              url(r'^chat/$', consumers.ChatConsumer),
          ])
      })
    
    • 编写处理websocket逻辑业务

    案例一

      #!/usr/bin/env python
      # -*- coding:utf-8 -*-
      from channels.generic.websocket import WebsocketConsumer
      from channels.exceptions import StopConsumer
      
      class ChatConsumer(WebsocketConsumer):
      
          def websocket_connect(self, message):
              self.accept()
      
          def websocket_receive(self, message):
              print('接收到消息', message)
              self.send(text_data='收到了')
      
          def websocket_disconnect(self, message):
              print('客户端断开连接了')
              raise StopConsumer()
    

    案例二

      #!/usr/bin/env python
      # -*- coding:utf-8 -*-
      from channels.generic.websocket import WebsocketConsumer
      from channels.exceptions import StopConsumer
      
      
      class SimpleChatConsumer(WebsocketConsumer):
          def connect(self):
              self.accept()
      
          def receive(self, text_data=None, bytes_data=None):
              self.send(text_data)
      
              # 主动断开连接
              # self.close()
      
          def disconnect(self, code):
              print('客户端要断开了')
    

    案例三

      #!/usr/bin/env python
      # -*- coding:utf-8 -*-
      from channels.generic.websocket import WebsocketConsumer
      from channels.exceptions import StopConsumer
      
      
      CLIENTS = []
      
      class ChatConsumer(WebsocketConsumer):
      
          def connect(self):
              self.accept()
              CLIENTS.append(self)
      
          def receive(self, text_data=None, bytes_data=None):
              for item in CLIENTS:
                  item.send(text_data)
      
              # 主动断开连接
              # self.close()
      
          def disconnect(self, code):
              CLIENTS.remove(self)
    

channel layer

基于内存的channel layer

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

业务处理

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)

基于 redis的channel layer

安装: pip3 install channels-redis

配置

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


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

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

CHANNEL_LAYERS = {
    "default": {
        "BACKEND": "channels_redis.core.RedisChannelLayer",
        "CONFIG": {
            "hosts": ["redis://:password@10.211.55.25:6379/0"],
            "symmetric_encryption_keys": [SECRET_KEY],
        },
    },
}

业务逻辑

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)

异步

import json
from channels.generic.websocket import WebsocketConsumer, AsyncWebsocketConsumer
from asgiref.sync import async_to_sync
from loguru import logger




class ChatConsumer(AsyncWebsocketConsumer):

    def get_room_id(self):
        '''获取组的id'''
        return self.scope['url_route']['kwargs']['group']

    async def connect(self):
        await self.channel_layer.group_add(str(self.get_room_id()), self.channel_name)
        await self.accept()
        logger.warning(f'组id:【{self.get_room_id()}】,通道名:【{self.channel_name}】连接成功')
        logger.success(f'当前全部组:{self.channel_layer}')

    async def receive(self, text_data=None, bytes_data=None):
        await self.channel_layer.group_send(str(self.get_room_id()), {
            'type': 'xxx.ooo',
            'message': text_data
        })

    async def xxx_ooo(self, event):
        print(event)
        message = event['message']
        await self.send(message)

    async def disconnect(self, code):
        print(code)
        await self.channel_layer.group_discard(str(self.get_room_id()), self.channel_name)
        await self.close()

在其他地方向通道发送消息

from channels.layers import get_channel_layer
from asgiref.sync import async_to_sync


def flash_data(url: str):
    channel_layer = get_channel_layer()
    async_to_sync(channel_layer.group_send)(str(88), {
        'type': 'chat_message',
        'message': json.dumps({'flash_url_ctypes': str(url)})
    })

关于启动

#daphne tree_hole_gpt.asgi:application -p 8002 -b 127.0.0.1
uvicorn tree_hole_gpt.asgi:application --port 8002 --host 127.0.0.1 --workers 2
  • 0
    点赞
  • 6
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
好的,下面是一个简单的 Django-channels 实现 WebSocket 的示例: 首先,安装 Django-channels: ``` pip install channels ``` 然后,在 Django 的 settings.py 中添加以下配置: ```python INSTALLED_APPS = [ ... 'channels', ] ASGI_APPLICATION = 'myproject.routing.application' CHANNEL_LAYERS = { 'default': { 'BACKEND': 'channels.layers.InMemoryChannelLayer', }, } ``` 在 myproject 目录下创建一个 routing.py 文件,添加以下内容: ```python from channels.routing import ProtocolTypeRouter, URLRouter from django.urls import path from myapp.consumers import MyConsumer application = ProtocolTypeRouter({ 'websocket': URLRouter([ path('ws/', MyConsumer.as_asgi()), ]), }) ``` 在 myapp 目录下创建一个 consumers.py 文件,添加以下内容: ```python from channels.generic.websocket import AsyncWebsocketConsumer import json class MyConsumer(AsyncWebsocketConsumer): async def connect(self): await self.accept() async def disconnect(self, close_code): pass async def receive(self, text_data): text_data_json = json.loads(text_data) message = text_data_json['message'] await self.send(text_data=json.dumps({ 'message': message })) ``` 在你的模板中添加以下 JavaScript 代码,用于连接 WebSocket 和发送消息: ```javascript const socket = new WebSocket('ws://' + window.location.host + '/ws/'); socket.onmessage = function(e) { const data = JSON.parse(e.data); console.log(data); }; socket.onclose = function(e) { console.error('Chat socket closed unexpectedly'); }; document.querySelector('#chat-message-input').focus(); document.querySelector('#chat-message-input').onkeyup = function(e) { if (e.keyCode === 13) { // enter, return document.querySelector('#chat-message-submit').click(); } }; document.querySelector('#chat-message-submit').onclick = function(e) { const messageInputDom = document.querySelector('#chat-message-input'); const message = messageInputDom.value; socket.send(JSON.stringify({ 'message': message })); messageInputDom.value = ''; }; ``` 最后,启动 Django 服务器,打开浏览器访问你的应用程序,进入调试器,打开控制台,你应该可以看到从服务器发送的消息。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

像风一样的男人@

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值