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.

How Websockets Works Diagram


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.

Websockets vs HTTP diagram

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

Simple Echo Server Diagram


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.

Chicken Spotting diagram

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:

🔹Identify each client as soon as they connect
🔹Handle disconnections by cleaning up inactive connections
🔹Avoid unnecessary broadcasts, sending messages only to those who need them
🔹Standardize the message format, usually using JSON with type and data fields
🔹Create a clear protocol structure, to make maintenance and expansion easier
🔹Separate clients by room/session, especially in larger games (I still need to learn more about this part haha)

Image description

It's important to say that in low-resource environments, like embedded systems, I also learned that JSON is often replaced by fixed-size binary buffers -- a more efficient approach that requires well-defined protocols. This topic is super interesting and definitely deserves its own post. (Coming soon! 😄)

Today, I’m putting all this knowledge to work in my final university project: a multiplayer arcade platform where players use their phones to join games via QR Code. Everything communicates in real time using WebSockets -- from login to session control.

It’s a challenging and exciting project, and I’ll definitely be sharing more about it in future posts! Stay tuned!

🔵⚙️ PS: I added Megaman to cover the QR codes as a reminder: keep learning, keep evolving. Just like Megaman gains new powers with each challenge, we level up with every experiment.
Trust the Megaman Method™️!