连接生命周期与重连
一个 Realtime 连接就是一个 WebSocket。网络变化、设备休眠和基础设施重启都可能关闭它。客户端会自动重连,而你的应用可以展示当前的连接状态。
六种状态
浏览器客户端中的 bird.connection.state 以及 Swift 和 Kotlin 中的 bird.connectionState 始终是以下值之一(Kotlin 将它们拼写为 ConnectionState 常量,因此 unavailable 读作 ConnectionState.UNAVAILABLE):
| 状态 | 含义 |
|---|---|
| initialized | 客户端已创建,但尚未打开 socket。 |
| connecting | socket 正在打开,或已打开并等待握手。 |
| connected | 握手完成。连接已获得 ID,可以进行订阅。 |
| unavailable | 连接已断开,重连已排入计划。 |
| disconnected | 你调用了 disconnect()。没有排入计划的操作。 |
| failed | 服务器拒绝了此连接。客户端不会重试。 |
绑定 state_change 可监听每次状态转换,或者只绑定某个状态名称来监听特定状态:
import { BirdRealtime } from "@messagebird/realtime";
const bird = new BirdRealtime({ appKey: "your-app-key", region: "us1" });
bird.connection.bind("state_change", ({ previous, current }) => {
console.log(previous, "->", current);
});
bird.connection.bind("unavailable", () => showReconnectingBanner());
bird.connection.bind("connected", () => hideReconnectingBanner());import BirdRealtime
let bird = BirdRealtime(options: .init(appKey: "your-app-key", region: "us1"))
bird.onConnectionStateChange { previous, current in
print(previous, "->", current)
if current == .unavailable { showReconnectingBanner() }
if current == .connected { hideReconnectingBanner() }
}import com.bird.realtime.BirdRealtime
import com.bird.realtime.BirdRealtimeOptions
import com.bird.realtime.ConnectionState
val bird = BirdRealtime(BirdRealtimeOptions(appKey = "your-app-key", region = "us1"))
bird.onConnectionStateChange { previous, current ->
println("$previous -> $current")
if (current == ConnectionState.UNAVAILABLE) showReconnectingBanner()
if (current == ConnectionState.CONNECTED) hideReconnectingBanner()
}Swift 和 Kotlin 使用同一个观察者监听所有状态转换,因此需要对 current 进行分支判断。处理函数在 Swift 中运行于主队列,在 Kotlin 中运行于 Android 主 looper(如果可用)。传入 deliveryQueue 或 Executor 可切换到其他执行上下文。
已打开的 socket 在服务器握手到达之前一直处于 connecting 状态。握手在状态变为 connected 之前设置 bird.connection.connectionId,因此授权可以对该 ID 进行签名。如果握手未携带 ID,客户端会报告错误并关闭 socket。
自动重连
在意外断开后,客户端会等待一段时间再打开新的 socket。延迟采用全抖动指数退避策略,基数为 1 秒,上限为 30 秒。每次握手成功都会重置退避计数。
关闭码决定了以下三种情况中的哪一种:
| 关闭码 | 行为 |
|---|---|
| 4000 至 4099 | 拒绝。不重试,状态变为 failed。 |
| 4200 至 4299 | 立即重试。 |
| 其他所有值 | 状态变为 unavailable,然后按退避策略重试。 |
网络故障走退避路径。请在你的 UI 中处理拒绝码,因为这些终态失败不会自动重连。
过期连接检测
网络路径失效后,socket 可能仍显示为已打开。客户端会在活动超时后发送 ping,默认为 120 秒,除非握手提供了其他值。如果在 30 秒的默认 pong 超时内没有收到 pong,客户端将使用立即重试码关闭 socket。
频道会发生什么
订阅属于连接,因此断开会移除它们。客户端会保留每个频道对象,在下一次进入 connected 状态后重新订阅,并保持已有的处理函数。
客户端会使用新的连接 ID 为每个私有频道和 presence 频道再次调用你的 authEndpoint。它无法复用旧签名,因为该签名包含了上一个连接 ID。在一次 signin() 调用后,客户端也会在每次新连接上进行登录,并通过 signin_error 报告后续失败。
bird.connection.bind("signin_error", ({ message }) => {
console.warn("connection has no identity:", message);
});bird.onSigninError { error in
print("connection has no identity:", error.message)
}bird.onSigninError { error ->
println("connection has no identity: ${error.message}")
}当频道正在重连时,channel.trigger 返回 false 但不会发送客户端事件。请检查 channel.subscribed,或者在 bird:subscription_succeeded 之后再发送,该事件在每次成功重新订阅后触发。
const room = bird.subscribe("presence-room-1");
room.bind("bird:subscription_succeeded", ({ members }) => {
renderMembers(members);
});guard let room = bird.subscribe("presence-room-1") as? PresenceChannel else { return }
room.bind(BirdProtocol.Event.subscriptionSucceeded) { _ in
renderMembers(room.members)
}val room = bird.subscribe("presence-room-1")
if (room is PresenceChannel) {
room.bind(BirdProtocol.Event.SUBSCRIPTION_SUCCEEDED) {
renderMembers(room.members)
}
}Realtime 不会重放客户端断开期间发布的事件。如果视图需要在断开后恢复,请在 bird:subscription_succeeded 时获取当前状态,然后从该点开始应用后续事件。
主动断开连接
bird.disconnect() 关闭 socket 且不排入重连计划。当已登出或不活跃的客户端不再需要实时数据时使用它。客户端会保留频道,因此稍后调用 bird.connect() 可重新打开 socket 并重新订阅。
bird.disconnect(); // state: disconnected, no reconnect
bird.connect(); // reopens and re-subscribesbird.disconnect() // state: disconnected, no reconnect
bird.connect() // reopens and re-subscribesbird.disconnect() // state: DISCONNECTED, no reconnect
bird.connect() // reopens and re-subscribes在移动端,当相关视图或 activity 停止时断开连接,恢复时重连。频道对象和绑定仍然可用。
连接上的错误与频道上的错误
属于某个频道的服务器错误会到达该频道。最常见的是订阅被拒绝,以及身份验证器返回 403 的情况:
room.bind("bird:subscription_error", (err) => {
console.warn("could not join:", err);
});room.bind(BirdProtocol.Event.subscriptionError) { error in
print("could not join:", error ?? "")
}room.bind(BirdProtocol.Event.SUBSCRIPTION_ERROR) { error ->
println("could not join: $error")
}所有未被归属到某个频道的线路数据都会到达连接:
bird.connection.bind("error", ({ code, message }) => {
console.warn(code, message);
});bird.onError { error in
print(error.code ?? 0, error.message)
}bird.onError { error ->
println("${error.code} ${error.message}")
}处理关闭码 4009
码 4009 属于不重试区间,因此连接进入 failed。检查原因以区分以下情况:
客户端不会自动重连,因此由你的应用决定下一步操作:
bird.connection.bind("error", ({ code }) => {
if (code === 4009) showSignedOutScreen();
});bird.onError { error in
if error.code == 4009 { showSignedOutScreen() }
}bird.onError { error ->
if (error.code == 4009) showSignedOutScreen()
}当身份验证已结束时,适合进入已登出状态。调用 bird.connect() 会启动新连接并重复授权流程,因此仅在调用者的授权状态发生变化后再重连。
后续步骤
- 授权频道是你的后端需要实现的契约,也是客户端在每次重连时重复的请求。
- 要求授权连接会将未授权的连接变为已关闭的连接。
- 终止成员连接用于通过 4009 主动关闭连接。
- Realtime 概览涵盖频道、成员和连接的整体模型。
相关资源
继续查阅此主题的文档、指南和示例。资源为英文。