# WebSocket 协议详解

## 协议概述

Zulip 集成系统使用 WebSocket 协议实现游戏客户端与服务器之间的实时双向通信。所有消息采用 JSON 格式编码。

## 连接生命周期

### 1. 建立连接

```
Client                                Server
   |                                     |
   |-------- WebSocket Connect --------->|
   |                                     |
   |<------- Connection Accepted --------|
   |                                     |
```

### 2. 认证握手

```
Client                                Server
   |                                     |
   |-------- login message ------------->|
   |                                     |
   |         [验证 Token]                |
   |         [创建 Zulip Client]         |
   |         [注册 Event Queue]          |
   |         [创建 Session]              |
   |                                     |
   |<------- login_success --------------|
   |                                     |
```

### 3. 消息交换

```
Client                                Server                              Zulip
   |                                     |                                   |
   |-------- chat message -------------->|                                   |
   |                                     |-------- POST /messages ---------->|
   |                                     |<------- 200 OK -------------------|
   |<------- chat_sent ------------------|                                   |
   |                                     |                                   |
   |                                     |<------- Event Queue Message ------|
   |<------- chat_render ----------------|                                   |
   |                                     |                                   |
```

### 4. 断开连接

```
Client                                Server
   |                                     |
   |-------- logout message ------------>|
   |                                     |
   |         [清理 Session]              |
   |         [注销 Event Queue]          |
   |         [销毁 Zulip Client]         |
   |                                     |
   |<------- logout_success -------------|
   |                                     |
   |-------- WebSocket Close ----------->|
   |                                     |
```

## 消息格式规范

### 消息结构

所有消息都是 JSON 对象，包含以下基本字段：

| 字段 | 类型 | 说明 |
|-----|------|------|
| `type` 或 `t` | string | 消息类型标识 |
| 其他字段 | any | 根据消息类型不同而变化 |

### 消息类型标识

- 客户端发送的消息使用 `type` 或 `t` 字段
- 服务器响应的消息统一使用 `t` 字段

## 客户端消息

### LOGIN - 登录认证

```json
{
  "type": "login",
  "token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9..."
}
```

| 字段 | 类型 | 必填 | 说明 |
|-----|------|-----|------|
| type | string | 是 | 固定值 "login" |
| token | string | 是 | 游戏认证 Token |

### CHAT - 发送聊天消息

```json
{
  "t": "chat",
  "content": "Hello, everyone!",
  "scope": "local"
}
```

| 字段 | 类型 | 必填 | 说明 |
|-----|------|-----|------|
| t | string | 是 | 固定值 "chat" |
| content | string | 是 | 消息内容 (1-1000 字符) |
| scope | string | 是 | 消息范围 |

**scope 取值:**
- `"local"`: 当前地图的默认 Topic
- `"topic_name"`: 指定的 Topic 名称

### POSITION - 位置更新

```json
{
  "t": "position",
  "x": 150.5,
  "y": 200.3,
  "mapId": "novice_village"
}
```

| 字段 | 类型 | 必填 | 说明 |
|-----|------|-----|------|
| t | string | 是 | 固定值 "position" |
| x | number | 是 | X 坐标 |
| y | number | 是 | Y 坐标 |
| mapId | string | 是 | 地图 ID |

### LOGOUT - 登出

```json
{
  "type": "logout"
}
```

| 字段 | 类型 | 必填 | 说明 |
|-----|------|-----|------|
| type | string | 是 | 固定值 "logout" |

## 服务器消息

### LOGIN_SUCCESS - 登录成功

```json
{
  "t": "login_success",
  "sessionId": "sess_abc123def456",
  "currentMap": "novice_village",
  "username": "player_name",
  "stream": "Novice Village"
}
```

| 字段 | 类型 | 说明 |
|-----|------|------|
| t | string | 固定值 "login_success" |
| sessionId | string | 会话 ID |
| currentMap | string | 当前地图 ID |
| username | string | 用户名 |
| stream | string | 当前 Zulip Stream |

### CHAT_SENT - 消息发送确认

```json
{
  "t": "chat_sent",
  "messageId": "msg_789xyz",
  "timestamp": 1703500800000
}
```

| 字段 | 类型 | 说明 |
|-----|------|------|
| t | string | 固定值 "chat_sent" |
| messageId | string | Zulip 消息 ID |
| timestamp | number | 发送时间戳 (毫秒) |

### CHAT_RENDER - 接收聊天消息

```json
{
  "t": "chat_render",
  "from": "other_player",
  "txt": "Hi there!",
  "bubble": true,
  "timestamp": 1703500800000,
  "stream": "Novice Village",
  "topic": "General"
}
```

