WebSocket Programming in Python: Basics and Beyond

Okay, here’s a comprehensive article on WebSocket Programming in Python, covering the basics and moving into more advanced concepts. I’ve tried to make it detailed and practical, with code examples and explanations.

WebSocket Programming in Python: Basics and Beyond

Introduction

WebSockets represent a significant advancement in web communication technology. Unlike the traditional HTTP request-response model, which is inherently unidirectional (client requests, server responds), WebSockets establish a persistent, bidirectional connection between a client (typically a web browser) and a server. This persistent connection allows for real-time data transfer in both directions, enabling applications like:

  • Live Chat Applications: Messages can be sent and received instantly without page reloads.
  • Online Multiplayer Games: Game state can be synchronized between players in real-time.
  • Financial Trading Platforms: Live price updates and trading signals can be delivered immediately.
  • Collaborative Editing Tools: Multiple users can see and edit a document simultaneously.
  • Real-time Dashboards and Monitoring Systems: System metrics and alerts can be displayed as they occur.
  • IoT (Internet of Things) Applications: Devices can communicate with a central server and each other continuously.

This article will guide you through the world of WebSocket programming in Python. We’ll start with the fundamental concepts, build a basic WebSocket server and client, and then delve into more advanced topics like handling connections, broadcasting messages, error handling, security, and scaling.

1. Understanding WebSockets

Before diving into code, let’s solidify our understanding of how WebSockets work.

1.1. The WebSocket Handshake

The WebSocket connection begins with an HTTP handshake. This is crucial for compatibility with existing web infrastructure. The client sends a standard HTTP request with a few special headers:

  • Upgrade: websocket: Indicates the client’s desire to upgrade the connection to a WebSocket.
  • Connection: Upgrade: Confirms the upgrade request.
  • Sec-WebSocket-Key: A Base64-encoded random value generated by the client. This is used for security and to prevent caching proxies from mistakenly treating the WebSocket connection as a regular HTTP connection.
  • Sec-WebSocket-Version: Specifies the WebSocket protocol version. The current standard version is 13.
  • Sec-WebSocket-Protocol (optional): Specifies one or more subprotocols the client wishes to use. The server will choose one (or none) and respond with the selected subprotocol.

The server, if it supports WebSockets and accepts the connection, responds with an HTTP 101 Switching Protocols status code and these headers:

  • Upgrade: websocket: Confirms the upgrade to WebSocket.
  • Connection: Upgrade: Confirms the upgrade.
  • Sec-WebSocket-Accept: A calculated value based on the client’s Sec-WebSocket-Key. The server takes the client’s key, appends a specific GUID (“258EAFA5-E914-47DA-95CA-C5AB0DC85B11”), calculates the SHA-1 hash of the result, and then Base64-encodes the hash. This helps ensure that the server is a genuine WebSocket server and not something else spoofing the handshake.
  • Sec-WebSocket-Protocol (optional): If the client specified subprotocols, the server indicates the chosen one here.

Once this handshake is complete, the HTTP connection is “upgraded” to a WebSocket connection. The underlying TCP connection remains open, and data can be sent in both directions.

1.2. Data Framing

Unlike HTTP, where data is exchanged in request and response messages, WebSocket data is transmitted in frames. Each frame has a specific structure:

  • FIN: A single bit indicating if this is the final frame in a message. Messages can be split across multiple frames.
  • RSV1, RSV2, RSV3: Reserved bits, usually 0 unless extensions are negotiated.
  • Opcode: Indicates the type of frame (e.g., text, binary, close, ping, pong). Common opcodes include:
    • 0x0 (Continuation Frame): Continues a fragmented message.
    • 0x1 (Text Frame): Contains text data (typically UTF-8 encoded).
    • 0x2 (Binary Frame): Contains binary data.
    • 0x8 (Close Frame): Initiates a connection closure.
    • 0x9 (Ping Frame): Sent by either party to check if the connection is alive.
    • 0xA (Pong Frame): Sent in response to a Ping frame.
  • Mask: A single bit indicating if the payload data is masked. All frames sent from a client to a server must be masked. This is a security measure to prevent malicious JavaScript from injecting crafted data that could be misinterpreted by intermediaries.
  • Payload Length: Indicates the length of the payload data.
  • Masking Key (if Mask bit is set): A 4-byte key used to mask the payload data. This key is randomly generated by the client for each frame.
  • Payload Data: The actual data being transmitted. If the Mask bit is set, this data is XORed with the Masking Key.

