django channels socket通信实现

我们知道python有socket包可以直接实现socket通信。

但在使用django时,不太适用于socket的方式与前端交互,对此django有channels来很好的支持socket通信。参考了网上很多资料之后发现写的不是很详细,最详细的是官方的资料。

项目地址:

GitHub - Wellbulizy/django-socket-channels: django-channels实现socket通信的简单例子

参考资料

Channels官方资料:

Introduction — Channels 3.0.4 documentation

Pypi-channels-redis:

channels-redis · PyPI

版本

Python:anaconda3/py3.7.2

Linux Centos8 localhost.localdomain 4.18.0-240.el8.x86_64 x86_64 x86_64 x86_64 GNU/Linux

Django: 2.2.16

项目结构

后续说明根据项目结构目录来讲解,请记住这个项目结构的文件。

 

页面代码编写

在传统的socket编写时,我们只需要一个端口,你发我接 + 我发你接,互相通信即完事了,然后突然转到django 的channels很难理解。其实原理是一样的,需要有路由中间件来区分web socket通信。

Ps:教程还是官网最详细,这里粗略带过,真的建议去看channels的官方

先不考虑socket通信,我们简单的把聊天室的界面做出来

dsocket/urls.py

from django.conf.urls import include
from django.contrib import admin
from django.urls import path

urlpatterns = [
    path('chat/', include('mysite.urls')),
    path('admin/', admin.site.urls),
]

#指向app的urls路径↓

mysite/urls.py

from django.urls import path

from . import views

urlpatterns = [

    path('', views.index, name='index'),

    path('<str:room_name>/', views.room, name='room'),

]

#因为模板这些设置是默认的,不需要更改settings可以直接创建目录并添加html文件

mysite/templates/index.html

<!-- chat/templates/chat/index.html -->

<!DOCTYPE html>

<html>

<head>

    <meta charset="utf-8"/>

    <title>Chat Rooms</title>

</head>

<body>

    What chat room would you like to enter?<br>

    <input id="room-name-input" type="text" size="100"><br>

    <input id="room-name-submit" type="button" value="Enter">



    <script>

        document.querySelector('#room-name-input').focus();

        document.querySelector('#room-name-input').onkeyup = function(e) {

            if (e.keyCode === 13) {  // enter, return

                document.querySelector('#room-name-submit').click();

            }

        };



        document.querySelector('#room-name-submit').onclick = function(e) {

            var roomName = document.querySelector('#room-name-input').value;

            window.location.pathname = '/chat/' + roomName + '/';

        };

    </script>

</body>

</html>

mysite/templates/room.html

#代码太长,稍微压缩了一些行,所以看上去可能不太美观…

<!-- chat/templates/chat/room.html -->

<!DOCTYPE html>

<html>

<head><meta charset="utf-8"/><title>Chat Room</title></head>

<body>

    <textarea id="chat-log" cols="100" rows="20"></textarea><br>

    <input id="chat-message-input" type="text" size="100"><br>

    <input id="chat-message-submit" type="button" value="Send">

    {{ room_name|json_script:"room-name" }}

    <script>

        const roomName = JSON.parse(document.getElementById('room-name').textContent);



        const chatSocket = new WebSocket('ws://'+window.location.host+'/ws/chat/'+roomName+ '/');



        chatSocket.onmessage = function(e) {const data = JSON.parse(e.data);document.querySelector('#chat-log').value += (data.message + '\n');};



        chatSocket.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;

            chatSocket.send(JSON.stringify({

                'message': message

            }));

            messageInputDom.value = '';

        };

    </script>

</body>

</html>

前置验证

启动项目:

Python manage.py runserver 192.168.70.132:8008

通过以上步骤,聊天室socket的界面暂时是OK了,访问如下

输入内容按回车,会跳转到聊天室:

 当然这里只是聊天室,核心的通信功能都没有,F12大概能看到通信报错

socket代码编写

现在到了核心代码编写部分,我们需要加入channels的中间件,拦截请求识别请求类型,需要更改settings.py,由于官方和网上教程都是免密的redis配置,所以我到另一个channels-redis的官网找到了带密码的配置内容。

vim /dsocket/settings.py

#INSTALLED_APPS添加channels插件,如果已经有自定义的中间件,自己看情况添加

INSTALLED_APPS = [
    'mysite',   #app的名字
    'channels',
    'django.contrib.admin',
    'django.contrib.auth',
    'django.contrib.contenttypes',
    'django.contrib.sessions',
    'django.contrib.messages',
    'django.contrib.staticfiles',
]
#添加下面的内容,单会话通信不需要
ASGI_APPLICATION = 'dsocket.asgi.application'
CHANNEL_LAYERS = {
    'default': {
        'BACKEND': 'channels_redis.core.RedisChannelLayer',
        'CONFIG': {
            "hosts": ["redis://:123456@127.0.0.1:6379/6"],
            #免密:"hosts": [('127.0.0.1', 6379)],
        },
    },
}

vim /dsocket/asgi.py
        
