杰瑞科技汇

Python Websockets如何实现实时通信?

Of course! Here is a comprehensive guide to Python WebSockets, covering the basics, popular libraries, a practical example, and advanced topics.

What are WebSockets?

Imagine a standard HTTP request like a phone call:

  1. Client: "Hey server, give me the weather for Paris." (Request)
  2. Server: "Here's the weather for Paris." (Response)
  3. Connection is closed.

The connection is temporary. If the client wants an update, it has to make a new call. This is inefficient for things that need real-time updates, like chat apps or live dashboards.

WebSockets are like a persistent phone line:

  1. Client: "Hey server, I want to open a connection."
  2. Server: "Okay, the line is open. What do you need?"
  3. Client & Server: They can now talk back and forth instantly and asynchronously without the other party having to ask first. The connection stays open until either side decides to hang up.

This makes WebSockets perfect for:

  • Chat Applications (Slack, Discord)
  • Live Notifications (Facebook/Twitter notifications)
  • Real-Time Dashboards (Stock tickers, server monitoring)
  • Multiplayer Games
  • Collaborative Tools (Google Docs)

Key Python WebSocket Libraries

The Python ecosystem has two main contenders for WebSocket development:

Library Description Best For
websockets A lightweight, library for building WebSocket clients and servers. It's built on asyncio, making it very efficient for handling many concurrent connections. Simple to use, great for learning, and perfect for building custom, high-performance servers.
Django Channels An extension for the Django framework that adds WebSocket support to the traditional request-response cycle. It integrates seamlessly with Django's ORM, auth, and views. Django developers who want to add real-time features (like chat, notifications) to their existing Django applications.

We'll focus on the websockets library as it's the most fundamental and widely used.


Part 1: The websockets Library

Installation

First, install the library:

pip install websockets

Basic Concepts

  • asyncio: The websockets library is built on Python's asyncio framework for writing concurrent code using the async/await syntax. You'll need to understand basic asyncio concepts.
  • Server: Listens for incoming WebSocket connections, handles messages, and can send messages back.
  • Client: Connects to a WebSocket server, sends messages, and handles incoming messages.

Part 2: A Simple "Echo" Server and Client

Let's start with the classic "echo" example: the server sends back any message it receives.

The Server (server.py)

This server will listen on localhost port 8765. When a client connects, it will listen for messages and echo them back.

# server.py
import asyncio
import websockets
# This function will be called for each new client connection
async def handler(websocket, path):
    """
    Handle a WebSocket connection.
    'websocket' is the connection object for the specific client.
    'path' is the URL path for the connection.
    """
    print(f"New connection from: {websocket.remote_address}")
    try:
        # Keep listening for messages from this client
        async for message in websocket:
            print(f"Received message: {message}")
            # Echo the message back to the client
            await websocket.send(f"Echo: {message}")
    except websockets.exceptions.ConnectionClosed as e:
        print(f"Connection closed: {e}")
    finally:
        print(f"Connection closed for: {websocket.remote_address}")
# Start the WebSocket server
async def main():
    # host = "0.0.0.0" to make it accessible from other machines on the network
    host = "localhost"
    port = 8765
    async with websockets.serve(handler, host, port):
        print(f"Server started on ws://{host}:{port}")
        # Keep the server running forever
        await asyncio.Future()  # run_forever()
if __name__ == "__main__":
    asyncio.run(main())

The Client (client.py)

This client will connect to the server, send a few messages, and print the responses.

# client.py
import asyncio
import websockets
async def hello():
    uri = "ws://localhost:8765"  # The WebSocket server URI
    async with websockets.connect(uri) as websocket:
        print("Connected to the server.")
        # Send a message to the server
        name = "Alice"
        await websocket.send(f"Hello, my name is {name}!")
        print(f"Sent: Hello, my name is {name}!")
        # Wait for and print the server's response
        response = await websocket.recv()
        print(f"Received: {response}")
        # Send another message
        await websocket.send("How are you?")
        print("Sent: How are you?")
        response = await websocket.recv()
        print(f"Received: {response}")
if __name__ == "__main__":
    asyncio.run(hello())

How to Run It

  1. Open two terminal windows.
  2. In the first terminal, run the server:
    python server.py
    # Output: Server started on ws://localhost:8765
  3. In the second terminal, run the client:
    python client.py
    # Output:
    # Connected to the server.
    # Sent: Hello, my name is Alice!
    # Received: Echo: Hello, my name is Alice!
    # Sent: How are you?
    # Received: Echo: How are you?

You'll see the server's console printing the messages it receives.


Part 3: A More Practical Example - Simple Chat Server

Let's build a server that broadcasts messages to all connected clients.

The Chat Server (chat_server.py)

The key here is to maintain a list of all connected clients and send messages to all of them.

