python源码:基于fastapi+websocket双向信息通道的简易网页聊天室

前言

由于我的另一个想法,我需要使用双向通信,并最终选择了fastapi模块中的WebSocket方法来实现这个目的。

为了能够尽快掌握它,我设计了这个《基于fastapi+websocket双向信息通道的简易网页聊天室》,并且具备以下功能:

用户进入退出提示、发送及广播文字消息和图片,这两个主体功能

代码及图片

效果图

代码

0、安装模块

python 复制代码
pip install fastapi uvicorn

1、websocket_chatRoom.py

python 复制代码
import json

from fastapi import FastAPI, WebSocket, WebSocketDisconnect
from fastapi.middleware.cors import CORSMiddleware
from tools import ws_demo

app = FastAPI()
# 增加防跨域组件
origins = ['*']
app.add_middleware(
    CORSMiddleware,
    allow_origins=origins,
    allow_credentials=True,
    allow_methods=["*"],
    allow_headers=["*"],
)

manager = ws_demo.ConnectionManager()


@app.websocket("/ws/chat")
async def websocket_endpoint(websocket: WebSocket):
    await manager.connect(websocket)
    user_id = manager.active_connections[websocket]
    try:
        await manager.send_one_message(websocket, f'您的userid:{user_id}', user_id, False)
        while True:
            data = await websocket.receive_text()
            data = json.loads(data)
            message = data.get("message")
            is_image = data.get("is_image", False)
            await manager.send_message(message, user_id, is_image)
    except WebSocketDisconnect:
        await manager.disconnect(websocket, user_id)


if __name__ == '__main__':
    import uvicorn

    uvicorn.run(app, host="0.0.0.0", port=int(8848))

2、 ws_demo.py

python 复制代码
from fastapi import WebSocket
from typing import Dict
import uuid
import json
import base64


# 维护连接的 WebSocket 客户端列表
class ConnectionManager:
    def __init__(self):
        self.active_connections: Dict[WebSocket, str] = {}

    async def connect(self, websocket: WebSocket):
        user_id = str(uuid.uuid4())  # 生成一个唯一的匿名ID
        await websocket.accept()
        self.active_connections[websocket] = user_id
        # 广播新用户进入聊天室
        await self.broadcast_user_status("joined", websocket, user_id)

    async def disconnect(self, websocket: WebSocket, user_id):
        self.active_connections.pop(websocket, None)
        # 广播用户离开聊天室
        await self.broadcast_user_status("left", websocket, user_id)

    # 发送消息:文字或者图片
    async def send_message(self, message: str, user_id: str, is_image: bool = False):
        formatted_message = {
            'user': user_id,
            'message': message,
            'is_image': is_image
        }
        for connection, connection_id in self.active_connections.items():
            await connection.send_json(formatted_message)

    # 发送消息:文字或者图片
    async def send_one_message(self, websocket: WebSocket, message: str, user_id: str, is_image: bool = False):
        formatted_message = {
            'user': user_id,
            'message': message,
            'is_image': is_image
        }
        for connection, connection_id in self.active_connections.items():
            if connection == websocket:
                await connection.send_json(formatted_message)
                break

    async def broadcast_user_status(self, status: str, websocket, user_id):
        message = json.dumps({
            "user": user_id,
            "userStatus": status,
            "message": f"{user_id} {'已进入聊天室' if status == 'joined' else '已离线'}",
            "is_image": False
        })
        for connection in self.active_connections:
            if connection != websocket:
                await connection.send_text(message)

3、show.html

