可行但需主动调用receive()拉取消息,设maximummessagesize防溢出,区分.data/.string类型解码,断连后须手动重连。

直接用 URLSessionWebSocketTask 接收消息是可行的,但默认行为容易卡住或丢帧——关键不在“能不能”,而在“怎么设、怎么调、怎么防崩”。
接收单条消息必须主动调用 receive(),不能靠自动触发
很多人以为建立连接后消息会自动推到 delegate 或某个闭包里,其实不是。URLSessionWebSocketTask 是拉取模型:每调一次 receive(),才读一个完整 message(URLSessionWebSocketTask.Message),且只读一次。不调,就永远等在那里。
- 同步写法:
task.receive { result in ... },适合简单场景,但需手动循环调用才能持续收 - 异步写法:
try await task.receive(),更符合现代 Swift 风格,但必须在async上下文中,且异常要显式处理 - 别漏掉错误分支:比如
result.failure?.localizedDescription可能是"The operation couldn’t be completed. (NSPOSIXErrorDomain error 57.)"(连接已断)
maximumMessageSize 默认值太小,大消息直接失败
默认 maximumMessageSize 是 16KB(iOS/macOS 一致),超过就会在 receive() 回调里返回 .failure,错误类型为 URLError.notConnectedToInternet 或类似网络层错误,实际和网络无关——纯属缓冲区溢出。
- 务必在
webSocketTask创建后、resume()前设置:task.maximumMessageSize = 4 * 1024 * 1024(4MB) - 如果服务端可能发超大二进制帧(如图片 base64、protobuf blob),建议按业务上限设,别硬塞
Int.max,否则内存暴涨 - 这个值只影响
receive(),不影响send();发送超长数据会由系统自动分帧,无需手动切片
区分 data 和 string 消息类型,解码逻辑不能混用
URLSessionWebSocketTask.Message 是枚举,只有两个 case:.data(Data) 和 .string(String)。服务端发什么,你就得按什么解——强行把 .data 当字符串转,会崩溃或乱码;把 .string 当 JSON Data 解,会 decode 失败。
- 典型错误:
if let str = message.string { JSONDecoder().decode(MyMsg.self, from: str.data(using: .utf8)!) }—— 这里str.data(using: .utf8)可能为 nil,且多此一举 - 正确做法:先 switch,再针对性处理:
switch message { case .string(let s): handleText(s); case .data(let d): handleBinary(d) } - 注意:即使服务端发的是 JSON 字符串,也走
.string分支;只有发的是 raw bytes(如 MessagePack、自定义二进制协议)才走.data
连接断开时 receive() 不会重试,得自己兜底
URLSessionWebSocketTask 没有内置重连机制。一旦网络抖动、后台切前台、或服务端主动 close,receive() 就会返回错误,之后再调也不会恢复——task 已进入完成状态。
- 检测断开:在
receive的 completion 或 await 中判断 error 是否属于连接类错误(如NSURLErrorNotConnectedToInternet、NSURLErrorCancelled、URLError.networkConnectionLost) - 重建连接:需要重新调用
session.webSocketTask(with: url),再resume(),再立刻receive(),整个链路要串起来 - 别忽略
closeCode和closeReason:它们可能包含服务端传来的断开原因,比如1001(going away)、4001(token expired),这些比网络错误更有业务意义
真正难的不是第一次收到消息,而是连续收 1000 条不丢、不卡、不崩——这要求你对 receive() 的调用节奏、错误分类、内存生命周期都有明确控制,而不是依赖框架“自动处理”。