The server must unmask client frames, and the client must unmask server frames (though server frames are typically not masked). This framing mechanism allows for efficient and secure data transfer.

1.3. Closing the Connection

Either the client or the server can initiate a WebSocket connection closure. This is done by sending a Close frame (opcode 0x8). The Close frame can optionally include a status code and a reason for the closure. Common status codes include:

  • 1000: Normal closure.
  • 1001: Going away (e.g., browser tab closed, server shutting down).
  • 1002: Protocol error.
  • 1003: Unsupported data (e.g., server received a binary frame but only accepts text).
  • 1007: Invalid frame payload data (e.g., invalid UTF-8).
  • 1008: Policy violation.
  • 1009: Message too big.
  • 1011: Internal server error.

When one party sends a Close frame, the other party should respond with its own Close frame to acknowledge the closure. After both Close frames are exchanged, the underlying TCP connection is closed.

2. Basic WebSocket Server and Client in Python (using websockets library)

Python offers several libraries for WebSocket programming. A popular and well-maintained choice is the websockets library. It provides both server and client functionality and is built on top of asyncio, Python’s asynchronous programming framework.

2.1. Installation

First, install the websockets library:

bash
pip install websockets

2.2. A Simple Echo Server

Let’s create a basic WebSocket server that simply echoes back any message it receives from a client.

“`python
import asyncio
import websockets

async def echo(websocket):
async for message in websocket:
await websocket.send(message)

async def main():
async with websockets.serve(echo, “localhost”, 8765):
await asyncio.Future() # run forever

if name == “main“:
asyncio.run(main())
“`

Explanation:

  • import asyncio and import websockets: Import the necessary libraries.
  • async def echo(websocket):: This is the handler function that will be called for each new WebSocket connection. The websocket argument represents the connection to a specific client.
  • async for message in websocket:: This loop iterates over incoming messages from the client. The websockets library handles the framing and unmasking automatically.
  • await websocket.send(message):: Sends the received message back to the client.
  • async def main():: Defines the main asynchronous function.
  • async with websockets.serve(echo, "localhost", 8765):: Starts the WebSocket server.
    • echo: The handler function.
    • "localhost": The host to bind to.
    • 8765: The port to listen on.
    • The async with statement ensures the server is properly closed when the program exits.
  • await asyncio.Future(): This keeps the server running indefinitely. An asyncio.Future is a special object that represents a result that may not be available yet. In this case, we create a Future that never completes, effectively making the async with block run forever.
  • asyncio.run(main()): Starts the asyncio event loop and runs the main function.

2.3. A Simple WebSocket Client

Now, let’s create a client to connect to our echo server.

“`python
import asyncio
import websockets

async def hello():
uri = “ws://localhost:8765”
async with websockets.connect(uri) as websocket:
await websocket.send(“Hello server!”)
response = await websocket.recv()
print(f”Received: {response}”)

if name == “main“:
asyncio.run(hello())
“`

Explanation:

  • uri = "ws://localhost:8765": The WebSocket URI. Note the ws:// scheme (or wss:// for secure WebSockets).
  • async with websockets.connect(uri) as websocket:: Establishes a connection to the WebSocket server. The async with statement ensures the connection is properly closed.
  • await websocket.send("Hello server!"): Sends a message to the server.
  • response = await websocket.recv(): Receives a message from the server.
  • print(f"Received: {response}"): Prints the received message.

2.4. Running the Example

  1. Save: Save the server code as server.py and the client code as client.py.
  2. Run the Server: Open a terminal and run python server.py.
  3. Run the Client: Open another terminal and run python client.py.