| 字段 | 类型 | 说明 |
|-----|------|------|
| t | string | 固定值 "chat_render" |
| from | string | 发送者名称 |
| txt | string | 消息内容 |
| bubble | boolean | 是否显示气泡 |
| timestamp | number | 消息时间戳 |
| stream | string | 来源 Stream |
| topic | string | 来源 Topic |

### POSITION_UPDATED - 位置更新确认

```json
{
  "t": "position_updated",
  "stream": "Novice Village",
  "topic": "General"
}
```

| 字段 | 类型 | 说明 |
|-----|------|------|
| t | string | 固定值 "position_updated" |
| stream | string | 新的 Zulip Stream |
| topic | string | 新的 Zulip Topic |

### LOGOUT_SUCCESS - 登出成功

```json
{
  "t": "logout_success"
}
```

### ERROR - 错误消息

```json
{
  "t": "error",
  "code": "RATE_LIMIT",
  "message": "消息发送过于频繁，请稍后再试",
  "details": {
    "retryAfter": 60
  }
}
```

| 字段 | 类型 | 说明 |
|-----|------|------|
| t | string | 固定值 "error" |
| code | string | 错误码 |
| message | string | 错误描述 |
| details | object | 可选，额外错误信息 |

## 心跳机制

### 客户端心跳

客户端应每 30 秒发送一次心跳消息：

```json
{
  "t": "ping"
}
```

### 服务器响应

```json
{
  "t": "pong",
  "timestamp": 1703500800000
}
```

### 超时处理

- 服务器在 60 秒内未收到任何消息将断开连接
- 客户端应在连接断开后自动重连

## 重连策略

### 指数退避算法

```
重试间隔 = min(baseDelay * 2^attempt, maxDelay)

baseDelay = 1000ms
maxDelay = 30000ms
```

### 重连流程

1. 检测到连接断开
2. 等待重试间隔
3. 尝试重新连接
4. 连接成功后重新发送 login 消息
5. 恢复会话状态

### 示例代码

```typescript
class ReconnectingWebSocket {
  private baseDelay = 1000;
  private maxDelay = 30000;
  private attempt = 0;
  
  private getDelay(): number {
    const delay = Math.min(
      this.baseDelay * Math.pow(2, this.attempt),
      this.maxDelay
    );
    this.attempt++;
    return delay;
  }
  
  private resetDelay(): void {
    this.attempt = 0;
  }
  
  async reconnect(): Promise<void> {
    const delay = this.getDelay();
    console.log(`等待 ${delay}ms 后重连...`);
    
    await new Promise(resolve => setTimeout(resolve, delay));
    
    try {
      await this.connect();
      this.resetDelay();
    } catch (error) {
      await this.reconnect();
    }
  }
}
```

## 消息序列化

### 发送消息

```typescript
function sendMessage(socket: WebSocket, message: object): void {
  const json = JSON.stringify(message);
  socket.send(json);
}
```

### 接收消息

```typescript
socket.onmessage = (event: MessageEvent) => {
  try {
    const message = JSON.parse(event.data);
    handleMessage(message);
  } catch (error) {
    console.error('消息解析失败:', error);
  }
};
```

## 并发处理

### 消息顺序

- 同一客户端的消息按发送顺序处理
- 不同客户端的消息可能并发处理
- 服务器响应顺序可能与请求顺序不同

### 消息确认

对于需要确认的操作（如发送聊天消息），客户端应：

1. 生成唯一的请求 ID
2. 等待对应的响应
3. 设置超时处理

```typescript
async function sendChatWithConfirmation(
  socket: WebSocket,
  content: string,
  timeout: number = 5000
): Promise<void> {
  return new Promise((resolve, reject) => {
    const timer = setTimeout(() => {
      reject(new Error('发送超时'));
    }, timeout);
    
    const handler = (event: MessageEvent) => {
      const message = JSON.parse(event.data);
      if (message.t === 'chat_sent') {
        clearTimeout(timer);
        socket.removeEventListener('message', handler);
        resolve();
      } else if (message.t === 'error') {
        clearTimeout(timer);
        socket.removeEventListener('message', handler);
        reject(new Error(message.message));
      }
    };
    
    socket.addEventListener('message', handler);
    
    socket.send(JSON.stringify({
      t: 'chat',
      content: content,
      scope: 'local'
    }));
  });
}
```

## 安全考虑

### Token 安全

- Token 仅在 login 消息中传输一次
- 服务器验证后不再需要 Token
- Token 应有合理的过期时间

### 消息验证

- 服务器验证所有消息格式
- 拒绝格式错误的消息
- 记录异常消息日志

### 防重放攻击

- 使用时间戳验证消息新鲜度
- 拒绝过期的消息
- 检测重复的消息 ID
