Swift Vapor服务端实现1对1 Socket聊天房间的逻辑及示例咨询
方案选型结论
优先选择「单Socket连接+房间路由标识」的方案,该方案资源消耗更低、维护成本更小,完全满足1对1聊天房间的需求。
核心实现逻辑
- 服务端维护两份全局映射表:
用户ID <-> WebSocket连接实例、房间ID <-> 参与用户ID数组 - 客户端与服务端仅建立1条Socket连接,所有交互消息都通过该连接传输,消息结构固定携带
房间ID、发送方ID、消息类型标识 - 1对1房间创建流程:用户发起聊天请求时,服务端生成唯一房间ID,将双方用户ID与房间ID绑定,再把房间ID通知给双方即可
- 消息转发流程:服务端收到用户发送的聊天消息后,根据房间ID找到对应的另一个用户,直接把消息转发给目标用户的连接即可
Vapor服务端代码示例
首先定义全局存储类,管理连接和房间映射:
import Vapor // 全局WebSocket资源管理 final class WebSocketManager { static let shared = WebSocketManager() // 存储用户ID与对应WebSocket连接 var userConnections: [String: WebSocket] = [:] // 存储房间ID与对应参与用户ID数组(1对1房间数组固定长度为2) var roomUsers: [String: [String]] = [:] private init() {} }
定义通用消息结构和消息类型:
// 消息类型枚举 enum MessageType: String, Codable { case bind // 客户端连接后绑定用户ID case createRoom // 发起创建1对1房间请求 case roomCreated // 服务端返回房间创建成功通知 case sendMessage // 发送聊天消息 } // 通用消息结构体,客户端与服务端交互统一使用该结构 struct ChatMessage: Codable { let type: MessageType let roomID: String? let fromUserID: String let toUserID: String? let content: String }
在routes.swift中注册WebSocket路由:
// 全局仅需暴露一个WebSocket入口,所有客户端都连接该地址 app.webSocket("chat") { req, ws in // 监听客户端消息 ws.onText { ws, text in // 解析客户端发来的消息 guard let data = text.data(using: .utf8), let message = try? JSONDecoder().decode(ChatMessage.self, from: data) else { return } switch message.type { case .bind: // 绑定用户ID与当前连接 WebSocketManager.shared.userConnections[message.fromUserID] = ws case .createRoom: // 创建1对1房间,toUserID为聊天对方的用户ID let roomID = UUID().uuidString WebSocketManager.shared.roomUsers[roomID] = [message.fromUserID, message.toUserID ?? ""] // 构造房间创建通知,发送给双方 let roomNotify = ChatMessage( type: .roomCreated, roomID: roomID, fromUserID: "system", toUserID: nil, content: "1对1房间创建成功" ) guard let notifyData = try? JSONEncoder().encode(roomNotify), let notifyStr = String(data: notifyData, encoding: .utf8), let fromWs = WebSocketManager.shared.userConnections[message.fromUserID], let toWs = WebSocketManager.shared.userConnections[message.toUserID ?? ""] else { return } fromWs.send(notifyStr) toWs.send(notifyStr) case .sendMessage: // 转发聊天消息到房间内的另一个用户 guard let roomID = message.roomID, let roomUsers = WebSocketManager.shared.roomUsers[roomID], let toUserID = roomUsers.first(where: { $0 != message.fromUserID }), let targetWs = WebSocketManager.shared.userConnections[toUserID], let msgData = try? JSONEncoder().encode(message), let msgStr = String(data: msgData, encoding: .utf8) else { return } targetWs.send(msgStr) default: break } } }
注意:需要补充实现WebSocket断开连接的监听回调,及时清除无效的用户连接和闲置房间,避免内存泄漏。
iOS客户端适配逻辑
不需要修改原有Socket连接逻辑,仅需要调整消息发送规则:
- Socket连接成功后,首先发送
type = bind类型的消息,把当前登录用户的ID传给服务端完成绑定 - 需要和指定用户发起聊天时,发送
type = createRoom、toUserID = 对方用户ID的消息,收到服务端返回的roomCreated通知后,即可拿到当前1对1房间的ID - 发送聊天消息时,指定
type = sendMessage、roomID = 对应房间ID,服务端会自动完成消息转发
两种方案对比
- 单Socket+路由方案:客户端仅需维护1条连接,断线重连逻辑仅需处理1次,连接资源占用不会随房间数量增长,适配1对1、群聊等所有聊天场景,推荐使用
- 每个房间单独建Socket方案:仅适合房间数量极少的特殊场景,连接数随房间数线性增长,服务端和客户端连接维护成本极高,断线后每个房间的连接都需要单独重连,不推荐使用
内容的提问来源于stack exchange,提问作者George Heinz
相关产品推荐
相关产品推荐