You should see the client print “Received: Hello server!”, confirming that the server echoed back the message.

3. Handling Multiple Connections

The echo server example only handled one connection at a time. A real-world server needs to handle multiple concurrent connections. The websockets library makes this easy using asyncio.Task objects.

“`python
import asyncio
import websockets

connected = set() # Keep track of connected clients

async def handler(websocket):
connected.add(websocket)
try:
async for message in websocket:
print(f”Received from {websocket.remote_address}: {message}”)
for ws in connected: # Broadcast to all clients
if ws != websocket: # Don’t send back to the sender
await ws.send(message)
except websockets.exceptions.ConnectionClosedOK:
print(f”Connection with {websocket.remote_address} closed normally.”)
except websockets.exceptions.ConnectionClosedError:
print(f”Connection with {websocket.remote_address} closed with an error.”)
finally:
connected.remove(websocket)

async def main():
async with websockets.serve(handler, “localhost”, 8765):
await asyncio.Future()

if name == “main“:
asyncio.run(main())
“`

Explanation:

  • connected = set(): A set to store the currently connected WebSocket objects. Using a set ensures that each connection is stored only once and provides efficient membership testing.
  • connected.add(websocket): Adds the new connection to the set.
  • connected.remove(websocket): Removes the connection from the set when it closes.
  • for ws in connected:: This loop iterates through all connected clients.
  • if ws != websocket:: Prevents sending the message back to the client that sent it.
  • try...except...finally block: This is crucial for handling connection closures gracefully.
    • websockets.exceptions.ConnectionClosedOK: This exception is raised when the connection is closed cleanly (with a Close frame).
    • websockets.exceptions.ConnectionClosedError: This exception is raised if the connection is closed abruptly (e.g., network error).
    • finally: This block always executes, ensuring that the connection is removed from the connected set, even if an error occurs.

This updated server now broadcasts messages to all connected clients except the sender. You can test this by running multiple instances of the client.

4. Error Handling and Connection Management

Proper error handling and connection management are essential for robust WebSocket applications. We’ve already seen some basic error handling with ConnectionClosedOK and ConnectionClosedError. Let’s explore this further.

4.1. Handling Different Close Codes

You can inspect the close_code attribute of the ConnectionClosed exception (both ConnectionClosedOK and ConnectionClosedError are subclasses of ConnectionClosed) to determine why a connection was closed.

“`python
import asyncio
import websockets

async def handler(websocket):
try:
async for message in websocket:
await websocket.send(message)
except websockets.exceptions.ConnectionClosed as e:
print(f”Connection closed with code: {e.code}, reason: {e.reason}”)
if e.code == 1000:
print(“Normal closure.”)
elif e.code == 1001:
print(“Client went away.”)
# Handle other close codes as needed…

async def main():
async with websockets.serve(handler, “localhost”, 8765):
await asyncio.Future()

if name == “main“:
asyncio.run(main())

“`

4.2. Sending Close Frames

You can explicitly close a connection from the server side by sending a Close frame using websocket.close(). You can also provide a custom close code and reason.

“`python
import asyncio
import websockets

async def handler(websocket):
try:
async for message in websocket:
if message == “close”:
await websocket.close(code=1000, reason=”User requested closure”)
return #exit handler to avoid further processing
await websocket.send(message)
except websockets.exceptions.ConnectionClosed as e:
print(f”Closed: code={e.code}, reason={e.reason}”)
async def main():
async with websockets.serve(handler, “localhost”, 8765):
await asyncio.Future()

if name == “main“:
asyncio.run(main())
“`

In the client, you can close the connection with websocket.close() as well.

