WebSocket 创建聊天应用中的房间和消息持久化

离开教师岗位之前,我注意到有一大批学生在数字交流方面遇到困难。受此启发,我创建了一个聊天应用程序,它可以存储信息,供教师日后查看,以便向学生提供反馈或评分。由于我们的交流越来越数字化,越来越随意,孩子们必须根据不同的情况主动学习正确和错误的打字方式。在大多数情况下,备忘录并不能帮助他们找到工作。

什么是 WebSockets?

WebSockets 本质上是一种升级版的互联网通信方法。它在客户端和服务器之间提供实时、事件驱动和持久的连接。WebSocket 连接非常适合实时消息传递和多人游戏。引用 MDN 的介绍:

WebSocket API 是一种先进的技术,可以在用户浏览器和服务器之间开启双向交互式通信会话。利用该 API,您可以向服务器发送消息并接收事件驱动的响应,而无需轮询服务器以获得回复。

想想升级后的互联网连接。WebSockets 已有十多年的历史,通过各种辅助工具,它变得非常容易使用。就我的聊天应用程序而言,我希望存储用户发送的信息,以便日后查看。我还希望为特定用户创建独立的聊天室,以便教师创建。因此,我在客户端使用了 Socket.IO,在服务器使用了 Flask-SocketIO。

WebSocket 创建聊天应用中的房间和消息持久化

聊天应用程序基础

市面上有很多资源可用于设置聊天应用程序的基本功能,如用户加入或离开时的提醒以及来回发送消息。我将提供我认为使用 Socket.IO 和 Flask-SocketIO 启动聊天应用程序的两个最佳资源。

房间

WebSocket 房间是客户可以加入或退出的虚拟通道。这些房间允许向房间内的所有客户端发送消息,而无需向所有连接的客户端广播。因此,在我的聊天应用程序中,教师应该能够为学生群组创建房间,这样只有指定的学生群组才能看到消息。

下面是使用 Flask-SocketIO 实现房间的基本方法:

from flask import Flask, render_template
from flask_socketio import SocketIO, join_room, leave_room, send

app = Flask(__name__)
app.config['SECRET_KEY'] = 'secret!'
socketio = SocketIO(app)

@app.route('/')
def index():
    return render_template('index.html')

@socketio.on('join')
def handle_join(data):
    room = data['room']
    join_room(room)
    send(f'{data["username"]} has entered the room {room}.', to=room)

@socketio.on('leave')
def handle_leave(data):
    room = data['room']
    leave_room(room)
    send(f'{data["username"]} has left the room {room}.', to=room)

@socketio.on('message')
def handle_message(data):
    room = data['room']
    send(data['message'], to=room)

if __name__ == '__main__':
    socketio.run(app)

本例中定义了三个 WebSocket 事件:joinleavemessagejoin 事件将用户添加到指定的房间,leave 事件将用户从房间移除,而 message 事件则向指定房间的所有客户端发送一条消息。

下面是与Socket.IO对应的客户端:

<!DOCTYPE html>
<html>
<head>
    <title>WebSocket Rooms</title>
    <script src="https://cdn.socket.io/4.0.0/socket.io.min.js"></script>
    <script>
        document.addEventListener("DOMContentLoaded", () => {
            const socket = io();

            document.getElementById('join-btn').addEventListener('click', () => {
                const room = document.getElementById('room').value;
                const username = document.getElementById('username').value;
                socket.emit('join', { room: room, username: username });
            });

            document.getElementById('leave-btn').addEventListener('click', () => {
                const room = document.getElementById('room').value;
                const username = document.getElementById('username').value;
                socket.emit('leave', { room: room, username: username });
            });

            document.getElementById('send-btn').addEventListener('click', () => {
                const room = document.getElementById('room').value;
                const message = document.getElementById('message').value;
                socket.emit('message', { room: room, message: message });
            });

            socket.on('message', function(msg) {
                const messages = document.getElementById('messages');
                const messageElement = document.createElement('li');
                messageElement.textContent = msg;
                messages.appendChild(messageElement);
            });
        });
    </script>
</head>
<body>
    <h1>WebSocket Rooms</h1>
    <input type="text" id="username" placeholder="Username">
    <input type="text" id="room" placeholder="Room">
    <button id="join-btn">Join Room</button>
    <button id="leave-btn">Leave Room</button>
    <br>
    <input type="text" id="message" placeholder="Message">
    <button id="send-btn">Send Message</button>
    <ul id="messages"></ul>
</body>
</html>

请注意,这个示例只是一个功能勉强的 HTML 页面。它只允许用户加入房间、离开房间和向房间发送消息。

升级房间

