【问题标题】:Using Websocket in Django View Not Working在 Django 视图中使用 Websocket 不起作用
【发布时间】:2021-06-15 00:51:51
【问题描述】:

问题总结

我正在使用 Django 和 web-sockets 将数据发送到前端(React 组件)。当我运行应用程序并从控制台发送数据时,一切正常。当我使用前端的按钮触发运行相同功能的 Django 视图时,它不起作用并生成令人困惑的错误消息。

我希望能够单击开始将数据发送到 websocket 的前端按钮。

我是 Django、websockets 和 React 的新手,因此请您耐心等待。

概述

  1. Django 后端和 React 前端使用 Django 通道(网络套接字)连接。
  2. 用户点击前端的按钮,这在 Django REST API 端点上执行 fetch()
  3. [不工作]上述端点的视图开始通过网络套接字发送数据。
  4. 使用此值更新前端。

简短的错误说明

错误 Traceback 很长,所以在文末附上。开头是:

内部服务器错误:/api/run-create

结尾是:

ConnectionResetError: [WinError 10054] 现有连接被远程主机强行关闭

我的尝试

在 Django 视图外部发送数据

  • 下面的函数将数据发送到 web-socket。
  • 当我在控制台中运行它时完美运行 - 前端更新符合预期。
  • 注意:当从 Django 视图内部运行时,相同的函数会导致附加错误。
    import json
    import time
    
    import numpy as np
    import websocket
    
    
    def gen_fake_path(num_cities):
        path = list(np.random.choice(num_cities, num_cities, replace=False))
        path = [int(num) for num in path]
        return json.dumps({"path": path})
    
    
    def fake_run(num_cities, limit=1000):
        ws = websocket.WebSocket()
        ws.connect("ws://localhost:8000/ws/canvas_data")
        while limit:
            path_json = gen_fake_path(num_cities)
            print(f"Sending {path_json} (limit: {limit})")
            ws.send(path_json)
            time.sleep(3)
            limit -= 1
        print("Sending complete!")
        ws.close()
        return

其他细节

相关文件和配置

consumer.py

    class AsyncCanvasConsumer(AsyncWebsocketConsumer):
        async def connect(self):
            self.group_name = "dashboard"
            await self.channel_layer.group_add(self.group_name, self.channel_name)
            await self.accept()
    
        async def disconnect(self, close_code):
            await self.channel_layer.group_discard(self.group_name, self.channel_name)
    
        async def receive(self, text_data=None, bytes_data=None):
            print(f"Received: {text_data}")
            data = json.loads(text_data)
            to_send = {"type": "prep", "path": data["path"]}
            await self.channel_layer.group_send(self.group_name, to_send)
    
        async def prep(self, event):
            send_json = json.dumps({"path": event["path"]})
            await self.send(text_data=send_json)

相关视图.py

    @api_view(["POST", "GET"])
    def run_create(request):
        serializer = RunSerializer(data=request.data)
        if not serializer.is_valid():
            return Response({"Bad Request": "Invalid data..."}, status=status.HTTP_400_BAD_REQUEST)
        # TODO: Do run here.
        serializer.save()
        fake_run(num_cities, limit=1000)
        return Response(serializer.data, status=status.HTTP_200_OK)

相关设置.py

    WSGI_APPLICATION = 'evolving_salesman.wsgi.application'
    ASGI_APPLICATION = 'evolving_salesman.asgi.application'
    
    CHANNEL_LAYERS = {
        "default": {
            "BACKEND": "channels.layers.InMemoryChannelLayer"
        }
    }

相关路由.py

    websocket_url_pattern = [
        path("ws/canvas_data", AsyncCanvasConsumer.as_asgi()),
    ]

完全错误

https://pastebin.com/rnGhrgUw

编辑:解决方案

Kunal Solanke 的建议解决了这个问题。我没有使用fake_run(),而是使用了以下内容:

    layer = get_channel_layer()
    for i in range(10):
        path = list(np.random.choice(4, 4, replace=False))
        path = [int(num) for num in path]
        async_to_sync(layer.group_send)("dashboard", {"type": "prep", "path": path})
        time.sleep(3)

【问题讨论】:

    标签: django websocket django-channels


    【解决方案1】:

    我建议您使用 get_channel_layer 实用程序,而不是创建从同一服务器到自身的新连接。因为您最终会通过打开这么多连接来增加服务器负载。 获得通道层后,您可以像通常发送 evnet 一样简单地进行组发送。 你可以阅读更多关于here

    from channels.layers import get_channel_layer
    from asgiref.sync import async_to_sync
    def media_image(request,chat_id) :
        if request.method == "POST" :
            data = {}
            if request.FILES["media_image"] is not None :
                item = Image.objects.create(owner = request.user,file=request.FILES["media_image"])
                message=Message.objects.create(item =item,user=request.user )
                chat = Chat.objects.get(id=chat_id)
                chat.messages.add(message)
                layer = get_channel_layer()
                item = {
                   "media_type": "image",
                    "url" : item.file.url,
                    "user" : request.user.username,
                    'caption':item.title
                }
                async_to_sync(layer.group_send)(
                    'chat_%s'%str(chat_id),
    #this is the channel group name,which is defined inside your consumer"
                    {
                        "type":"send_media",
                        "item" : item
                        
                    }
                )
    
            return HttpResponse("media sent")
    

    在错误日志中,我可以看到第一次迭代握手成功,第二次握手失败。您可以通过在 for 循环中打印一些内容来检查。如果是这种情况,握手很可能由于多个连接而失败。我不知道 Inmemrorycache 支持多少个来自同一来源的连接,但这可能是第二个连接断开的原因。你可以在频道docs获得一些想法。如果你不想更改代码,请尝试使用 redis,如果你使用的是 linux,这很容易。

    【讨论】:

    • 谢谢Kunal,这解决了!如您所述,我使用了从视图中发送的组。
    猜你喜欢
    • 2017-06-02
    • 1970-01-01
    • 2014-01-31
    • 1970-01-01
    • 2016-12-08
    • 2020-07-15
    • 2016-03-31
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多