我有一个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
:作为客户端打开许多浏览器选项卡,并一次关闭一个浏览器选项卡。
似乎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
中删除客户。