处理多个房间和多个用户的一种方法是在后台以列表形式存储房间和用户数据。当用户离开或加入房间时,可将数据存储并发送给相应房间的活跃用户。

active_rooms = []

@socketio.on('enter_room')
def handle_enter_room(data):
    username = data['username']
    roomID = data['room']
    global active_rooms
    room = next((room for room in active_rooms if room['room_id'] == roomID), None)
    if room is None:
        room = {'room_id': roomID, 'users': [username]}
        active_rooms.append(room)
    elif username in room['users']:
        print('user already connected')
    else:
        room['users'].append(username)
    join_room(roomID)
    emit('user_joined', room, to=roomID)


@socketio.on('leave_room')
def handle_leave_room(data):
    username = data['username']
    roomID = data['room']
    global active_rooms
    room = next((room for room in active_rooms if room['room_id'] == roomID), None)
    if room is None:
        print('no room detected')
    else:
        room['users'].remove(username)
    emit('user_left', room, to=roomID)
    leave_room(roomID)

在前端,我们可以根据房间的情况利用状态来维护活跃用户列表。

const [activeUsers, setActiveUsers] = useState([])

function connectWS() {
  socket.connect()
  setIsOpen(true)
  socket.emit('enter_room', {'room':id, 'username':user.username})
}

function disconnectWS() {
  socket.emit('leave_room', {room:id, username:user.username})
  socket.disconnect()
  setIsOpen(false)
  setActiveUsers(["You're Disconnected!"])
}

socket.on('user_joined', (data) => {
  setActiveUsers(data.users)
})

socket.on('user_left', (data) => {
  setActiveUsers(data.users)
})

消息持久化

最常见的消息持久化方法之一是通过状态更新。就像我们在后端对活动室所做的那样,我们可以在前端为消息创建一个状态变量,并将我们从服务器收到的新消息作为 websocket 事件添加到状态变量中。但如果我们想稍后访问这些数据呢?一种解决方案是将发送的消息存储到后端数据库中。我使用了 flask-sqlalchemy,并进行了如下设置:

# 添加 Message Model 类,用于存储消息内容
class  Message (db.Model, SerializerMixin): 
    __tablename__ = 'messages' 

    id = db.Column(db.Integer, primary_key= True ) 
    body = db.Column(db.String) 

# 添加 POST 
class  Messages (Resource, SerializerMixin): 

    def  post ( self ): 
        
        request_json = request.get_json() 
        body = request_json.get( 'body' ) 

        try : 
          new_message = Message( 
            body=body 
          ) 
          db.session.add(new_message) 
          db.session.commit() 
          return new_message.to_dict(), 201 

        except IntegrityError: 
            return { 'error' : 'could not create message' }, 422

 api.add_resource(Messages, '/api/messages' ) 

# 添加 WebSocket 事件处理程序
@socketio.on( 'send_message' ) 
def  handle_send_message(msg):
    username = msg[ 'username' ] 
    message = msg[ 'userInput' ] 
    room = msg[ 'room' ] 
    emit('new_message', {'username': username, 'message': message}, to=room)

在前端:

const [userInput, setUserInput] = useState("")
const [currentMessages, setCurrentMessages] = useState([])

function sendMessage() {
  socket.emit('send_message', {userInput: userInput, username:user.username, room:id})
  fetch("/api/messages", {
    method: "POST",
    headers: {
       "Content-Type": "application/json",
    },
    body: JSON.stringify({
       body: userInput
     })
   })
   setUserInput("")
}

socket.on('new_message', (data) => {
  setCurrentMessages(prevMessages => [...prevMessages, data])
})

从这一点出发,您可以采取多种方法。一种方法是获取该组的信息,并将其添加到 currentMessages 之前的 div 中。至于如何继续,就看您的了。

WebSocket 房间提供了一种管理客户端组之间实时通信的有效方法。在服务器端使用 Flask-SocketIO,在客户端使用 Socket.IO,您可以在 Python Web 应用程序中轻松创建和管理这些房间。

无论您正在构建聊天应用程序、协作工具还是任何其他实时系统,了解和利用 WebSocket 房间都可以极大地增强应用程序的性能和用户体验。

作者:Christopher Lozzi
原文:https://medium.com/@clozzi12/websocket-rooms-and-message-persistence-87c94debcab6

版权声明:本文内容转自互联网,本文观点仅代表作者本人。本站仅提供信息存储空间服务,所有权归原作者所有。如发现本站有涉嫌抄袭侵权/违法违规的内容, 请发送邮件至1393616908@qq.com 举报,一经查实,本站将立刻删除。

(0)

相关推荐

发表回复

登录后才能评论