Wired with WebSockets: Experiments in Real-Time Multiplayer Communication
One of the coolest things I’ve learned on my journey as a developer was understanding how real-time communication works in games. For a long time, it felt distant, complicated, and really hard to understand. But that started to change when I joined the Systems Analysis and Development graduation course at IFPI. Throughout the classes and projects, I began to understand how everything works and started seeing WebSockets differently -- as an accessible, powerful, and incredibly fun tool to work with. In this post, I want to walk you through that journey in a simple and practical way, starting from the basics and showing how we can implement this kind of communication in games or other real-world applications. WebSockets vs HTTP One of the main advantages of WebSockets over traditional HTTP is asynchronous and bidirectional communication. It's bidirectional because both the client and the server can "start a conversation," and asynchronous because messages don’t have to follow a strict request-response pattern. With HTTP, for example, a game would need to keep polling constantly (asking "hey, is there anything new?"), which is pretty inefficient. But with WebSockets, a single persistent connection is enough, and the server can notify when something new happens. WebSocket connections actually start as regular HTTP requests -- the initial handshake is HTTP, and then the connection is upgraded to a WebSocket. So in a way, it's like an improved version of HTTP, designed to support real-time communication. In a WebSocket network, all messages go through the server, which can process, validate, and route them as needed. WebSockets also allow either party to close the connection, which is why it's common to have specific methods to handle these events (like reconnections or state cleanup). Starting with an Echo Server The first thing I did was implement an Echo Server using Python. This is one of the simplest ways to experiment with WebSockets: the server just sends back to all clients any message it receives. import asyncio import websockets # Set to store active connections connected_clients = set() async def echo(websocket, path): # Add the connected client to the set connected_clients.add(websocket) try: async for message in websocket: # Send the received message to all other connected clients for client in connected_clients: if client != websocket: # Avoid sending it back to the sender await client.send(message) finally: # Remove the disconnected client from the set connected_clients.remove(websocket) async def main(): # Set up the WebSocket server async with websockets.serve(echo, "localhost", 8765): await asyncio.Future() # Keep the server running asyncio.run(main()) This setup uses two important Python libraries: asyncio and websockets. Asyncio is a Python little library that helps handle asynchronous tasks, which means running multiple operations at the same time efficiently, without blocking the program. Websockets is another little library that implements the WebSocket protocol, allowing real-time communication between clients and servers. We use it to create a persistent communication channel without the need to establish new connections each time. In this code block above, we set up the server to call the echo function whenever a new client connects. This function listens for messages and echoes them to all other connected clients. Pretty useful haha Creating a WebSocket Game in the Browser My second experiment was connecting multiple people to the same game through their browsers. Each player would access a page on their phone and could join the game in real time. At this point, I started using JSON messages, which are very common in the web world. Each message sent had a type and some data. For example, when a new player joined the game: { "type": "playerJoined", "data": {"name": "Patrocinio", "iconIndex": 3} } This type and data message structure allows you to create a custom protocol -- it's like inventing a mini-language between the client and the server. The server would then handle the message based on its type and send updates to the other players. This kind of structure helped me better organize game logic and understand how to build personalized protocols on top of WebSocket. It's a powerful approach, because both client and server speak the same "language", based on standardized message formats. Every time a new game feature was added, I just needed to define a new type and its corresponding handler. In this case, the game was played on a TV, while players used their phones as controllers. Each phone (via browser) would send information through WebSocket to the game running on the TV. However, notice that in this particular scenario, it wasn’t important for all clients (the players) to kn

One of the coolest things I’ve learned on my journey as a developer was understanding how real-time communication works in games. For a long time, it felt distant, complicated, and really hard to understand.
But that started to change when I joined the Systems Analysis and Development graduation course at IFPI. Throughout the classes and projects, I began to understand how everything works and started seeing WebSockets differently -- as an accessible, powerful, and incredibly fun tool to work with.
In this post, I want to walk you through that journey in a simple and practical way, starting from the basics and showing how we can implement this kind of communication in games or other real-world applications.
WebSockets vs HTTP
One of the main advantages of WebSockets over traditional HTTP is asynchronous and bidirectional communication. It's bidirectional because both the client and the server can "start a conversation," and asynchronous because messages don’t have to follow a strict request-response pattern.
With HTTP, for example, a game would need to keep polling constantly (asking "hey, is there anything new?"), which is pretty inefficient. But with WebSockets, a single persistent connection is enough, and the server can notify when something new happens.
WebSocket connections actually start as regular HTTP requests -- the initial handshake is HTTP, and then the connection is upgraded to a WebSocket. So in a way, it's like an improved version of HTTP, designed to support real-time communication.
In a WebSocket network, all messages go through the server, which can process, validate, and route them as needed. WebSockets also allow either party to close the connection, which is why it's common to have specific methods to handle these events (like reconnections or state cleanup).
Starting with an Echo Server
The first thing I did was implement an Echo Server using Python. This is one of the simplest ways to experiment with WebSockets: the server just sends back to all clients any message it receives.
import asyncio
import websockets
# Set to store active connections
connected_clients = set()
async def echo(websocket, path):
# Add the connected client to the set
connected_clients.add(websocket)
try:
async for message in websocket:
# Send the received message to all other connected clients
for client in connected_clients:
if client != websocket: # Avoid sending it back to the sender
await client.send(message)
finally:
# Remove the disconnected client from the set
connected_clients.remove(websocket)
async def main():
# Set up the WebSocket server
async with websockets.serve(echo, "localhost", 8765):
await asyncio.Future() # Keep the server running
asyncio.run(main())
This setup uses two important Python libraries: asyncio and websockets.
Asyncio is a Python little library that helps handle asynchronous tasks, which means running multiple operations at the same time efficiently, without blocking the program.
Websockets is another little library that implements the WebSocket protocol, allowing real-time communication between clients and servers. We use it to create a persistent communication channel without the need to establish new connections each time.
In this code block above, we set up the server to call the echo function whenever a new client connects. This function listens for messages and echoes them to all other connected clients. Pretty useful haha
Creating a WebSocket Game in the Browser
My second experiment was connecting multiple people to the same game through their browsers. Each player would access a page on their phone and could join the game in real time.
At this point, I started using JSON messages, which are very common in the web world. Each message sent had a type and some data. For example, when a new player joined the game:
{
"type": "playerJoined",
"data": {"name": "Patrocinio", "iconIndex": 3}
}
This type and data message structure allows you to create a custom protocol -- it's like inventing a mini-language between the client and the server. The server would then handle the message based on its type and send updates to the other players. This kind of structure helped me better organize game logic and understand how to build personalized protocols on top of WebSocket.
It's a powerful approach, because both client and server speak the same "language", based on standardized message formats. Every time a new game feature was added, I just needed to define a new type and its corresponding handler.
In this case, the game was played on a TV, while players used their phones as controllers. Each phone (via browser) would send information through WebSocket to the game running on the TV.
However, notice that in this particular scenario, it wasn’t important for all clients (the players) to know when others joined the game -- that information only mattered to the main game instance (which was also a WebSocket client).
Since I didn’t yet know how to segment messages for different types of clients, the server echoed everything to everyone. That meant all players were receiving unnecessary data, which caused a small waste of bandwidth and processing. Even so, the server worked well for its experimental purpose -- and the learning experience was what truly mattered.
Evolving with WebSockets: Identification, Best Practices, and My Final Project
Over time, I started facing more complex challenges -- like distinguishing between different types of connected devices (phones vs. the game on the TV), identifying clients right after connection, handling disconnections, and avoiding unnecessary broadcasts. I also began adopting some best practices that remain essential in the projects I work on today: