I'm trying to build a multiplayer game (to be precise a Card game) in Python via websockets, but I'm currently failing in the very early steps.
What I'm trying to do:
- Clients connect to the websocket, until a certain number is reached
- The client sends an input to the server
- The server responds to each client separately and simultaneously
What works:
- Letting the client connect and storing a websocket per client works as expected, but then I'm a bit stuck.
What I've tried:
client.py
import asyncio
import websockets
import random
def random_name():
return "".join([random.choice("abcdefghijkl") for _ in range(5)])
async def main():
uri = "ws://localhost:1235"
print("Starting...")
async with websockets.connect(uri) as ws:
client_name = random_name()
await ws.send(client_name)
print(await ws.recv()) # server sends :client_name registered
print(await ws.recv()) # server sends: 3 people registered
print(await ws.recv()) # server sends: Waiting for input
inp = input()
await ws.send(inp)
asyncio.get_event_loop().run_until_complete(main())
server.py (very naive approach)
import websockets
import asyncio
clients = []
async def main(ws, *args, **kwargs):
while True:
print(f"Waiting for new connection! {ws}")
client_name = await ws.recv()
clients.append((client_name, ws, ws.remote_address))
await ws.send(f"{client_name} registered on {ws.remote_address}")
if len(clients) >= 3:
registered_clients = ', '.join([c[0] for c in clients])
print(f"3 clients ({registered_clients}) registered")
break
for name, ws_, _ in clients:
await ws_.send(f"3 clients registered ({registered_clients})")
await ws_.send(f"Waiting for input")
for _ in clients:
inp = await ws_.recv()
print(ws_.remote_address, inp)
start_server = websockets.serve(main, "localhost", "1235")
asyncio.get_event_loop().run_until_complete(start_server)
asyncio.get_event_loop().run_forever()
This fails with RuntimeError: cannot call recv while another coroutine is already waiting for the next message
. It seems that I cannot wait for the answer of each connected clients individually. Even though not perfect, I thought I can distinguish each client by looking at their remote addresses. For doing so, I modified the main function to
server.py, main function (naive approach)
async def main(ws, *args, **kwargs):
while True:
print(f"Waiting for new connection! {ws}")
client_name = await ws.recv()
clients.append((client_name, ws, ws.remote_address))
await ws.send(f"{client_name} registered on {ws.remote_address}")
if len(clients) >= 3:
registered_clients = ', '.join([c[0] for c in clients])
print(f"3 clients ({registered_clients}) registered")
break
for name, ws_ in clients:
await ws_.send(f"3 clients registered ({registered_clients})")
await ws_.send(f"Waiting for input")
for _ in range(len(clients)):
inp = await ws.recv()
print(ws.remote_address, inp)
This doesn't work neither, cause the loop doesn't break for all connected clients.
My questions:
- How can my examples be repaired?
- Is the
websockets
package the right framework for me? Two things let me think of that:- In case of sockets, I can bind one client to the socket instance and directly read from one instance (which it seems I cannot easily do here).
- Even though I'm pretty sure, that one can achieve what I want, it seems that there's a lot code involved for doing that (!?)
- But somehow I'd like to stick to websockets in general (because of also having the possibility to connect to communicate wit a browser). And sticking to the
websockets
package in particular, would have the advantage for me to get to know theasyncio
module better. But if someone says, that I should rather switch toaiohttp
,flask-sockets
,tornado
etc. I'd like to do so as well.