Files
ts-mobile-go/docs/流程/01_连接服务器.md
2026-07-20 19:01:03 +08:00

245 lines
11 KiB
Markdown
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# 连接服务器
> 用户流程:输入服务器地址 → 输入昵称 → 输入密码(可选) → 点击进入服务器 → 连接成功 → 首次同步
> 对应程序流程:准备配置 → TSBridge.connect() → EventBus 收到 Connected 事件 → 首次同步
---
## 一、生命周期
### 1.1 完整生命周期
```mermaid
flowchart TD
A[准备配置<br/>地址、昵称、密码] --> B[TSBridge.connect<br/>组装连接参数并注册 EventCallback]
B --> C[Go SDK Connect<br/>通过 gomobile 发起 UDP 会话与协议握手]
C --> D{连接请求是否成功<br/>判断 connect 返回值}
D -- 否 --> E[连接失败处理<br/>记录错误并释放客户端资源]
D -- 是 --> F{是否已收到 Connected 事件<br/>等待 EventBus 连接就绪事件}
F -- 否 --> G[等待失败处理<br/>处理超时或握手异常]
F -- 是 --> H[EventBus Connected 事件<br/>接收连接成功事件]
H --> I[首次同步<br/>请求频道、成员和自身身份信息]
I --> J[运行就绪<br/>允许发送命令、聊天和语音]
J --> K[持续接收 EventBus 事件<br/>归并成员、移动、消息和 Poke]
K --> J
J --> L{结束原因<br/>区分主动断开与被动中断}
L -- 用户主动退出 --> M[TSBridge.disconnect<br/>发送优雅关闭请求]
L -- 网络或服务端中断 --> N[EventBus Disconnected 事件<br/>接收断开原因并停止写操作]
L -- 自己被踢出 --> O[EventBus Kicked 事件<br/>接收频道踢或服务器踢原因]
M --> P[清理运行资源<br/>停止语音、取消请求并清空会话状态]
N --> P
O --> P
P --> Q[生命周期结束<br/>不再承担当前会话请求]
```
### 生命周期约束
1. `TSBridge.connect()` 发起连接,连接就绪通过 EventBus `Connected` 事件确认。
2. EventBus 事件监听应在 `connect()` 之前开始收集,避免连接早期事件无人接收。
3. `connect()` 成功只表示连接流程已启动;发送普通命令前必须等待 `Connected` 事件。
4. `Connected` 是 SDK 推送的已连接事实,可作为首次同步入口。
5. `TSBridge.disconnect()` 用于主动优雅退出;`Disconnected` 事件用于观察最终断开结果或异常断开。
6. `Kicked` 事件来源于 `notifyclientleftview` 的特定原因,必须与普通成员离开区分。
7. 断开后应停止语音、拒绝新命令,并清理只属于当前会话的状态。
### 1.2 生命周期阶段与允许操作
| 阶段 | 允许操作 | 禁止或不建议操作 | 中文说明 |
| --- | --- | --- | --- |
| 构造前 | 准备配置 | 调用任何 TSBridge 方法 | 连接尚未建立 |
| 已配置 | 注册 EventBus 事件收集、读取本地配置 | 发送服务器命令 | 尚未建立网络会话 |
| 连接中 | 等待 `Connected` 事件、响应取消 | `getClientsJSON()``moveToChannel()` 等业务命令 | 握手尚未确认完成 |
| 已连接 | 同步频道和成员、发送聊天、发送语音 | 重复调用 `connect()` | SDK 已具备业务通信条件 |
| 断开中 | 停止上层写操作、等待清理 | 发起新业务命令 | 会话正在关闭 |
| 已断开 | 释放资源或重新连接 | 复用失效会话发送命令 | 旧会话不再可信 |
---
## 二、初始化配置
### 2.1 初始化配置结构
```mermaid
flowchart TD
A[应用启动配置<br/>收集身份、地址、昵称和可选参数] --> B[Identity<br/>提供 TeamSpeak 加密身份]
A --> C[服务器地址 addr<br/>提供域名、IP 或 TSDNS 地址]
A --> D[昵称 nickname<br/>提供当前连接显示名称]
A --> E[连接选项<br/>提供可选运行能力]
E --> E3[serverPassword<br/>设置服务器连接密码]
E --> E4[defaultChannel<br/>设置连接后默认进入的频道名称]
E --> E5[defaultChannelPassword<br/>设置默认频道密码]
B --> F[TSBridge.connect<br/>组装参数并通过 gomobile 发起连接]
C --> F
D --> F
E3 --> F
E4 --> F
E5 --> F
F --> G[注册 EventCallback<br/>JNI 回调转 EventBus 事件]
G --> H[Go SDK Connect<br/>UDP 会话与协议握手]
```
### 2.2 推荐初始化顺序
```kotlin
// TSBridge.connect() 内部注册 JNI EventCallback
// 将所有 Go 回调转为 EventBus.emit() 事件。
// ViewModel 通过 EventBus.events.collect() 接收事件并更新状态。
// 示例:ServerViewModel 中收集连接事件
viewModelScope.launch {
EventBus.events.collect { event ->
when (event) {
is TSEvent.Connected -> {
// 开始首次同步
performInitialSync()
}
is TSEvent.Disconnected -> {
// 清理会话
handleDisconnect(event.message)
}
else -> { /* 其他事件由对应 ViewModel 处理 */ }
}
}
}
// connect 发起网络连接,Connected 事件确认握手真正完成。
val error = TSBridge.connect(host, nickname, password, defaultChannel, defaultChannelPassword, callbacks)
// error 为空字符串表示连接流程已启动,等待 EventBus Connected 事件
```
### 2.3 配置之间的依赖
| 配置 | 前置条件 | 影响阶段 | 依赖说明 |
| --- | --- | --- | --- |
| `Identity` | 必须可用 | 连接、握手 | 缺少身份无法正确创建客户端 |
| `addr` | 必须非空且可解析 | 地址解析、连接 | 由 SDK 内部解析器解析 |
| `nickname` | 必须满足服务器命名规则 | 握手、上线 | 服务器可能拒绝无效或冲突昵称 |
| `serverPassword` | 服务器启用密码时需要 | 握手 | 密码错误会导致连接失败 |
| `defaultChannel` | 目标频道名称存在 | 连接完成阶段 | SDK 尝试在连接后自动进入该频道 |
| `defaultChannelPassword` | 已设置默认频道且频道有密码 | 进入默认频道 | 单独设置密码而无默认频道没有明确目标 |
---
## 三、状态树
### Client 总状态树(连接相关部分)
```mermaid
flowchart TD
ROOT[Client 生命周期状态树<br/>描述一个连接从创建到结束的完整状态]
ROOT --> U[未配置 Uninitialized<br/>仅准备 Identity 和连接参数]
ROOT --> C[已配置 Configured<br/>EventBus 已注册但尚未连接]
ROOT --> N[连接过程 Connecting<br/>正在解析地址并执行握手]
ROOT --> R[已连接 Connected<br/>握手完成且可以执行业务命令]
U --> U1[配置 Identity<br/>准备加密身份]
U --> U2[配置地址与昵称<br/>准备基础连接参数]
U --> U3[配置连接选项<br/>准备密码和默认频道]
C --> C1[注册 EventBus 事件收集<br/>绑定服务端推送处理器]
C --> C2[等待 connect 调用<br/>尚不能发送业务命令]
N --> N1[Resolving<br/>SDK 内部解析地址]
N --> N2[Handshaking<br/>通过 UDP 建立协议会话]
N --> N3[WaitingReady<br/>等待 Connected 事件]
N --> N4[ConnectFailed<br/>解析、密码、网络或握手失败]
R --> R1[Syncing<br/>通过列表命令建立频道和成员基线]
R --> R2[Ready<br/>允许聊天、移动、查询和语音]
```
---
## 四、时序:构造、连接与首次同步
```mermaid
sequenceDiagram
participant UI as 上层应用<br/>发起连接并展示状态
participant VM as ViewModel<br/>收集 EventBus 事件
participant BRIDGE as TSBridge<br/>管理连接生命周期
participant SDK as Go SDK (gomobile)<br/>管理协议通信
participant TS as TeamSpeak 服务器<br/>执行握手和列表请求
participant STORE as 业务状态仓库<br/>保存频道和成员基线
UI->>BRIDGE: connect(host, nickname, password, ...)
BRIDGE->>SDK: gomobile 调用 Go Connect
SDK->>TS: 连接握手请求<br/>协商身份、密码和会话
TS-->>SDK: 握手响应
SDK-->>BRIDGE: connect 返回 ""
SDK->>BRIDGE: OnConnected 回调<br/>gomobile 转换为 JNI 回调
BRIDGE->>BRIDGE: EventBus.emit(Connected)
BRIDGE->>VM: EventBus SharedFlow<br/>分发 Connected 事件
VM->>VM: 更新连接状态
par 请求频道基线
VM->>BRIDGE: TSBridge.getChannelsJSON()
BRIDGE->>SDK: gomobile 调用 GetChannelsJSON
SDK->>TS: channellist
TS-->>SDK: JSON 字符串
SDK-->>BRIDGE: 频道 JSON
BRIDGE-->>VM: JSON 字符串
VM->>STORE: 解析并保存频道基线
and 请求成员基线
VM->>BRIDGE: TSBridge.getClientsJSON()
BRIDGE->>SDK: gomobile 调用 GetClientsJSON
SDK->>TS: clientlist
TS-->>SDK: JSON 字符串
SDK-->>BRIDGE: 成员 JSON
BRIDGE-->>VM: JSON 字符串
VM->>STORE: 解析并保存成员基线
and 读取自身 ID
VM->>BRIDGE: TSBridge.getClientID()
BRIDGE-->>VM: selfClientID
VM->>STORE: 保存自身 ID
end
VM->>STORE: 原子提交频道、成员和自身 ID
STORE-->>UI: 同步完成<br/>连接状态进入业务就绪
```
---
## 五、事件依赖
### 连接相关前置依赖矩阵
| 操作或事件 | 必须依赖 | 建议依赖 | 依赖失败时的处理 |
| --- | --- | --- | --- |
| `TSBridge.connect` | 地址、昵称 | 已注册 EventBus 事件收集 | 参数无效时不进入连接流程 |
| `connect` 成功 | 已注册 EventBus 事件收集 | — | 返回错误信息并停止等待连接 |
| `Connected` 事件 | 已调用 `connect` | — | 超时后终止本次初始化 |
| 首次同步 | `Connected` 事件 | 同时读取频道、成员和自身 ID | 任一核心请求失败则不标记业务就绪 |
### 事件依赖总图(连接阶段)
```mermaid
flowchart TD
CONFIG[初始化配置完成<br/>地址、昵称已准备] --> REG[注册 EventBus 事件收集<br/>确保早期服务端推送可被处理]
REG --> CONNECT[TSBridge.connect<br/>启动地址解析、UDP 会话和握手]
CONNECT --> CONNECTED[EventBus Connected 事件<br/>SDK 推送连接成功事实]
CONNECTED --> CID[TSBridge.getClientID<br/>读取当前用户客户端 ID]
CONNECTED --> CHANNELS[TSBridge.getChannelsJSON<br/>建立频道基线]
CONNECTED --> CLIENTS[TSBridge.getClientsJSON<br/>建立在线成员基线]
CID --> SELFREADY[当前用户身份就绪<br/>能够识别自己的移动和离开事件]
CHANNELS --> DATAREADY[服务器实体就绪<br/>频道和成员引用关系可被验证]
CLIENTS --> DATAREADY
SELFREADY --> READY[业务就绪<br/>允许移动、聊天和语音]
DATAREADY --> READY
```
### 权威性划分
| 数据 | 推荐权威来源 | 原因 |
| --- | --- | --- |
| 是否完成连接 | EventBus `Connected` 事件 | `connect()` 只负责启动连接 |
| 自身客户端 ID | `TSBridge.getClientID()` | SDK 本地缓存服务器分配的 ID |
| 初始频道列表 | `TSBridge.getChannelsJSON()` 响应 | SDK 未列出频道创建、更新、删除事件 |
| 初始在线成员 | `TSBridge.getClientsJSON()` 响应 | 建立完整在线成员基线 |