什么是WebSocket ?ios 中如何使用?

40 阅读5分钟

1. WebSocket 基础

1.1 什么是 WebSocket?

  • 全双工通信协议:基于 TCP,允许客户端与服务器建立持久连接,双方可主动发送数据。
  • 实时性:替代 HTTP 轮询,适用于实时聊天、股票行情、在线游戏等场景。
  • 协议升级:通过 HTTP 握手(Upgrade: websocket)建立连接,后续通过 WebSocket 帧通信。

1.2 与 HTTP 对比

特性HTTPWebSocket
连接方式短连接(请求-响应后断开)长连接(持久化,双向通信)
通信方向单向(客户端主动请求)双向(客户端/服务器均可主动发送)
头部开销每次请求携带完整 HTTP 头初始握手后,数据帧头部极小
适用场景静态资源获取、REST API实时数据传输(如聊天、推送)

2. iOS 中 WebSocket 的实现方式

2.1 官方方案:URLSessionWebSocketTask(iOS 13+)

  • 核心类URLSessionWebSocketTask(属于 URLSession 框架)
  • 特点
    • 原生支持,无需第三方库。
    • 支持 iOS 13+,兼容性有限。
    • 提供基本连接管理、消息发送/接收功能。
基本使用流程
import Foundation

// 使用 actor 来确保线程安全
actor WebSocketManager {
    // 创建一个 URLSession 实例,用于管理 WebSocket 连接
    private let session = URLSession(configuration: .default)
    
    // 声明一个可选的 URLSessionWebSocketTask,用于处理 WebSocket 通信
    private var webSocketTask: URLSessionWebSocketTask?
    
    // 连接到 WebSocket 服务器
    func connect() {
        // 检查 URL 是否有效
        guard let url = URL(string: "wss://your-server.com/socket") else {
            print("无效的 URL") // 如果 URL 无效,打印错误信息并退出
            return
        }
        
        // 创建 WebSocketTask 并将其赋值给 webSocketTask 属性
        webSocketTask = session.webSocketTask(with: url)
        
        // 启动 WebSocket 连接
        webSocketTask?.resume()
        
        // 开始接收消息
        receiveMessage()
    }
    
    // 接收消息的方法
    private func receiveMessage() {
        // 调用 WebSocketTask 的 receive 方法,监听服务器发来的消息
        webSocketTask?.receive { [weak self] result in
            // 在一个新的 Task 中处理接收到的消息(确保线程安全)
            Task {
                await self?.handleReceive(result)
            }
        }
    }
    
    // 处理接收到的消息
    private func handleReceive(_ result: Result<URLSessionWebSocketTask.Message, Error>) {
        switch result {
        case .success(let message):
            // 根据消息类型进行处理
            switch message {
            case .data(let data):
                // 如果是二进制数据,打印数据内容
                print("收到二进制数据: (data)")
            case .string(let text):
                // 如果是文本消息,打印文本内容
                print("收到文本消息: (text)")
            @unknown default:
                // 处理未知的消息类型(防止未来扩展导致崩溃)
                fatalError()
            }
            
            // 继续监听下一条消息
            self.receiveMessage()
        case .failure(let error):
            // 如果接收消息失败,打印错误信息
            print("接收失败: (error)")
        }
    }
    
    // 发送消息到 WebSocket 服务器
    func sendMessage(_ message: String) {
        // 将消息以字符串形式发送到服务器
        webSocketTask?.send(.string(message)) { error in
            if let error = error {
                // 如果发送失败,打印错误信息
                print("发送失败: (error)")
            } else {
                // 如果发送成功,打印成功信息
                print("消息发送成功")
            }
        }
    }
    
    // 关闭 WebSocket 连接
    func disconnect() {
        // 取消 WebSocketTask,并指定关闭原因
        webSocketTask?.cancel(with: .goingAway, reason: nil)
        print("连接已关闭")
    }
}

