Python multiplayer game with websockets: Handling multiple clients
Asked Answered
G

0

7

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 the asyncio module better. But if someone says, that I should rather switch to aiohttp, flask-sockets, tornado etc. I'd like to do so as well.
Goines answered 22/12, 2019 at 13:55 Comment(1)
Out of curiosity, did you try to solved using a single transaction ID on the client side? Letting the server "publish" to all clients but clients would "consume" only message from server using their ID?Physoclistous

© 2022 - 2024 — McMap. All rights reserved.