Python Tornado KeyError从客户端集中删除客户端时

问题描述 投票:0回答:1

我有一个Python Tornado Websocket服务器,该服务器将客户端存储在一个共享的set()中,以便我知道连接了多少个客户端。

挑战在于,在on_close之后调用WebSocketClosedError会引发KeyError,并且不会从连接的客户端集中删除client-instance。此错误导致我的服务器累积了1000个以上的客户端,即使活动的客户端大约只有5个。

我的代码:

import tornado.iostream
import tornado.websocket
import asyncio


class SocketHandler(tornado.websocket.WebSocketHandler):
    socket_active_message = {"status": "Socket Connection Active"}
    waiters = set()

    def initialize(self):
        self.client_name = "newly_connected"

    def open(self):
        print('connection opened')
        # https://kite.com/python/docs/tornado.websocket.WebSocketHandler.set_nodelay
        self.set_nodelay(True)
        SocketHandler.waiters.add(self)

    def on_close(self):
        print("CLOSED!", self.client_name)
        SocketHandler.waiters.remove(self)

    def check_origin(self, origin):
        # Override the origin check if needed
        return True

    async def send_updates(self, message):
        print('starting socket service loop')
        loop_counter = 0
        while True:
            try:
                await self.write_message({'status': 82317581})
            except tornado.websocket.WebSocketClosedError:
                self.on_close()
            except tornado.iostream.StreamClosedError:
                self.on_close()
            except Exception as e:
                self.on_close()
                print('Exception e:', self.client_name)
            await asyncio.sleep(0.05)

    async def on_message(self, message):
        print("RECEIVED :", message)
        self.client_name = message
        await self.send_updates(message)


def run_server():
    # Create tornado application and supply URL routes
    webApp = tornado.web.Application(
        [
            (
                r"/",
                SocketHandler,
                {},
            ),
        ]
    )

    application = tornado.httpserver.HTTPServer(webApp)
    webApp.listen(3433)
    # Start IO/Event loop
    tornado.ioloop.IOLoop.instance().start()


run_server()

堆栈跟踪:

Traceback (most recent call last):
  File "/mnt/c/Users/EE/projects/new/venv/lib/python3.8/site-packages/tornado/web.py", line 1699, in _execute
    result = await result
  File "/mnt/c/Users/EE/projects/new/venv/lib/python3.8/site-packages/tornado/websocket.py", line 278, in get
    await self.ws_connection.accept_connection(self)
  File "/mnt/c/Users/EE/projects/new/venv/lib/python3.8/site-packages/tornado/websocket.py", line 881, in accept_connection
    await self._accept_connection(handler)
  File "/mnt/c/Users/EE/projects/new/venv/lib/python3.8/site-packages/tornado/websocket.py", line 964, in _accept_connection
    await self._receive_frame_loop()
  File "/mnt/c/Users/EE/projects/new/venv/lib/python3.8/site-packages/tornado/websocket.py", line 1118, in _receive_frame_loop
    await self._receive_frame()
  File "/mnt/c/Users/EE/projects/new/venv/lib/python3.8/site-packages/tornado/websocket.py", line 1209, in _receive_frame
    await handled_future
  File "/mnt/c/Users/EE/projects/new/venv/lib/python3.8/site-packages/tornado/ioloop.py", line 743, in _run_callback
    ret = callback()
  File "/mnt/c/Users/EE/projects/new/venv/lib/python3.8/site-packages/tornado/websocket.py", line 658, in <lambda>
    self.stream.io_loop.add_future(result, lambda f: f.result())
  File "ask_So.py", line 50, in on_message
    await self.send_updates(message)
  File "ask_So.py", line 39, in send_updates
    self.on_close()
  File "ask_So.py", line 26, in on_close
    SocketHandler.waiters.remove(self)
KeyError: <__main__.SocketHandler object at 0x7ffef9f25520>

我已经尝试过将服务员集合移出课堂,但仍然会产生相同的行为。

模拟WebSocketClosedError:作为客户端打开许多浏览器选项卡,并一次关闭一个浏览器选项卡。

python websocket tornado keyerror
1个回答
0
投票

似乎self.on_close()被调用了两次。一旦您从send_updates()内部手动调用它,然后在实际上关闭连接时,Tornado也将调用self.on_close()。由于self对象已第一次从集合中移除,因此第二次引发KeyError

如果要关闭连接,只需拨打self.close()self.close()方法将由Tornado自动调用。

此外,您可以在self.on_close()内部的try...except块中处理异常。


更新

此答案的前一部分应解决on_close相关问题。此更新是关于为什么未从KeyError集中删除客户端的原因。

所以,我测试了您的代码,并在这里发现了一个主要问题:

waiters

[每当客户端发送消息时,它将运行 async def on_message(self, message): print("RECEIVED :", message) self.client_name = message await self.send_updates(message) # <- This is problematic 方法。因此,即使只有一个客户端发送一条消息,比如说10次,self.send_updates也将被调用10次,结果,您将同时运行10个send_updates循环!

随着循环次数的增加,它最终会阻塞服务器。这意味着Tornado没有时间运行其他代码,因为它忙于处理许多while循环。因此,如果龙卷风有机会,则很少会从while中删除客户。

© www.soinside.com 2019 - 2024. All rights reserved.