2.2 第三方库:Starscream

  • 适用场景:支持 iOS 11+,功能更丰富(如 SSL 验证、自定义头、自动重连)。
  • 集成方式:通过 CocoaPods 或 SPM 安装。
基本使用
import Starscream

// 1. 创建 WebSocket 实例
var request = URLRequest(url: URL(string: "wss://your-server.com/socket")!)
request.setValue("Bearer token", forHTTPHeaderField: "Authorization")
let socket = WebSocket(request: request)

// 2. 设置代理
socket.delegate = self

// 3. 连接服务器
socket.connect()

// 4. 发送消息
socket.write(string: "Hello Server")

// 5. 关闭连接
socket.disconnect()

// 实现 WebSocketDelegate
extension ViewController: WebSocketDelegate {
    func didReceive(event: WebSocketEvent, client: WebSocket) {
        switch event {
        case .connected(let headers):
            print("连接成功: \(headers)")
        case .disconnected(let reason, let code):
            print("断开连接: \(reason), code: \(code)")
        case .text(let text):
            print("收到文本: \(text)")
        case .binary(let data):
            print("收到二进制数据: \(data)")
        case .error(let error):
            print("错误: \(error?.localizedDescription ?? "")")
        case .cancelled:
            print("连接取消")
        default:
            break
        }
    }
}

3. 核心功能与最佳实践

3.1 连接管理

  • 心跳机制(Ping/Pong):定期发送 Ping 帧检测连接活性。
    // URLSessionWebSocketTask
    webSocketTask.sendPing { error in
        if let error = error {
            print("Ping 失败: \(error)")
        }
    }
    
    // Starscream
    socket.write(ping: Data()) // 发送 Ping
    
  • 自动重连:网络中断后自动尝试重新连接(需第三方库支持或自定义逻辑)。

3.2 消息处理

  • 数据类型:支持文本(String)、二进制(Data)。
  • 序列化:常用 JSON 格式传输结构化数据。
    // 发送 JSON
    let message = ["type": "chat", "content": "Hello"]
    if let data = try? JSONEncoder().encode(message) {
        webSocketTask.send(.data(data)) { /* 处理错误 */ }
    }
    

3.3 线程安全

  • 主线程更新 UI:在接收消息的回调中切换至主线程更新界面。
    DispatchQueue.main.async {
        self.label.text = receivedText
    }
    

3.4 错误处理与断线重连

  • 监听错误事件:捕获连接错误、消息发送失败等。
  • 重连策略:指数退避重试(如 1s、2s、4s...)。

4. 安全性

  • 使用 wss://:WebSocket Secure(基于 TLS 加密)。
  • 证书验证:在 URLSessionDelegate 中处理 SSL 证书校验(如需自定义)。
  • 身份验证:通过 HTTP 头(如 Authorization: Bearer token)或握手阶段传递 Token。

5. 性能优化

  • 消息压缩:服务器启用 WebSocket 扩展(如 permessage-deflate)。
  • 连接复用:避免频繁创建/销毁连接。
  • 数据量控制:避免单次发送过大消息(分片传输)。

6. 常见问题与解决方案

问题解决方案
连接不稳定实现自动重连逻辑,监听网络状态变化(如 Network.framework)。
内存泄漏使用 [weak self] 避免循环引用,及时释放不再使用的 WebSocket 实例。
后台连接断开启用后台模式(需配置 Capabilities > Background Modes > Remote notifications)。
消息顺序混乱在消息中添加序列号,客户端按序处理。

7. 适用场景示例

  • 实时聊天应用:消息即时推送、已读回执。
  • 金融行情推送:实时股票价格、K线数据。
  • 多人在线游戏:玩家位置同步、状态更新。
  • 物联网控制:设备状态监控、指令下发。

总结

  • 核心价值:WebSocket 为 iOS 应用提供高效、实时的双向通信能力。
  • 选择方案:优先使用 URLSessionWebSocketTask(iOS 13+),低版本或需高级功能时选择 Starscream。
  • 关键实践:心跳保活、错误重连、数据序列化、线程安全。