# chat_server.py
import asyncio
import websockets
import json
# A set to keep track of all connected clients
connected_clients = set()
async def chat_handler(websocket, path):
    """
    Handles a client connection for the chat room.
    """
    # Add the new client to the set
    connected_clients.add(websocket)
    try:
        # Notify everyone that a new user has joined
        join_message = {"type": "system", "message": f"A user joined the chat."}
        await broadcast(json.dumps(join_message))
        # Listen for messages from this client
        async for message in websocket:
            print(f"Received: {message}")
            # Broadcast the message to all other clients
            await broadcast(json.dumps({"type": "chat", "message": message}))
    except websockets.exceptions.ConnectionClosed:
        pass
    finally:
        # When the client disconnects, remove them from the set
        connected_clients.remove(websocket)
        # Notify everyone that a user has left
        leave_message = {"type": "system", "message": "A user left the chat."}
        await broadcast(json.dumps(leave_message))
async def broadcast(message):
    """
    Sends a message to all connected clients.
    """
    # Create a list of tasks to send to each client
    tasks = [client.send(message) for client in connected_clients]
    # Run all tasks concurrently
    await asyncio.gather(*tasks, return_exceptions=True)
async def main():
    async with websockets.serve(chat_handler, "localhost", 8765):
        print("Chat server started on ws://localhost:8765")
        await asyncio.Future()  # run_forever()
if __name__ == "__main__":
    asyncio.run(main())

The Chat Client (chat_client.py)

This client will connect and continuously listen for messages, printing them to the console. You'll need to run multiple instances of this to see the chat in action.

# chat_client.py
import asyncio
import websockets
import json
async def chat_client():
    uri = "ws://localhost:8765"
    async with websockets.connect(uri) as websocket:
        print("Connected to the chat server. Type a message and press Enter.")
        # Create a task to listen for messages from the server
        listen_task = asyncio.create_task(listen_for_messages(websocket))
        # Main loop to get user input from the console
        while True:
            message = await asyncio.get_event_loop().run_in_executor(None, input, "> ")
            if message.lower() == 'exit':
                break
            await websocket.send(message)
        # Cancel the listening task when the user exits
        listen_task.cancel()
        try:
            await listen_task
        except asyncio.CancelledError:
            pass
async def listen_for_messages(websocket):
    """
    A dedicated task to listen for and print messages from the server.
    """
    try:
        async for message in websocket:
            data = json.loads(message)
            if data["type"] == "system":
                print(f"\n[SYSTEM] {data['message']}")
            elif data["type"] == "chat":
                print(f"\n[CHAT] {data['message']}")
            print("> ", end="", flush=True) # Re-prompt the user
    except websockets.exceptions.ConnectionClosed:
        print("\nConnection to the server was closed.")
if __name__ == "__main__":
    asyncio.run(chat_client())

How to Run the Chat Example

  1. Open three terminal windows.
  2. In the first terminal, run the server:
    python chat_server.py
  3. In the second and third terminals, run the client:
    python chat_client.py
  4. Type messages in one client window and press Enter. You will see the message appear in the other client window almost instantly!

Part 4: Advanced Topics & Production Considerations

Authentication

How do you know who is connecting? A common pattern is:

  1. Client connects to the WebSocket.
  2. Server sends a "challenge" message (e.g., "Please provide your token").
  3. Client sends back its authentication token (e.g., {"token": "my_secret_jwt"}).
  4. Server validates the token. If valid, the connection proceeds. If not, it closes the connection.

Deployment

You cannot run a WebSocket server with a simple WSGI server like Gunicorn or uWSGI. You need an ASGI (Asynchronous Server Gateway Interface) server.

  • Daphne: The recommended ASGI server for Django Channels.
  • Hypercorn: A general-purpose ASGI server that works well with any ASGI app, including a raw websockets server.

Example with Hypercorn:

First, install it:

pip install hypercorn

Modify your server.py to be a proper ASGI app:

# server_asgi.py
import asyncio
import json
from websockets import serve
# ... (keep your chat_handler and broadcast functions from the chat example) ...
async def chat_handler(websocket, path):
    # ... (handler code) ...
# This is the ASGI application
async def app(scope, receive, send):
    if scope["type"] == "websocket":
        # Create a task for the handler
        await serve(chat_handler, "localhost", 8765)(scope, receive, send)
    else:
        # Handle HTTP requests if needed
        await send({
            "type": "http.response.start",
            "status": 404,
            "headers": [[b"content-type", b"text/plain"]],
        })
        await send({"type": "http.response.body", "body": b"Not found"})
if __name__ == "__main__":
    # Run with Hypercorn
    # hypercorn server_asgi:app --host 0.0.0.0 --port 8000
    print("To run this server, use: hypercorn server_asgi:app --host 0.0.0.0 --port 8000")

Then, run it from your terminal:

hypercorn server_asgi:app --host 0.0.0.0 --port 8000

Your client would then connect to ws://localhost:8000.

Scaling

A single Python process can handle thousands of connections, but for massive scale, you need to run multiple instances. This introduces the problem of how to get a message from one server instance to another. This is typically solved with a message broker like Redis or RabbitMQ, which acts as a central "pub/sub" (publish/subscribe) hub for your server instances.

分享:
扫描分享到社交APP
上一篇
下一篇