# 连接服务器

> 用户流程：输入服务器地址 → 输入昵称 → 输入密码（可选） → 点击进入服务器 → 连接成功 → 首次同步
> 对应程序流程：准备配置 → 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()` 响应 | 建立完整在线成员基线 |