该文件的作用是请求拦截中间件,如下有识别http与websocket请求(前缀ws://或wss://),根据不同的请求做不同的处理。

import os
import django
from channels.auth import AuthMiddlewareStack
from channels.http import AsgiHandler
from channels.routing import ProtocolTypeRouter,URLRouter

os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'dsocket.settings')
django.setup()
from django.urls import re_path
from mysite import consumers
websocket_urlpatterns = [re_path(r'ws/chat/(?P<room_name>\w+)/$', consumers.ChatConsumer.as_asgi()),]


application = ProtocolTypeRouter({
    "http": AsgiHandler(),  #django3这里有区别,这里只是django2的写法
    "websocket": AuthMiddlewareStack(URLRouter(websocket_urlpatterns)),
})

单会话vim /mysite/consumers.py

#这是一个单会话通信的处理代码

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
        }))

什么是单会话通信?如果你同时打开多个聊天室,比如http://192.168.70.132:8008/chat/rowb/开两个界面,两个界面互相输入的信息都只有自己能看到。如果需要在同一个聊天室的都能看到,那需要启动通道层。这就是上面settings.py里配置redis的作用。下面即启用通道层后的consumers.py代码。

多会话 /mysite/consumers.py

对比上面的单会话模式,逻辑上加入了同一个room的对象会加入到组中,即变成多对一的模式,在处理回信时发送给一个组。而不是单独回信与发起者。

import json
from asgiref.sync import async_to_sync
from channels.generic.websocket import WebsocketConsumer

class ChatConsumer(WebsocketConsumer):
    #新的socket函数,支持广播
    def connect(self):
        self.room_name = self.scope['url_route']['kwargs']['room_name']
        self.room_group_name = 'chat_%s' % self.room_name

        # Join room group
        async_to_sync(self.channel_layer.group_add)(
            self.room_group_name,
            self.channel_name
        )

        self.accept()

    def disconnect(self, close_code):
        # Leave room group
        async_to_sync(self.channel_layer.group_discard)(
            self.room_group_name,
            self.channel_name
        )

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

        # Send message to room group
        async_to_sync(self.channel_layer.group_send)(
            self.room_group_name,
            {
                'type': 'chat_message',
                'message': message
            }
        )

    # Receive message from room group
    def chat_message(self, event):
        message = event['message']

        # Send message to WebSocket
        self.send(text_data=json.dumps({
            'message': message
        }))

常见错误

ModuleNotFoundError: No module named 'django.core.asgi'
官方asgi.py的坑,其实这个导入没有必要,删掉不影响
aioredis.errors.AuthError: NOAUTH Authentication required.
Redis密码错误,或者redis需要密码,但是你没有配置

初始化运行

因为启用了redis的会话认证,django的会话框架需要数据库,所以要migrate初始化迁移应用数据库更改

python manage.py migrate

Operations to perform:

  Apply all migrations: admin, auth, contenttypes, sessions

Running migrations:

  Applying contenttypes.0001_initial... OK

  Applying auth.0001_initial... OK

  Applying admin.0001_initial... OK

  Applying admin.0002_logentry_remove_auto_add... OK

  Applying admin.0003_logentry_add_action_flag_choices... OK

  Applying contenttypes.0002_remove_content_type_name... OK

  Applying auth.0002_alter_permission_name_max_length... OK

  Applying auth.0003_alter_user_email_max_length... OK

  Applying auth.0004_alter_user_username_opts... OK

  Applying auth.0005_alter_user_last_login_null... OK

  Applying auth.0006_require_contenttypes_0002... OK

  Applying auth.0007_alter_validators_add_error_messages... OK

  Applying auth.0008_alter_user_username_max_length... OK

  Applying auth.0009_alter_user_last_name_max_length... OK

  Applying auth.0010_alter_group_name_max_length... OK

  Applying auth.0011_update_proxy_permissions... OK

  Applying sessions.0001_initial... OK

#启动django后台

python manage.py runserver 192.168.70.132:8008

socket监听验证

运行项目后,会发现启动的信息变了,比如下面有starting asgi/channels即表示监听了socket的请求。

Watching for file changes with StatReloader
Performing system checks...
System check identified no issues (0 silenced).
You have 18 unapplied migration(s). Your project may not work properly until you apply the migrations for app(s): admin, auth, contenttypes, sessions.
Run 'python manage.py migrate' to apply them.
October 1, 2020 - 21:31:22
Django version 2.2.16, using settings dsocket.settings'
Starting ASGI/Channels version 3.0.0 development server at http://192.168.70.132:8008/
Quit the server with CONTROL-C.