python 复制代码
<!DOCTYPE html>
<html>
<head>
    <title>WebSocket Chat</title>
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>WebSocket Chat</title>
    <style>
        body {
            font-family: Arial, sans-serif;
            background-color: #f4f4f4;
            margin: 0;
            padding: 20px;
        }

        h1 {
            text-align: center;
            color: #333;
            margin-bottom: 20px;
        }

        #messages {
            border: 1px solid #ccc;
            height: 500px;
            overflow-y: auto;
            padding: 10px;
            background-color: #fff;
            border-radius: 5px;
            box-shadow: 0 2px 5px rgba(0, 0, 0, 0.1);
            margin-bottom: 20px;  /* 添加底部间距 */
        }

        .message {
            margin: 10px 0;
        }

        .message img {
            max-width: 100%;
            border-radius: 5px;
        }

        #inputContainer {
            display: flex;
            flex-direction: column; /* 改为垂直排列 */
            gap: 10px; /* 添加间距 */
        }

        .inputRow {
            display: flex;
            align-items: center; /* 垂直居中 */
            gap: 10px; /* 添加间距 */
        }

        #messageInput {
            flex: 1;
            padding: 10px;
            border: 1px solid #ccc;
            border-radius: 5px;
            transition: border-color 0.3s;
        }

        #messageInput:focus {
            border-color: #5cb85c; /* 聚焦时边框颜色 */
            outline: none; /* 去掉默认聚焦轮廓 */
        }

        #sendButton, #sendImageButton {
            padding: 10px 15px;
            background-color: #5cb85c;
            color: white;
            border: none;
            border-radius: 5px;
            cursor: pointer;
            transition: background-color 0.3s;
        }

        #sendButton:hover, #sendImageButton:hover {
            background-color: #4cae4c;
        }

        #imageInput {
            padding: 10px;
        }

        #imageInput:hover {
            cursor: pointer; /* 鼠标悬停提示 */
        }
    </style>
    <script>
        document.addEventListener("DOMContentLoaded", function() {
            const socket = new WebSocket("ws://localhost:8848/ws/chat");
            const messages = document.getElementById("messages");
            const input = document.getElementById("messageInput");

            socket.onmessage = function(event) {
                const message = document.createElement("div");
                if (event.data.startsWith("{")) {
                    // 如果数据是 JSON 格式,则可能是特殊消息(例如系统通知)
                    const data = JSON.parse(event.data);
                    if (data.is_image) {
                        // 显示图片
                        message.textContent = data.user + ": ";
                        const imgElement = document.createElement("img");
                        imgElement.src = data.message;
                        message.appendChild(imgElement);
                    }
                    else {
                        message.textContent = data.user + ": " + data.message;
                    }
                    messages.appendChild(message);
                }
                messages.scrollTop = messages.scrollHeight;  // 自动滚动到最新消息
            };

            document.getElementById("sendButton").addEventListener("click", function() {
                const message = input.value;
                socket.send(JSON.stringify({message: message }));
                input.value = "";
            });

            document.getElementById("sendImageButton").addEventListener("click", function() {
            const fileInput = document.getElementById("imageInput");
            const file = fileInput.files[0];
            if (file) {
                const reader = new FileReader();
                reader.onload = function(e) {
                    const base64Image = e.target.result;
                    // 发送 Base64 编码的图片
                    socket.send(JSON.stringify({message: base64Image, is_image: true }));
                };
                reader.readAsDataURL(file);
            } else {
                alert("Please select an image to send.");
            }
        });
        });

    </script>
</head>
<body>
    <h1>Chat Room with Image Support</h1>
    <div id="messages"></div>
    <div id="inputContainer">
        <div class="inputRow">
            <input id="messageInput" type="text" placeholder="Type a message..." />
            <button id="sendButton">Send Text</button>
        </div>
        <div class="inputRow">
            <input id="imageInput" type="file" accept="image/*" />
            <button id="sendImageButton">Send Image</button>
        </div>
    </div>
</body>
</html>

4、项目结构

相关推荐
2401_8725149714 分钟前
代理IP设置后IP不变?可能的原因及解决方法
网络·网络协议·tcp/ip
Mr_wilson_liu14 分钟前
win10怎么配置dnat规则,访问win10的网口A ip的6443端口,映射到1.1.1.1的6443端口去
网络·网络协议·tcp/ip
汪先声17 分钟前
详解TCP的三次握手
网络·网络协议·tcp/ip
椰椰椰耶17 分钟前
【IP协议】解决 IP 地址不够用的问题(IP地址管理:动态分配、NAT、Ipv6)
网络·网络协议·tcp/ip
KookeeyLena526 分钟前
动态IP的最大更新频率
网络·网络协议·tcp/ip
Hello_WOAIAI2 小时前
ImportError: DLL load failed while importing _ssl: 找不到指定的模块的解决方法
网络·网络协议·ssl
Dragon_qu·x2 小时前
Certbot 生成 SSL 证书并配置自动续期
运维·网络协议·https·ssl
Bob99982 小时前
电脑浏览器访问华为路由器报错,无法访问路由器web界面:ERR_SSL_VERSION_OR_CIPHER_MISMATCH 最简单的解决办法!
开发语言·javascript·网络·python·网络协议·华为·ssl
kaixin_learn_qt_ing3 小时前
Qt与Udp
网络·网络协议·udp
Flying_Fish_roe4 小时前
Spring Boot-WebSocket相关问题
spring boot·后端·websocket