You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

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连接逻辑,仅需要调整消息发送规则:

  1. Socket连接成功后,首先发送type = bind类型的消息,把当前登录用户的ID传给服务端完成绑定
  2. 需要和指定用户发起聊天时,发送type = createRoom、toUserID = 对方用户ID的消息,收到服务端返回的roomCreated通知后,即可拿到当前1对1房间的ID
  3. 发送聊天消息时,指定type = sendMessage、roomID = 对应房间ID,服务端会自动完成消息转发

两种方案对比
  • 单Socket+路由方案:客户端仅需维护1条连接,断线重连逻辑仅需处理1次,连接资源占用不会随房间数量增长,适配1对1、群聊等所有聊天场景,推荐使用
  • 每个房间单独建Socket方案:仅适合房间数量极少的特殊场景,连接数随房间数线性增长,服务端和客户端连接维护成本极高,断线后每个房间的连接都需要单独重连,不推荐使用

内容的提问来源于stack exchange,提问作者George Heinz

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.09.30 08:48:03