未调用 Django Channels WebSocketConsumer 的 Disconnect() 方法

Posted

技术标签:

【中文标题】未调用 Django Channels WebSocketConsumer 的 Disconnect() 方法【英文标题】:Disconnect() method of Django Channels WebSocketConsumer not being called 【发布时间】:2020-07-11 09:00:14 【问题描述】:

我有以下继承自 WebSocketConsumer 的类:

from channels.generic.websocket import WebsocketConsumer
from channels.exceptions import StopConsumer

class MyConsumer(WebsocketConsumer):
   
       def connect(self):
          self.accept()
       
       def receive(self, text_data=None, bytes_data=None):
          data = json.loads(text_data)
          n = data["number"]
          
          for i in range(n):
              self.send(json.dumps("number":i))
              
       def disconnect():
           raise StopConsumer

输入 JSON 仅包含一个名为 number 的参数。我正在使用 chrome 插件测试此代码。当我打开连接并关闭它而不发送任何消息时,断开连接方法按预期执行。

当数字是例如 100 并且接收方法内的循环尚未完成并且我在两者之间断开连接时,不调用断开连接方法并且我收到以下错误:

ERROR - server - Exception inside application: Attempt to send on a closed protocol.
File "MyConsumer.py", line 2, in receive
    self.send
File "python3.6/site-packages/channels/generic/websocket.py", line 69, in send
    "type": "websocket.send", "text": text_data,
  File "python3.6/site-packages/channels/consumer.py", line 107, in send
    self.base_send(message)
  File "python3.6/site-packages/asgiref/sync.py", line 64, in __call__
    return call_result.result()
  File "/usr/local/var/pyenv/versions/3.6.10/lib/python3.6/concurrent/futures/_base.py", line 432, in result
    return self.__get_result()
  File "/usr/local/var/pyenv/versions/3.6.10/lib/python3.6/concurrent/futures/_base.py", line 384, in __get_result
    raise self._exception
  File "python3.6/site-packages/asgiref/sync.py", line 78, in main_wrap
    result = await self.awaitable(*args, **kwargs)
  File "python3.6/site-packages/channels/sessions.py", line 220, in send
    return await self.real_send(message)
  File "python3.6/site-packages/daphne/server.py", line 198, in handle_reply
    protocol.handle_reply(message)
  File "python3.6/site-packages/daphne/ws_protocol.py", line 179, in handle_reply
    self.serverSend(message["text"], False)
  File "site-packages/daphne/ws_protocol.py", line 223, in serverSend
    self.sendMessage(content.encode("utf8"), binary)
  File "python3.6/site-packages/autobahn/websocket/protocol.py", line 2216, in sendMessage
    raise Disconnected("Attempt to send on a closed protocol")
  Attempt to send on a closed protocol

即使给定号码的处理尚未完成,我也希望能够断开连接。 WebSocketConsumer 不可能吗?我是否误解了WebSocketConsumer 的同步性质?

【问题讨论】:

【参考方案1】:

我最近遇到了同样的问题,并做了一些关于如何克服这个问题的研究。

当我们使用WebsocketConsumer时,直到当前执行没有完成才会调用disconnect方法。这是因为名为 daphne 的包中存在问题(从他们的 Github 页面找到此信息)。

但这是我使用的一个技巧。如果您使用的是loop,请为此创建一个单独的方法并使用线程调用该方法。然后,您将能够在循环仍在运行时调用 disconnect 方法。也许你想在调用断开连接时停止执行,所以你可以谷歌一下或者使用我下面使用的方法:

from channels.generic.websocket import WebsocketConsumer
from channels.exceptions import StopConsumer
import threading

class MyConsumer(WebsocketConsumer):
   
       def connect(self):
          self.accept()
       
       def receive(self, text_data=None, bytes_data=None):
          data = json.loads(text_data)
          n = data["number"]
        
          self.stop = False  # A flag to break out from for loop
          self.thread = threading.Thread(target=self.action)  # This thread will work same but 
          self.thread.start()                                 # also allows to call disconnect()

       def action(self, n):
          for i in range(n):
              self.send(json.dumps("number":i))
              if self.stop:
                  break

              
       def disconnect():
           self.stop = True  # This will trigger the termination of loop
           del self.thread  # Maybe you also want to delete the thread
           raise StopConsumer

您可以调整要调用这些方法的逻辑。

【讨论】:

【参考方案2】:

当用户连接关闭并且您继续向其发送数据时,通道将引发异常 使用try ... except... block

def receive(self, text_data=None, bytes_data=None):
    data = json.loads(text_data)
    n = data["number"]
          
    try:
        for i in range(n):
            self.send(json.dumps("number":i))
    except Exception as e:# i don't remember exact exception, will change this soon
        print(e) #you can log this situation or do what ever you want

【讨论】:

异常来自 autobahn.exception.Disconnected。但这是一种干净的实施方式。我的意思是程序正在尝试在关闭的连接上发送。而且并不总是会引发此异常。我希望能够以干净的方式关闭连接。 你的代码中有for loop,程序会尝试完成它。您可以尝试在发送到通道之前检查连接是否处于活动状态,但我认为 try except 块更清晰 我明白你的意思,但为什么没有调用断开连接方法?我做错了什么吗? 你的循环正在运行,完成后应该会断开连接,但之前发生过异常,这就是为什么你没有断开连接方法 我理解的方式是当websocket客户端关闭连接并接收到一个事件时应该发生断开连接。来自 Django 文档:channels.readthedocs.io/en/latest/topics/…

以上是关于未调用 Django Channels WebSocketConsumer 的 Disconnect() 方法的主要内容,如果未能解决你的问题,请参考以下文章

Django Channels - 握手和连接,但未执行 websocket.connect 函数

Django 频道实时聊天保存发送的消息

Django用websocket实现聊天室之筑基篇

Django Channels - 根据打开的 Web 套接字的数量多次调用接收器函数

Django Channels 2.x 还是 Ajax?

Django 频道 - websocket_disconnect 在循环中被调用