I'm relatively new to websocket and asyncio and is struggling to gracefully handle the scenario when my server looses a connection.
My server code is:
#!/usr/bin/env python
# https://websockets.readthedocs.io/en/stable/intro.html
import asyncio
import websockets
import socket
USERS = set()
def users_event():
return str(len(USERS))
async def notify_users():
if USERS: # asyncio.wait doesn't accept an empty list
message = users_event()
await asyncio.wait([user.send(message) for user in USERS])
async def register(websocket):
USERS.add(websocket)
print('Current number of users:',len(USERS))
await notify_users()
async def unregister(websocket):
USERS.remove(websocket)
print('Current number of users:',len(USERS))
await notify_users()
async def consumer_handler(websocket, path):
try:
async for message in websocket:
ip = websocket.remote_address[0]
print('From ip ', ip, ' RECEIVED message: ', message)
except asyncio.exceptions.CancelledError:
print('Could not complete receiving')
except websockets.exceptions.ConnectionClosedError:
print('Connection unexpectly closed')
finally:
await unregister(websocket)
async def sendcommands(websocket, path):
ip = websocket.remote_address[0]
await register(websocket)
try:
while True:
await asyncio.sleep(2)
await websocket.send("test")
except asyncio.exceptions.CancelledError:
print('Could not complete sending')
except websockets.exceptions.ConnectionClosedError:
print('Connection unexpectly closed')
except websockets.exceptions.ConnectionClosed:
print('Connection closed during sending')
finally:
await unregister(websocket)
async def handler(websocket, path):
try:
consumer_task = asyncio.ensure_future(
consumer_handler(websocket, path))
producer_task = asyncio.ensure_future(
sendcommands(websocket, path))
done, pending = await asyncio.wait(
[consumer_task, producer_task],
return_when=asyncio.FIRST_COMPLETED,
)
for task in pending:
task.cancel()
except asyncio.exceptions.CancelledError:
print('Caught unfinished task')
if __name__ == '__main__':
serverport = 8765 # just a number that is assumed used by no other process
hostname = socket.gethostname()
IPAddr = socket.gethostbyname(hostname)
start_server = websockets.serve(handler, IPAddr, serverport)
try:
asyncio.get_event_loop().run_until_complete(start_server)
print('Server started')
asyncio.get_event_loop().run_forever()
except KeyboardInterrupt: # CTRL-C
print("Received exit, existing")
When the server start and the client connects, the server gives the following output to terminal:
Server started
Current number of users: 1
From ip 192.168.50.132 RECEIVED message: [WSc] Connected
From ip 192.168.50.132 RECEIVED message: A test message
I create a connection loss by pulling the plug on the client. The server gives the following output to terminal 40s after I pulled the plug:
Connection unexpectly closed
Current number of users: 0
Connection unexpectly closed
Task exception was never retrieved
future: <Task finished name='Task-10' coro=<sendcommands() done, defined at .\websocketserver.py:47> exception=KeyError(<websockets.server.WebSocketServerProtocol object at 0x000001E8617A09D0>)>
Traceback (most recent call last):
File ".\websocketserver.py", line 61, in sendcommands
await unregister(websocket)
File ".\websocketserver.py", line 29, in unregister
USERS.remove(websocket)
KeyError: <websockets.server.WebSocketServerProtocol object at 0x000001E8617A09D0>
My questions are:
- what is the reason for the KeyError?
- what is meant by "Task exception was never retrieved"?
- why do I get the messages after 40s? I guess a default timeout is set somewhere to 40s. Would it be a benefit with a shorter timeout duration?
Beside answering my questions, any suggestions for improving server robustness will be highly appreciated. The answers help me get my head around this setup because documentation of websockets+asyncio server in python is sparse.