我是Stack Overflow的新手(虽然是一个长期的“追踪者”!)所以请温柔地对待我!
我正在尝试学习Python,尤其是使用websockets的Asyncio。
在网上搜索了示例/教程我已经将以下微小的聊天应用程序放在一起,并且可以在它变得越来越庞大(更多命令等)之前使用一些建议,并且变得难以重构。
我的主要问题是,为什么(在发送DISCONNECT命令时)是否需要asyncio.sleep(0)才能在关闭连接之前发送断开连接验证消息?
除此之外,我在这个结构的正确轨道上吗?
我觉得有太多异步/等待,但我无法完全理解为什么。
在几个小时内盯着教程和S / O帖子似乎没有帮助,所以我想我会直接得到一些专家建议!
我们开始,简单的WS服务器响应“nick”,“msg”,“test”& “断开”命令。不需要前缀,即“nick Rachel”。
import asyncio
import websockets
import sys
class ChatServer:
def __init__(self):
print("Chat Server Starting..")
self.Clients = set()
if sys.platform == 'win32':
self.loop = asyncio.ProactorEventLoop()
asyncio.set_event_loop(self.loop)
else:
self.loop = asyncio.get_event_loop()
def run(self):
start_server = websockets.serve(self.listen, '0.0.0.0', 8080)
try:
self.loop.run_until_complete(start_server)
print("Chat Server Running!")
self.loop.run_forever()
except:
print("Chat Server Error!")
async def listen(self, websocket, path):
client = Client(websocket=websocket)
sender_task = asyncio.ensure_future(self.handle_outgoing_queue(client))
self.Clients.add(client)
print("+ connection: " + str(len(self.Clients)))
while True:
try:
msg = await websocket.recv()
if msg is None:
break
await self.handle_message(client, msg)
except websockets.exceptions.ConnectionClosed:
break
self.Clients.remove(client)
print("- connection: " + str(len(self.Clients)))
async def handle_outgoing_queue(self, client):
while client.websocket.open:
msg = await client.outbox.get()
await client.websocket.send(msg)
async def handle_message(self, client, data):
strdata = data.split(" ")
_cmd = strdata[0].lower()
try:
# Check to see if the command exists. Otherwise, AttributeError is thrown.
func = getattr(self, "cmd_" + _cmd)
try:
await func(client, param, strdata)
except IndexError:
await client.send("Not enough parameters!")
except AttributeError:
await client.send("Command '%s' does not exist!" % (_cmd))
# SERVER COMMANDS
async def cmd_nick(self, client, param, strdata):
# This command needs a parameter (with at least one character). If not supplied, IndexError is raised
# Is there a cleaner way of doing this? Otherwise it'll need to reside within all functions that require a param
test = param[1][0]
# If we've reached this point there's definitely a parameter supplied
client.Nick = param[1]
await client.send("Your nickname is now %s" % (client.Nick))
async def cmd_msg(self, client, param, strdata):
# This command needs a parameter (with at least one character). If not supplied, IndexError is raised
# Is there a cleaner way of doing this? Otherwise it'll need to reside within all functions that require a param
test = param[1][0]
# If we've reached this point there's definitely a parameter supplied
message = strdata.split(" ",1)[1]
# Before we proceed, do we have a nickname?
if client.Nick == None:
await client.send("You must choose a nickname before sending messages!")
return
for each in self.Clients:
await each.send("%s says: %s" % (client.Nick, message))
async def cmd_test(self, client, param, strdata):
# This command doesn't need a parameter, so simply let the client know they issued this command successfully.
await client.send("Test command reply!")
async def cmd_disconnect(self, client, param, strdata):
# This command doesn't need a parameter, so simply let the client know they issued this command successfully.
await client.send("DISCONNECTING")
await asyncio.sleep(0) # If this isn't here we don't receive the "disconnecting" message - just an exception in "handle_outgoing_queue" ?
await client.websocket.close()
class Client():
def __init__(self, websocket=None):
self.websocket = websocket
self.IPAddress = websocket.remote_address[0]
self.Port = websocket.remote_address[1]
self.Nick = None
self.outbox = asyncio.Queue()
async def send(self, data):
await self.outbox.put(data)
chat = ChatServer()
chat.run()
答案 0 :(得分:1)
您的代码使用无限大小Queues
,这意味着.put()
调用.put_nowait()
并立即返回。 (如果您确实希望在代码中保留这些队列,请考虑使用队列中的“无”作为关闭连接并将client.websocket.close()
移至handle_outgoing_queue()
)的信号。
另一个问题:请考虑将for x in seq: await co(x)
替换为await asyncio.wait([co(x) for x in seq])
。尝试使用asyncio.sleep(1)
来体验巨大的差异。
我认为更好的选择是放弃所有发件箱Queue
,然后继续内置的asyncio队列和ensure_future
。 websockets包已在其实现中包含队列。
答案 1 :(得分:1)
我想指出的是,websockets的作者在2017年7月17日的帖子中指出,当连接关闭但是在某些时候发生了变化时,websockets 习惯于返回None。相反,他建议您使用try并处理异常。 OP的代码显示了对None和try / except的检查。 None检查是不必要的冗长,显然甚至不准确,因为在当前版本中,websocket.recv()在客户端关闭时不返回任何内容。
解决“主要”问题,它看起来像种类的竞争条件。请记住,asyncio通过四处走动并触摸所有等待的元素来实现它的工作,以便轻轻推动它们。如果在清除队列之前的某个时刻处理了“close connection”命令,则客户端将永远不会获取队列中的最后一条消息。添加async.sleep会为循环添加额外的步骤,并可能使您的队列清空任务超前于“关闭连接”。
解决等待的数量,这就是你需要完成多少异步事件才能完成目标。如果你在任何时候阻止,你将停止你想要继续的所有其他任务。