Skip to main content

Python 网络编程

Python WebSocket 编程

WebSocket 是一种在单个 TCP 连接上进行全双工通信的协议,允许客户端和服务器之间实时、双向、持久的通信。它基于 HTTP 协议,通过一次握手(HTTP Upgrade 请求)建立连接,随后双方可以随时发送数据,无需像 HTTP 那样每次都发起新请求。WebSocket 适用于需要低延迟、实时交互的应用,如聊天室、实时通知、在线游戏等。

核心特点

  • 全双工:客户端和服务器可同时发送和接收消息。
  • 持久连接:连接建立后保持开放,直到一方主动关闭。
  • 低开销:相比 HTTP 的轮询,WebSocket 减少了协议头和连接建立的开销。
  • 事件驱动:通过事件(如 onopen, onmessage, onclose, onerror)处理连接状态和数据。

编程要点

  • 握手:客户端发起 WebSocket 请求,服务器响应后建立连接。
  • 消息处理:监听消息事件,处理文本或二进制数据。
  • 连接管理:处理连接断开、错误重试和心跳机制以保持连接活跃。
  • 协议:URL 使用 ws:// 或 wss://(加密),支持子协议(如 chat 或 json)。

fastapi 编写聊天室

编写服务端代码 server.py

服务端安装


pip install fastapi "uvicorn[standard]" 

服务端代码

import asyncio
from fastapi import FastAPI, WebSocket, WebSocketDisconnect
from fastapi.responses import HTMLResponse
import logging

app = FastAPI()

# 配置日志
logging.basicConfig(level=logging.INFO)
logger = logging.getLogger("chat_server")

# 存储已连接的客户端和用户名
connected_clients = {}
usernames = set()

async def broadcast_message(message: str):
    """向所有已连接的客户端广播消息"""
    for connection in connected_clients.values():
        await connection.send_text(message)

@app.get("/", response_class=HTMLResponse)
async def get_chat_page():
    """提供聊天室网页"""
    with open("index.html") as f:
        return f.read()

@app.websocket("/ws")
async def websocket_endpoint(websocket: WebSocket):
    await websocket.accept()
    username = websocket.query_params.get('username')
    if not username:
        await websocket.send_text("Username is required.")
        await websocket.close()
        return
    if username in usernames:
        await websocket.send_text("Username already taken.")
        await websocket.close()
        return
    usernames.add(username)
    connected_clients[username] = websocket
    await websocket.send_text(f"Welcome, {username}!")
    await broadcast_message(f"System: {username} has joined the chat")
    try:
        while True:
            message = await websocket.receive_text()
            await broadcast_message(f"{username}: {message}")
    except WebSocketDisconnect:
        del connected_clients[username]
        usernames.remove(username)
        await broadcast_message(f"System: {username} has left the chat")

if __name__ == "__main__":
    import uvicorn
    uvicorn.run(app, host="127.0.0.1", port=8000)
    

客户端代码

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Chat Room</title>
    <style>
        #chat-area {
            height: 300px;
            overflow-y: scroll;
            border: 1px solid #ccc;
            padding: 10px;
        }
    </style>
</head>
<body>
<div>
    <input type="text" id="username" placeholder="Username">
    <button id="connect">Connect</button>
</div>
<div id="chat-area"></div>
<div>
    <input type="text" id="message" placeholder="Type a message" disabled>
    <button id="send" disabled>Send</button>
</div>
<script>
    const usernameInput = document.getElementById('username');
    const connectButton = document.getElementById('connect');
    const messageInput = document.getElementById('message');
    const sendButton = document.getElementById('send');
    const chatArea = document.getElementById('chat-area');

    let ws;
    let connected = false;

    connectButton.addEventListener('click', () => {
        const username = usernameInput.value.trim();
        if (!username) {
            alert('Please enter a username');
            return;
        }
        connectButton.disabled = true;
        const wsUrl = `ws://127.0.0.1:8000/ws?username=${encodeURIComponent(username)}`;
        ws = new WebSocket(wsUrl);

        ws.onopen = () => {
            // 等待欢迎消息
        };

        ws.onmessage = (event) => {
            const message = event.data;
            if (message === "Username already taken.") {
                alert('Username already taken. Please choose another.');
                connectButton.disabled = false;
            } else if (message.startsWith("Welcome, ")) {
                connected = true;
                messageInput.disabled = false;
                sendButton.disabled = false;
                appendMessage(message);
            } else {
                appendMessage(message);
            }
        };

        ws.onclose = () => {
            if (connected) {
                appendMessage("Disconnected from chat");
            }
            connected = false;
            messageInput.disabled = true;
            sendButton.disabled = true;
            connectButton.disabled = false;
        };

        ws.onerror = (error) => {
            console.error('WebSocket error:', error);
        };
    });

    sendButton.addEventListener('click', () => {
        const message = messageInput.value.trim();
        if (message && connected) {
            ws.send(message);
            messageInput.value = '';
        }
    });

    function appendMessage(message) {
        const p = document.createElement('p');
        p.textContent = message;
        chatArea.appendChild(p);
        chatArea.scrollTop = chatArea.scrollHeight;
    }
</script>
</body>
</html>

聊天室使用方法

  • 先运行服务端 server.py 确保8000端口正常监听
  • 浏览器打开 client.html ,输入用户名点击 connect,然后就可以发送消息了

yuziyue加入聊天

后来 easy 用户也加入了聊天