在开始为 BoostServerTech 聊天项目编写代码之前,Rubén Pérez (@anarthal) 必须弄清楚各个系统之间的通信方式,包括浏览器与服务器、服务器与 Redis、MySQL 以及内存广播系统等。作为 Boost.MySQL 的作者和 Boost.Redis 的共同维护者,他构建这个聊天服务器作为利用 Boost 库的案例研究。最初的设计工作并不涉及 Boost,而是绘制系统之间的边界并决定它们之间的消息格式。
应用功能
在深入 API 应该包含什么之前,我们首先要问:“我们想支持什么功能?”功能众多,因此需要聚焦。Rubén 选择从简单开始:用户可以创建账户并使用用户名和密码登录,用户可以参与称为房间的群聊,房间目前是静态的。
两种协议,一台服务器
账户创建和登录是一次性操作,客户端发送请求并等待响应,HTTP 足以满足这些需求。聊天消息则不同。当用户在房间中输入内容时,每个连接的客户端都需要立即看到。WebSockets 提供了持久连接,服务器可以在有数据时主动推送。因此,Rubén 采用了这种方式。规则很简单:一次性操作通过 HTTP,实时交互通过 WebSocket。
HTTP 接口非常简单,仅有两个端点:
POST /api/create-account用于自注册POST /api/login用于身份验证
WebSocket 协议
WebSocket 是一个双向通道,但仍需消息格式。Rubén 选择了简单的封装:每条消息是一个 JSON 对象,包含类型字段和负载字段。类型指示消息类型,负载则携带数据。连接时,服务器会发送 hello 事件,包含 UI 渲染所需的所有信息:已认证用户、房间列表和每个房间的最近消息历史。
hello 事件示例
{ "type": "hello", "payload": { "me": { "id": 1, "username": "alice" }, "rooms": [ { "id": "beast", "name": "Boost.Beast", "messages": [ { "id": "1697312400000-0", "content": "Has anyone tried the new...", "user": { "id": 2, "username": "bob" }, "timestamp": 1697312400000 } ], "hasMoreMessages": true } ] } }
实时消息广播
clientMessages:用户发送的消息,携带房间 ID 和消息对象数组。serverMessages:服务器广播的消息,当任何人发送消息时,服务器会将其存储,并将serverMessages推送给该房间的每个客户端。
WebSocket: clientMessages
{ "type": "clientMessages", "payload": { "roomId": "beast", "messages": [ { "content": "This is my message" } ] } }
WebSocket: serverMessages
{ "type": "serverMessages", "payload": { "roomId": "beast", "messages": [ { "id": "1697312500000-0", "content": "This is my message", "user": { "id": 1, "username": "alice" }, "timestamp": 1697312500000 } ] } }
房间历史
hello 事件仅包含每个房间的最新消息。客户端可以请求更旧的消息,使用 requestRoomHistory 消息。
WebSocket: requestRoomHistory
{ "type": "requestRoomHistory", "payload": { "roomId": "beast", "firstMessageId": "1697312400000-0" } }
HTTP API
HTTP API 处理身份验证,服务器生成会话 ID 并存储在 Redis 中,客户端在随后的请求中通过 Cookie 发送此 ID。
HTTP: 创建账户请求
{ "username": "alice", "email": "alice@example.com", "password": "hunter2" }
后端系统
三种系统分别管理不同的数据:MySQL 负责用户数据,Redis 负责消息,内存发布/订阅系统负责广播。
结论
Rubén 选择将系统分开,以便于管理和扩展。将旧消息从 Redis 移动到 MySQL 的计划仅影响消息层,其他层保持不变。
博主点评: 这篇文章深入探讨了聊天服务器的设计思路,特别是在接口层的构建上。通过合理划分后端系统,Rubén 展现了如何优化数据存储与访问模式,确保系统的高效性与可扩展性。