“`python

client.py

import asyncio
import websockets

async def hello():
uri = “ws://localhost:8765”
async with websockets.connect(uri) as websocket:
await websocket.send(“Hello server!”)
response = await websocket.recv()
print(f”Received: {response}”)
await websocket.send(“close”) #Request closure
# try to receive – will raise ConnectionClosed
try:
await websocket.recv()
except websockets.exceptions.ConnectionClosed as e:
print(f”Connection closed by server: code={e.code}, reason={e.reason}”)

if name == “main“:
asyncio.run(hello())
“`
4.3. Pings and Pongs (Keep-Alive)

WebSockets have a built-in mechanism for checking the health of a connection: pings and pongs. Either the client or the server can send a ping frame, and the other party should respond with a pong frame. This can be used to detect dead connections. The websockets library handles pings and pongs automatically by default, but you can customize the behavior.

“`python
import asyncio
import websockets
import time

async def handler(websocket):
try:
async for message in websocket:
await websocket.send(message)
except websockets.exceptions.ConnectionClosed as e:
print(f”Connection closed with code: {e.code}”)

async def main():
# Customize ping interval and timeout
async with websockets.serve(handler, “localhost”, 8765, ping_interval=5, ping_timeout=2):
await asyncio.Future()

if name == “main“:
asyncio.run(main())
``
In this server example:
* **
ping_interval=5:** Sends a ping every 5 seconds.
* **
ping_timeout=2`:** If a pong is not received within 2 seconds, the connection is considered dead and closed.

The client can also be configured to send pings and respond to pongs. By default, websockets.connect has ping_interval=20 and ping_timeout=20. If you set ping_interval to None, the client will not send pings, but will still respond to pings from the server.

4.4. Timeouts

You can set timeouts on various operations, such as receiving messages or the initial handshake. This can prevent your application from getting stuck waiting for a response that will never arrive.

“`python

Server-side handshake timeout

async def main():
async with websockets.serve(handler, “localhost”, 8765, open_timeout=5): # 5-second handshake timeout
await asyncio.Future()

Client-side receive timeout

async def client():
uri = “ws://localhost:8765″
async with websockets.connect(uri) as websocket:
try:
message = await asyncio.wait_for(websocket.recv(), timeout=10) # 10-second receive timeout
print(f”Received: {message}”)
except asyncio.TimeoutError:
print(“Receive timed out!”)

“`

5. Sending and Receiving Different Data Types

So far, we’ve been sending text messages. WebSockets also support sending binary data.

5.1. Sending Binary Data

To send binary data, you need to send a bytes object.

“`python

Server

async def handler(websocket):
await websocket.send(b”This is binary data!”)

Client

async def client():
uri = “ws://localhost:8765”
async with websockets.connect(uri) as websocket:
data = await websocket.recv()
if isinstance(data, bytes):
print(“Received binary data:”, data)
else:
print(“Received text data:”, data)

“`

5.2. Receiving Text or Binary Data

The websocket.recv() method can return either a str (for text frames) or a bytes object (for binary frames). You can use isinstance() to check the type.

5.3. JSON Data

A common practice is to send structured data as JSON. Python’s json module can be used to encode and decode JSON data.

“`python
import asyncio
import websockets
import json

Server

async def handler(websocket):
data = {“message”: “Hello from server!”, “count”: 42}
await websocket.send(json.dumps(data)) # Convert dict to JSON string

Client

async def client():
uri = “ws://localhost:8765″
async with websockets.connect(uri) as websocket:
response = await websocket.recv()
data = json.loads(response) # Convert JSON string to dict
print(f”Received: {data[‘message’]}, count: {data[‘count’]}”)
“`