### 回答1: Django-Channels 是一个基于 Django 框架的 WebSocket 库,用于实现实时应用程序。要发送 WebSocket 消息,您可以按照以下步骤进行操作: 1. 在 Django-Channels 的 consumer 中定义一个 WebSocket 处理函数,例如: ```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 })) ``` 2. 在前端代码中创建 WebSocket 连接并发送消息,例如: ```javascript const socket = new WebSocket('ws://localhost:8000/ws/my_consumer/'); socket.onopen = function(event) { console.log('WebSocket is connected.'); }; socket.onmessage = function(event) { console.log('Message received:', event.data); }; socket.onclose = function(event) { console.log('WebSocket is closed.'); }; const message = { 'message': 'Hello, world!' }; socket.send(JSON.stringify(message)); ``` 在这个例子中,我们创建了一个 WebSocket 连接,并将消息发送到 `MyConsumer` consumer 中。当 `MyConsumer` consumer 接收到消息时,它将发送一个响应消息回到前端,前端代码将在 `onmessage` 回调函数中接收到响应消息并在控制台中打印出来。 注意,在生产环境中,您应该将 WebSocket 连接配置为使用 HTTPS 而不是 HTTP,并使用适当的身份验证和授权方法来保护您的应用程序。 ### 回答2: Django-Channels是一个用于构建实时Web应用程序的库,它支持使用WebSocket进行实时通信。在Django-Channels中,我们可以使用`channels.layers`模块来发送socket消息。 首先,我们需要在Django的设置文件中配置Channels。我们需要添加Channels的应用,并设置`ASGI_APPLICATION`为我们的应用名称。如下所示: ```python INSTALLED_APPS = [ ... 'channels', ] ASGI_APPLICATION = 'myproject.routing.application' ``` 接下来,我们需要创建一个routing.py文件来配置Channels路由。在这个文件中,我们可以定义消息路由的处理函数。示例如下: ```python from channels.routing import ProtocolTypeRouter, URLRouter from myapp import consumers application = ProtocolTypeRouter({ 'websocket': URLRouter([ url(r'^ws/myapp/(?P<room_name>\w+)/$', consumers.ChatConsumer), ]), }) ``` 然后,我们可以创建一个消费者类来处理接收到的消息,并使用Channels的`channels.layers`模块发送socket消息。在这个类中,我们可以重写`connect`、`disconnect`、`receive`等方法来处理连接、断开连接和接收消息的逻辑。以下是一个简单的示例: ```python from channels.generic.websocket import AsyncWebsocketConsumer from channels.layers import get_channel_layer from asgiref.sync import async_to_sync class MyConsumer(AsyncWebsocketConsumer): async def connect(self): self.room_name = self.scope['url_route']['kwargs']['room_name'] self.room_group_name = 'chat_%s' % self.room_name await self.channel_layer.group_add( self.room_group_name, self.channel_name ) await self.accept() async def disconnect(self, close_code): await self.channel_layer.group_discard( self.room_group_name, self.channel_name ) async def receive(self, text_data): await self.channel_layer.group_send( self.room_group_name, { 'type': 'chat_message', 'message': text_data } ) async def chat_message(self, event): message = event['message'] await self.send(text_data=message) ``` 最后,我们可以在需要发送socket消息的地方,使用`channel_layer`模块获取channel层对象,并使用其`group_send`方法来发送消息。以下是一个简单的示例: ```python from channels.layers import get_channel_layer from asgiref.sync import async_to_sync channel_layer = get_channel_layer() async_to_sync(channel_layer.group_send)( 'chat_room', { 'type': 'chat_message', 'message': 'Hello, World!' } ) ``` 以上是使用Django-Channels发送socket消息的基本实现代码。当有客户端连接到我们定义的WebSocket路由时,将会触发对应的消费者类方法,并通过`channel_layer`发送消息给指定的群组或客户端。 ### 回答3: Django-Channels是一个基于Django的扩展,用于为Web应用程序添加实时功能。它可以通过WebSocket协议发送和接收消息。以下是一个使用Django-Channels发送socket的代码示例: 首先,安装Django-Channels和其依赖项。可以使用pip命令来安装: ``` pip install channels ``` 然后,将Channels添加到您的Django项目的`INSTALLED_APPS`中。在您的项目的`settings.py`文件中进行如下配置: ```python INSTALLED_APPS = [ ... 'channels', ... ] ``` 接下来,创建一个名为`consumers.py`的文件,用于定义处理WebSockets连接的消费者: ```python from channels.generic.websocket import WebsocketConsumer class MyConsumer(WebsocketConsumer): def connect(self): # 连接成功时调用 self.accept() def disconnect(self, close_code): # 断开连接时调用 pass def receive(self, text_data): # 接收到来自客户端的消息时调用 pass def send_message(self, text_data): # 向客户端发送消息 self.send(text_data) ``` 在您的Django项目的`routing.py`文件中,配置将传入的WebSocket连接传递给相应的消费者: ```python from django.urls import path from . import consumers websocket_urlpatterns = [ path('ws/my-consumer/', consumers.MyConsumer.as_asgi()), ] ``` 最后,在您的Django项目的`settings.py`文件中启用Channels: ```python ASGI_APPLICATION = 'myapp.routing.application' ``` 现在,您可以在任何视图或其他地方使用以下代码来向客户端发送WebSocket消息: ```python from asgiref.sync import async_to_sync from channels.layers import get_channel_layer channel_layer = get_channel_layer() def send_socket_message(message): async_to_sync(channel_layer.group_send)('my-group', { 'type': 'send_message', 'text': message }) ``` 这将向名称为`my-group`的组中的所有客户端发送消息。 以上就是使用Django-Channels发送socket的代码实现
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值