在本教程中,我们将使用 FastAPI 和 websockets 实现一个简易的聊天室应用程序。FastAPI 是一个高性能框架,主要用于构建 API,但您也可以使用它构建快速应用程序。
我们正在构建的应用程序只有一个页面,聊天页面又名 index.html,用户将在这里互相聊天。
设置项目
https://fastapi.tiangolo.com/,使用此链接设置 FastAPI 项目。您将需要 FastAPI 软件包。
pip install fastapi
pip install uvicorn
设置main.py
from fastapi import *
from fastapi.staticfiles import StaticFiles
from fastapi.templating import Jinja2Templates
import uvicorn
app = FastAPI()
app.mount("/static", StaticFiles(directory="static"), name="static")
templates = Jinja2Templates(directory = "templates")
@app.get('/')
def home(request: Request):
return templates.TemplateResponse("index.html", {"request": request})
if __name__ == "__main__":
return uvicorn.run("main:app")
为了设置文件系统,使“/”返回index.html,需创建一个“static”文件夹和“templates”文件夹。文件系统应遵循如下图所示的层次结构:
设置index.html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>WebSocket - client</title>
<style>
#messages .clientID {
font-weight: bold;
}
</style>
</head>
<body >
<main>
<div>
<h1>Chat App</h1>
<form id="room-form" class="mb-2">
<label for="room_id" class="text-slate-8000">Room id:</label>
<input type="text" id='room-id' class="border p-1" required>
<button type="submit" id="room-submit">Enter</button>
</form>
<div id="message-box">
<!-- Messages will be added here -->
</div>
<form id="message-form">
<div>
<input type="text" id="message" placeholder="Enter your message">
<button type="submit" text-slate-300 border-0 w-1/12 outline-0">Send</button>
</div>
</form>
</div>
</main>
我们唯一的样式是将 clientID 设为粗体,以便它看起来与实际消息不同。该文件的主要部分是两个 form 元素和 div 元素。
id 为“room-form”的<form>
元素允许用户输入聊天室 ID。它由“房间 id”输入字段的<label>
元素组成,该元素附带一个<input>
“文本”类型的字段(id 为“room-id”),供用户输入房间 ID。提供“提交”按钮(ID为“room-submit”)来提交表单。
在此表单下方,有一个<div>
id 为“message-box”的内容,其中将显示实际的聊天消息;在使用过程中,这将通过我们的 JS 代码填充消息。
再往下,还有另一个<form>
id 为“message-form”的元素,它允许用户输入和发送聊天消息。它包含一个<input>
“文本”类型的字段(ID 为“消息”),用户可以在其中键入消息。输入字段的右侧是“发送”按钮,用户可以单击该按钮来发送消息。
设置 main.js
在我的代码中,我只是在 index.html 中 body 元素末尾的 script 标签中加入了它,因为它的 Javascript 代码并不多。
const roomIDInput = document.getElementById("room-id");
const roomForm = document.getElementById('room-form')
const messageForm = document.getElementById("message-form");
const messageInput = document.getElementById("message");
const messageBox = document.getElementById("message-box");
const clientID = Date.now();
const ws;
前五行 JavaScript 代码通过 document.getElementById
获取带有相应 ID 的 HTML 元素。如果两个用户同时访问页面,就有可能分配到相同的const clientID
。这就是为什么这段代码最适合在本地主机上使用,而且只需学习 websockets 的逻辑即可。此外,我们还初始化了 ws
,稍后将为其分配一个 websocket。
function processMessage(event) {
const d = JSON.parse(event.data);
const messageEl = document.createElement("div");
messageEl.className = "w-full flex justify-start";
messageEl.innerHTML = `
<div>
<p>${d.userID}: ${d.msg}</p>
</div>`;
messageBox.appendChild(messageEl);
messageBox.scrollTop = messageBox.scrollHeight;
}
processMessage(event)
函数通过处理传入的消息在聊天应用程序中发挥着重要作用。首先,它会解析事件中的 JSON 数据,提取用户 ID 和消息内容。随后,它会创建一个新的 HTML div 元素来代表消息,并设置其类别以便格式化。在该元素中,动态插入用户 ID 和信息文本。构建好的消息 div 会附加到聊天界面的 messageBox
中,使其可见。
roomForm.addEventListener("submit", (e) => {
console.log("here")
e.preventDefault()
if (ws) {
ws.onmessage = processMessage
}
else{
roomID = roomIDInput.value;
ws = new WebSocket(`ws://localhost:8000/ws/${roomID}/${clientID}`)
console.log(`ws://localhost:8000/ws/${roomID}/${clientID}`)
ws.onmessage = processMessage
}
});
现在,我们为 roomForm
元素添加了一个事件监听器,用于捕捉表单提交。提交表单后,代码会检查 WebSocket 连接 ws
是否已存在。如果存在,就会分配 processMessage
函数来处理传入的聊天信息。但如果不存在 WebSocket 连接,它就会从输入字段中提取房间 ID,建立与特定房间的新 WebSocket 连接,并将其与唯一的客户端 ID 关联。此外,它还会记录 WebSocket 连接的详细信息,以便调试。在这两种情况下,代码都会确保阻止默认的表单提交行为,以便在聊天应用程序中进行房间输入和消息接收的客户端处理。
messageForm.addEventListener("submit", (event) => {
event.preventDefault();
const message = messageInput.value;
ws.send(JSON.stringify({ msg: message, userID: clientID }));
messageInput.value = "";
});
window.addEventListener("beforeunload", () => {
if (ws) {
ws.close();
}
});
最后,这段 JavaScript 代码为 messageForm
元素和窗口的 “beforeunload “事件添加了事件侦听器。当 messageForm
提交时(当用户发送聊天信息时),它首先会阻止默认的表单提交行为,然后从输入框中获取信息,连同 JSON 格式的客户端唯一 ID 一起发送到 WebSocket 服务器,并清除输入框。卸载前 “事件监听器可确保当用户离开或刷新页面时,如果存在 WebSocket 连接 (ws),该连接将被关闭以清理资源,从而帮助维护聊天系统的完整性并优雅地处理用户断开连接的情况。
完成服务器端(main.py)
class Room:
def __init__(self, room_id):
self.room = room_id
self.connections = []
async def broadcast(self, message, sender):
for connection in self.connections:
print(message)
await connection.send_text(message)
room_dict = {}
这里我们定义一个Room
类,用于管理聊天室中的房间。每个Room
实例初始化时都使用一个唯一的room_id
,它标识特定的聊天室,并调用一个空列表connections
来存储房间中客户端的 WebSocket 连接。Room
类中的broadcast
方法遍历连接列表,并将给所有连接的客户端发送message
,包括sender
. 此方法负责向聊天室内的所有用户广播消息。我们还初始化一个字典,当应用程序运行时,该字典将填充各个房间对象。
@app.websocket('/ws/{room_id}/{client_id}')
async def websocket_endpoint(websocket:WebSocket, room_id: str, client_id: str):
try:
await websocket.accept()
if room_id not in room_dict:
room_dict[room_id] = Room(room_id)
room = room_dict[room_id]
room.connections.append(websocket)
print(f"connection established for {client_id} in room {room_id}")
while True:
data = await websocket.receive_text()
await room.broadcast(data, websocket)
except WebSocketDisconnect:
if room_id not in room_dict:
room = room_dict[room_id]
room.connections.remove(websocket)
if len(room.connections) == 0:
del room_dict[room_id]
这里我们定义了一个 WebSocket 端点,用于在网络应用程序中管理聊天室功能。当客户端连接到该端点时,代码首先会接受 WebSocket 连接。它会检查 room_dict
中是否存在指定的聊天室,room_dict
是一个用于管理聊天室的字典。如果聊天室不存在,它就会创建一个新的聊天室类实例并将其添加到字典中。然后通过将 WebSocket 连接添加到相应房间实例的连接列表中,将其与聊天室关联起来。客户发送的信息会被连续接收,并通过房间实例的广播方法广播给同一聊天室中所有已连接的客户,确保实时通信。如果 WebSocket 断开连接,则会从连接列表中删除。如果断开连接后聊天室为空,则会从字典中删除,以保持聊天室管理系统的整洁和高效。
if __name__ == "__main__" :
uvicorn.run( "main:app" )
最后,该代码块检查脚本是否作为主程序运行,如果是,则使用 uvicorn
启动网络服务器,为变量名为 “app “的 “main “模块中定义的 FastAPI 应用程序提供服务。
结论
该程序制作了一个简单的聊天室应用程序,用户可以连接到不同的房间,并向特定房间中的所有用户发送消息。
本文来自作者投稿,版权归原作者所有。如需转载,请注明出处:https://www.nxrte.com/jishu/im/34523.html