6. Secure WebSockets (wss://)

For secure communication, use Secure WebSockets (wss://), which is the WebSocket equivalent of HTTPS. It uses TLS (Transport Layer Security) to encrypt the data transmitted between the client and the server.

6.1. Generating a Self-Signed Certificate (for development)

For testing purposes, you can create a self-signed certificate:

bash
openssl req -x509 -newkey rsa:4096 -keyout key.pem -out cert.pem -days 365 -nodes

This command generates two files: key.pem (the private key) and cert.pem (the certificate). Important: Self-signed certificates are not suitable for production environments. Browsers will display a warning because the certificate is not signed by a trusted Certificate Authority (CA).

6.2. Using the Certificate with websockets (Server)

“`python
import asyncio
import websockets
import ssl

async def handler(websocket):
async for message in websocket:
await websocket.send(message)

async def main():
ssl_context = ssl.SSLContext(ssl.PROTOCOL_TLS_SERVER)
ssl_context.load_cert_chain(“cert.pem”, “key.pem”)

async with websockets.serve(handler, "localhost", 8765, ssl=ssl_context):
    await asyncio.Future()

if name == “main“:
asyncio.run(main())
“`

Explanation:

  • import ssl: Imports the ssl module.
  • ssl_context = ssl.SSLContext(ssl.PROTOCOL_TLS_SERVER): Creates an SSL context for a server.
  • ssl_context.load_cert_chain("cert.pem", "key.pem"): Loads the certificate and private key.
  • ssl=ssl_context: Passes the SSL context to websockets.serve.

6.3. Connecting to a Secure WebSocket (Client)
For a self-signed certificate, the client needs to be told to either accept the certificate or you have to provide the certificate.

“`python

client.py – trusting any certificate (don’t do this in production!)

import asyncio
import websockets
import ssl

async def hello():
uri = “wss://localhost:8765”
# Disable certificate verification (DANGEROUS – for testing only!)
ssl_context = ssl.SSLContext(ssl.PROTOCOL_TLS_CLIENT)
ssl_context.check_hostname = False
ssl_context.verify_mode = ssl.CERT_NONE

async with websockets.connect(uri, ssl=ssl_context) as websocket:
    await websocket.send("Hello secure server!")
    response = await websocket.recv()
    print(f"Received: {response}")

if name == “main“:
asyncio.run(hello())
python

client.py – using the certificate authority

import asyncio
import websockets
import ssl

async def hello():
uri = “wss://localhost:8765″
# Use the certificate authority
ssl_context = ssl.create_default_context(cafile=”cert.pem”) # Path to server’s public certificate

async with websockets.connect(uri, ssl=ssl_context) as websocket:
    await websocket.send("Hello secure server!")
    response = await websocket.recv()
    print(f"Received: {response}")

if name == “main“:
asyncio.run(hello())
“`
Explanation (trust any):

  • ssl_context = ssl.SSLContext(ssl.PROTOCOL_TLS_CLIENT): Creates an SSL context for a client.
  • ssl_context.check_hostname = False: Disables hostname verification. This is dangerous and should only be used for testing with self-signed certificates.
  • ssl_context.verify_mode = ssl.CERT_NONE: Disables certificate verification. Again, this is dangerous and for testing only.
  • ssl=ssl_context: Passes the SSL context to websockets.connect.

Explanation (certificate authority):
* ssl_context = ssl.create_default_context(cafile="cert.pem"): Creates an SSL context for a client and specifies the certificate authority file.

Important: In a production environment, you should obtain a certificate from a trusted CA (like Let’s Encrypt) and configure your server and client to use it properly. The client should always verify the server’s certificate to prevent man-in-the-middle attacks.

7. Subprotocols

WebSocket subprotocols allow you to define application-level protocols on top of the raw WebSocket connection. This can be useful for structuring your communication and ensuring that both the client and server understand the format of the messages.

7.1. Specifying Subprotocols (Client)

The client can specify a list of desired subprotocols in the subprotocols argument to websockets.connect.

python
async def client():
uri = "ws://localhost:8765"
async with websockets.connect(uri, subprotocols=["my-protocol", "another-protocol"]) as websocket:
print(f"Negotiated subprotocol: {websocket.subprotocol}")
# ...

7.2. Choosing a Subprotocol (Server)

The server can choose one of the subprotocols offered by the client. The websockets.serve function accepts a subprotocols argument as well. If you provide this argument, websockets will automatically handle the subprotocol negotiation.

“`python
async def handler(websocket):
print(f”Negotiated subprotocol: {websocket.subprotocol}”)
async for message in websocket:
await websocket.send(message)

async def main():
async with websockets.serve(handler, “localhost”, 8765, subprotocols=[“my-protocol”]):
await asyncio.Future()
``
If the client requests
[“my-protocol”, “another-protocol”]and the server is configured withsubprotocols=[“my-protocol”], thenwebsocket.subprotocolwill be“my-protocol”on both the client and server. If there's no overlap,websocket.subprotocolwill beNone`. If the server doesn’t specify any supported subprotocols, then it will accept any subprotocol the client offers (or no subprotocol).

8. Scaling WebSocket Applications

Scaling WebSocket applications presents unique challenges compared to traditional HTTP applications. Because WebSocket connections are persistent, you can’t simply add more servers behind a load balancer and expect it to work seamlessly. Here’s a breakdown of the key considerations and techniques:

8.1. The Challenge of Persistent Connections

  • Statefulness: WebSocket servers are inherently stateful. They maintain an open connection with each client, potentially storing information about that client’s state.
  • Load Balancer Limitations: Traditional HTTP load balancers work on a request-by-request basis. They distribute incoming HTTP requests to different backend servers. With WebSockets, the initial handshake goes through the load balancer, but subsequent WebSocket messages bypass it, going directly to the server that established the connection. This means adding more servers behind a standard load balancer won’t automatically increase your WebSocket capacity.
  • Connection Limits: Each server has a finite number of file descriptors (and thus, a limit on the number of concurrent TCP connections it can handle). You need to consider these limits when scaling.

8.2. Scaling Strategies

Here are several strategies for scaling WebSocket applications:

  • Vertical Scaling (Bigger Servers): The simplest approach is to increase the resources (CPU, RAM, network bandwidth) of your existing server. This is often the easiest option initially, but it has limitations – there’s a maximum size a single server can reach.
  • Horizontal Scaling (More Servers): This involves adding more servers to handle the load. This is where things get more complex with WebSockets.
  • Sticky Sessions (with Load Balancer): Some load balancers support “sticky sessions,” also known as session affinity. This means that once a client connects to a particular server, all subsequent traffic from that client is routed to the same server. This can work for WebSockets, but it has drawbacks:
    • Uneven Load Distribution: If one server gets more initial connections, it might become overloaded while others are underutilized.
    • Server Failure: If a server goes down, all clients connected to it will lose their connections.
    • Limited Scalability: You’re still limited by the capacity of individual servers.
  • Connection Migration (Advanced): This is a more sophisticated approach where the load balancer can migrate existing WebSocket connections from one server to another. This is complex to implement and requires specialized load balancing solutions.
  • Message Broker (Recommended for Most Cases): This is the most robust and scalable approach for most WebSocket applications. It involves using a message broker (like Redis, RabbitMQ, Kafka) to decouple the WebSocket connections from the application logic.

8.3. Message Broker Architecture

Here’s how the message broker approach works:

  1. Client Connects: Clients connect to any available WebSocket server.
  2. Server Subscribes: Each WebSocket server subscribes to a specific channel or topic on the message broker. This channel represents a group of clients that should receive the same messages (e.g., all users in a chat room).
  3. Client Sends Message: When a client sends a message, the WebSocket server publishes that message to the appropriate channel on the message broker.
  4. Message Broker Broadcasts: The message broker delivers the message to all servers subscribed to that channel.
  5. Server Sends to Clients: Each WebSocket server receives the message from the message broker and sends it to the relevant clients connected to it.

Benefits of Message Broker:

  • Scalability: You can easily add more WebSocket servers or message broker instances to handle increasing load.
  • Resilience: If a WebSocket server fails, clients can reconnect to a different server, and the message broker ensures they still receive messages.
  • Decoupling: The WebSocket servers are no longer directly responsible for managing all client connections. They just handle the WebSocket protocol and interact with the message broker.
  • Simplified Logic: The application logic for broadcasting messages becomes much simpler.

Example using Redis as a Message Broker:

First, install the aioredis library:

bash
pip install aioredis

Then you can modify the broadcast server example to use it:

“`python
import asyncio
import websockets
import aioredis
import json

async def handler(websocket, redis):
try:
# Subscribe to a Redis channel
channel = “chat_channel” # Or a unique channel per room/group
await redis.subscribe(channel)
# Handle incoming messages and publish to Redis
async for message in websocket:
print(f”Received from {websocket.remote_address}: {message}”)
await redis.publish(channel, json.dumps({“sender”: str(websocket.remote_address), “message”: message}))

except websockets.exceptions.ConnectionClosedOK:
    print(f"Connection with {websocket.remote_address} closed normally.")
except websockets.exceptions.ConnectionClosedError:
    print(f"Connection with {websocket.remote_address} closed with an error.")
except Exception as e:
    print(f"An error occurred: {e}")
finally:
    await redis.unsubscribe(channel) #Important to unsubscribe.

async def reader(redis,websocket_server):
while True:
try:
message = await redis.get_message(ignore_subscribe_messages=True)
if message is not None and message[‘type’] == ‘message’:
data = json.loads(message[‘data’])
for ws in websocket_server.websockets:
if str(ws.remote_address) != data[‘sender’]: # Don’t send to sender.
await ws.send(data[‘message’])

    except Exception as e:
        print(f"Redis reader error: {e}")
        break # Break the loop on error - in a real app, consider reconnection logic

async def main():
# Connect to Redis
redis = await aioredis.from_url(“redis://localhost”)

# Start WebSocket server
async with websockets.serve(lambda ws: handler(ws, redis), "localhost", 8765) as websocket_server:
    # Start Redis message reader
    asyncio.create_task(reader(redis,websocket_server))
    await asyncio.Future()

if name == “main“:
asyncio.run(main())
“`

Explanation:

  • aioredis: Used for asynchronous communication with Redis.
  • handler(websocket, redis): The WebSocket handler now takes a redis connection as an argument.
  • redis.subscribe(channel): Subscribes to a Redis channel.
  • redis.publish(channel, message): Publishes messages to the Redis channel.
  • reader(redis,websocket_server): This function continuously reads messages from the Redis channel and forwards them to connected WebSockets. It filters out messages from the same client.
  • asyncio.create_task(reader(redis,websocket_server)): Runs the Redis reader concurrently with the WebSocket server.

8.4. Choosing a Message Broker

Several message brokers are suitable for WebSocket scaling:

  • Redis: A popular in-memory data store that also offers Pub/Sub functionality. It’s fast and relatively easy to set up.
  • RabbitMQ: A more robust and feature-rich message broker that supports various messaging patterns.
  • Kafka: A distributed streaming platform designed for high-throughput, fault-tolerant messaging. Often used for large-scale applications.

The best choice depends on your specific requirements and the complexity of your application. Redis is often a good starting point for simpler applications, while RabbitMQ or Kafka might be better for more complex scenarios.

9. Advanced Topics and Best Practices

9.1. Compression (permessage-deflate)

The permessage-deflate extension allows for compressing WebSocket messages, reducing bandwidth usage. The websockets library supports this extension.

“`python

Server-side

async with websockets.serve(handler, “localhost”, 8765, compression=”permessage-deflate”):
# …

Client-side

async with websockets.connect(uri, compression=”permessage-deflate”) as websocket:
# …
“`

9.2. Fragmentation

Large messages can be split into multiple frames (fragmentation). The websockets library handles fragmentation automatically, but it’s good to be aware of it. You can control the maximum size of a message using the max_size parameter in websockets.serve and websockets.connect. The default is 1MiB.

9.3. Backpressure

Backpressure occurs when a sender is sending data faster than the receiver can process it. The websockets library implements backpressure mechanisms to help prevent this. If the send buffer fills up, websocket.send() will await until there’s space in the buffer.

9.4. Rate Limiting

You might want to implement rate limiting to prevent abuse or to control the flow of messages. You can do this

Leave a Comment

Your email address will not be published. Required fields are marked *

Scroll to Top