

本文属于机器翻译版本。若本译文内容与英语原文存在差异，则一律以英文原文为准。

# Amazon GameLift Streams 后端服务和网络客户端
<a name="sdk"></a>

 Amazon GameLift Streams 允许您通过网络浏览器流式传输应用程序。使用 Amazon GameLift Streams Web SDK，您可以设置后端流媒体服务。然后，最终用户通过 Web 客户端连接到直播。他们可以通过云端玩您的游戏或与您的应用程序进行交互。

 Amazon GameLift Streams Web SDK 包括一个示例后端服务器和一个示例 Web 客户端，您可以使用它们开始创建后端服务。您也可以使用这些示例来测试 Amazon GameLift Streams 的直播方式，无需额外开发。要开始使用，请参阅[使用 Amazon GameLift Streams 设置网络服务器和客户端](setting-up-web-sdk.md)。

**Topics**
+ [支持的浏览器和输入](sdk-browsers-input.md)
+ [必需的端口](required-ports.md)
+ [使用 Amazon GameLift Streams 设置网络服务器和客户端](setting-up-web-sdk.md)
+ [自定义直播外观](sdk-stream-appearance.md)
+ [区域偏好](sdk-locale-support.md)
+ [鼠标移动处理](sdk-mouse-movement.md)
+ [应用程序和 Web 客户端之间的数据通道通信](data-channels.md)

# 支持的浏览器和输入
<a name="sdk-browsers-input"></a>

以下列出了用于查看 Amazon GameLift Streams 直播的支持平台和浏览器及其兼容的输入外围设备。浏览器还必须与高级视频编码 (AVC)（也称为 H.264）兼容。

总体而言，我们建议使用谷歌浏览器、Microsoft Edge或基于Chromium的定制桌面应用程序，以获得最佳的最终用户体验和最大的兼容性，尤其是与游戏控制器的兼容性。

 要详细了解哪些控制器与哪些浏览器兼容，请参阅 [Web Gamepad API](https://developer.mozilla.org/en-US/docs/Web/API/Gamepad_API)。尽管某些指导可能不适用于 Amazon GameLift Streams，但我们预计大多数游戏控制器都能通过蓝牙成功连接。

[\[See the AWS documentation website for more details\]](http://docs.aws.amazon.com/zh_cn/gameliftstreams/latest/developerguide/sdk-browsers-input.html)

## 已知问题
<a name="sdk-browsers-input-known-issues"></a>

以下是浏览器和输入的已知问题：
+ 只要按下 Safari 浏览器就会`Esc`立即退出全屏模式。这不能被覆盖。
+ iOS 不支持 “嵌入式” 或 “应用内” 浏览器视图 LinkedIn，例如Yelp、Instagram等移动应用程序中的视图。它们往往会禁用实时交互式直播所需的浏览器WebRTC支持。我们建议检测非标准的浏览器字符串，并提示用户在 Safari 中打开。
+ 如果应用程序中的屏幕分辨率未设置为 1080p，则鼠标跟踪可能会受到影响。如果可能，我们建议禁用任何其他分辨率的选择。我们还建议禁用窗口模式，并且只能在全屏模式下运行。
+ 为了支持 Proton 上的游戏控制器的即插即用，尽管原生 Linux 应用程序缺乏对游戏控制器的支持，但在 Proton 运行时环境中运行的游戏将*始终*显示已连接的游戏控制器，即使客户端上没有插入任何控制器。对于即使控制器处于空闲状态且未使用也提示输入控制器的游戏来说，这可能是一个问题。我们建议游戏根据最后一种输入法显示输入界面。

## 限制
<a name="sdk-browsers-input-limits"></a>
+ 除了 Ubuntu 22.04 LTS 之外，大多数运行时环境都支持游戏控制器。如果您需要游戏控制器支持，可以考虑使用其他运行时环境创建游戏。有关其他运行时环境的列表，请参阅[运行时环境](configuration-options.md#configuration-options-runtime)。
+ Firefox 不支持 PlayStation 5 和 Luna 游戏控制器。
+ 触觉反馈支持：
  + Chrome、Edge 和 Safari 支持对 PlayStation 4 和 Xbox 系列 S/X 控制器的触觉反馈。
  + 只有 Safari 浏览器支持 PlayStation 5 DualSense 控制器上的 Haptics。
  + Firefox 不支持任何控制器上的触觉反馈。
  + Android 和 iOS 设备不支持任何控制器上的触觉反馈。
+ Amazon **Stream GameLift s 控制台中的测试**直播功能不支持麦克风。

## IPv6 支持
<a name="sdk-browsers-input-ipv6-support"></a>

 IPv6只有 Windows 运行时应用程序才支持向仅限客户端进行流式传输。


| 运行时 | 直播结束 IPv4 | 直播结束 IPv6 | 
| --- | --- | --- | 
| Microsoft Windows Server 2022 Base | 支持 | 是 | 
| Ubuntu 22.04 LTS | 是 | 否 | 
| Proton 运行时 | 是 | 否 | 

# 必需的端口
<a name="required-ports"></a>

 要集成 Amazon GameLift Streams，请确保您的网络基础设施具有必要的端口开放且可供访问。以下是您应计划在网络上开放的端口列表，以便与 Amazon GameLift Streams 通信。


| 端口 | 协议 | 用途 | 
| --- | --- | --- | 
|  443  |  (HTTPS) CP  |  AWS APIs，包括 Amazon GameLift Streams  | 
|  33435-33465  |  UDP  |  Web RTC  | 

# 使用 Amazon GameLift Streams 设置网络服务器和客户端
<a name="setting-up-web-sdk"></a>

 在本教程中，您将设置一个集成 Amazon GameLift Streams 流媒体服务的网络客户端应用程序。然后，您将使用 Amazon GameLift Streams Web SDK、一个 JavaScript 库和可以开始使用的示例代码。示例代码包括一个简单的 Amazon GameLift Streams 后端 Web 服务器和一个简单的 Web 客户端。在本教程结束时，您可以使用示例代码启动直播。

 如果您是第一次使用 Amazon GameLift Streams，我们强烈建议您从本教程开始，该[在 Amazon Streams 中开始你的第一个 GameLift 直播](streaming-process.md)教程将引导您完成将游戏上传到 Amazon S3，并在浏览器中测试从 Amazon GameLift Streams 控制台中进行流式传输的过程。

## 先决条件
<a name="setting-up-web-sdk-prereq"></a>
+ 具有适当编程访问凭证的 AWS 账户。有关更多信息，请参阅 [以开发者身份设置 Amazon GameLift Streams](setting-up.md)。
+ AWS 开发工具包。
+  GameLift 支持 Amazon Streams 的网络浏览器 — 请参阅。[支持的浏览器和输入](sdk-browsers-input.md)
+ Node.js — 参见 [Node.js 下载](https://nodejs.org/en/download)页面。

## 下载 Web 开发工具包
<a name="setting-up-web-sdk-materials"></a>

在本教程中，您需要从 “[入门” 产品页面](https://aws.amazon.com/gamelift/streams/getting-started/)的 “资源” 部分下载以下材料：
+ **Amazon GameLift Streams Web SDK 捆绑包**：其中包括用于简单后端服务和网络客户端的示例代码。
+ **Amazon GameLift Streams Web SDK API 参考**：本 API 参考记录了 Amazon GameLift Streams 的 API 包装器。 JavaScript

## 设置您的直播资源
<a name="setting-up-web-sdk-resources"></a>

您必须拥有流资源（应用程序和直播组）才能启动直播。具体而言，你必须有：
+  处于 “就**绪**” 状态的应用程序。
+  处于**活动**状态且具有可用直播容量的直播组。
+  要在主位置以外的其他位置进行流式传输，应用程序必须已完成向该位置的复制。

 要使用 Amazon Stream GameLift s 控制台或 Amazon Streams CLI 设置应用程序[在 Amazon GameLift Streams 中准备应用程序](applications.md)和 GameLift 直播组[使用 Amazon Stre GameLift ams 直播群组管理直播](stream-groups.md)，请分别参考和。或者，有关 Amazon GameLift Streams 控制台中的 end-to-end演练，请参阅。[在 Amazon Streams 中开始你的第一个 GameLift 直播](streaming-process.md)

## 设置后端服务器
<a name="setting-up-web-sdk-backend"></a>

 后端服务器负责处理诸如对用户进行身份验证、配置直播参数以及代表最终用户执行 Amazon Stre GameLift ams 服务 API 调用之类的任务。查看示例代码和 Amazon GameLift Streams Web SDK API 参考，了解有关设置的更多信息。具体而言，请参阅 Amazon GameLift Streams Web SDK 包中的 server.js 文件。

**重要**  
 此代码是示例代码，仅用于测试和评估目的，不应在生产环境中使用。

**运行示例后端服务**

1.  打开终端或命令提示符并导航到该文件夹`AmazonGameLiftStreamsWebSDK\GameLiftStreamsSampleGamePublisherService\`。

1.  运行以下 命令：

   ```
   npm install
   node server.js
   ```

 运行示例后端服务后，最终用户可以通过 Web 客户端连接到直播。在下一步中测试 Web 客户端。

## 启动 Web 客户端
<a name="setting-up-web-sdk-client"></a>

Web 客户端应用程序负责接收和解码 Amazon GameLift Streams 流、向最终用户进行流式传输，以及为最终用户提供与该应用程序互动的 Web 浏览器用户界面。查看示例代码和 Amazon GameLift Streams Web SDK API 参考，详细了解如何将 JavaScript Amazon GameLift Streams Web SDK 集成到您自己的网络客户端应用程序中。具体而言，请参阅 Amazon GameLift Streams Web 软件开发工具包`public/index.html`中的内容。在浏览器中启动 Web 客户端时，也可以查看网页源代码。

**注意**  
Amazon Stre GameLift ams 中的 Windows 运行时支持通过 IPv4 或进行直播会话 IPv6。但是，Linux 和 Proton 运行时环境仅支持流式传输。 IPv4

**启动 Web 客户端应用程序**

1.  打开 Web 浏览器并导航至`http://localhost:port/`。端口号由后端服务器设置；默认情况下，这是 HTTP 端口 8000。

1. 玩游戏或使用软件。

   1. 要附加输入，例如鼠标，请选择**附加输入**。

   1. 要退出游戏，请选择 **Esc** 键。

   1. 要停止服务器进程，请选择 **Ctrl\$1C 键**。

## 清理直播资源
<a name="setting-up-web-sdk-cleanup"></a>

**警告**  
 流组在分配了流媒体容量时会产生成本，即使该容量未使用也是如此。为避免不必要的成本，请将直播组扩展到所需的大小。我们建议您在开发过程中将直播组中的始终开启容量和目标空闲容量在不使用时调整为零。有关更多信息，请参阅[将直播组扩展到零容量](pricing.md#pricing-pause-stream-groups)。

完成本教程并且不再需要流式传输应用程序后，请按照以下步骤清理您的 Amazon GameLift Streams 资源。

**删除直播组**

 当您删除直播组时，Amazon Stre GameLift ams 会释放所有直播容量。

**使用 Amazon Streams 控制台删除 GameLift 直播组**

1.  登录 AWS 管理控制台 并打开 [Amazon GameLift Streams 控制台](https://console.aws.amazon.com/gameliftstreams/)。

1.  要查看现有直播组的列表，请在导航窗格中选择**直播组**。

1.  选择要删除的直播组的名称。

1.  在直播组详情页面上，选择**删除**。

1.  在 “**删除**” 对话框中，确认删除操作。

 Amazon GameLift Streams 开始释放计算资源并删除直播组。在此期间，直播组处于 “**删除**” 状态。Amazon Stream GameLift s 删除直播组后，您将无法再对其进行检索。

**删除应用程序**

 您只可删除符合以下条件的应用程序：
+  应用程序处于**就绪**或**错误**状态。
+  应用程序未在任何正在进行的流会话中进行流式传输。您必须等到客户端结束直播会话或在 Amazon Stre GameLift ams API [TerminateStreamSession](https://docs.aws.amazon.com/gameliftstreams/latest/apireference/API_TerminateStreamSession.html)中调用才能结束直播。

 如果应用程序链接到任何流组，则必须先将其与所有关联的流组取消链接，然后才能将其删除。在控制台中，有一个对话框将引导您完成此过程。

**使用 Amazon GameLift Streams 控制台删除应用程序**

1. 登录 AWS 管理控制台 并打开 [Amazon GameLift Streams 控制台](https://console.aws.amazon.com/gameliftstreams/)。

1. 在导航栏中，选择**应用程序**以查看现有应用程序的列表。选择要删除的应用程序。

1. 在应用程序详细信息页面中，选择**删除**。

1. 在 “**删除**” 对话框中，确认删除操作。

 Amazon GameLift Streams 开始删除该应用程序。在此期间，应用程序处于`Deleting`状态。在 Amazon GameLift Streams 删除应用程序后，您将无法再对其进行检索。

# 自定义直播外观
<a name="sdk-stream-appearance"></a>

## 加载画面
<a name="sdk-loading-screen"></a>

当客户打开网络浏览器查看直播时，网络客户端开始建立与 Amazon Stre GameLift ams 直播会话的连接。直播会话加载时，您可以在客户的屏幕上显示自定义背景和徽标。

`GameLiftStreamsSampleGamePublisherService/public/LoadingScreen/loadingscreen.js`文件中的 Amazon GameLift Streams Web SDK 示例客户端演示了如何在前端 Web 客户端中实现动画徽标。默认加载屏幕由 2 张图像组成：背景和前景。前景图像位于中间，具有脉冲动画。动画仅在直播会话连接时播放。

**启用加载屏幕**

1. 在 Amazon GameLift Streams Web SDK 示例客户端中，导航到该`GameLiftStreamsSampleGamePublisherService/public/LoadingScreen/`文件夹。

1. 使用默认名称添加背景和前景图像，`Background.png`以及。`LoadingLogo.png`如果要重命名它们或使用不同的图像格式，则必须更新中的代码`GameLiftStreamsSampleGamePublisherService/public/loadingscreen.js`。

1. （可选）在中`GameLiftStreamsSampleGamePublisherService/public/loadingscreen.js`，更新 JavaScript 代码以实现不同的动画。

# 区域偏好
<a name="sdk-locale-support"></a>

 在 Amazon GameLift Streams 中，您可以为每个直播设置区域偏好。如果您的应用程序从最终用户的操作系统中检索特定于位置的信息，例如时间或货币，这将非常有用。

 Amazon GameLift Streams 支持以下语言：


| 值 | 说明 | 
| --- | --- | 
|  `en_US`  |  美式英语（默认）  | 
|  `ja_jp.UTF-8`  |  日式料理  | 

 **更改语言环境设置** 

 当您[StartStreamSession](https://docs.aws.amazon.com/gameliftstreams/latest/apireference/API_StartStreamSession.html)使用 Amazon GameLift Streams API 调用时，请将其`LANG=<language>`添加到您的`AdditionalEnvironmentVariables`。由于区域设置首选项是每个用户唯一的，因此您可以在直播会话级别进行设置。如果您未设置此项，则直播默认使用美式英语。

**Example 示例**  

```
aws gameliftstreams start-stream-session \
   --identifier arn:aws:gameliftstreams:us-west-2:123456789012:streamgroup/1AB2C3De4 \
   --protocol WebRTC \
   --signal-request "[webrtc-ice-offer json string]" \
   --user-id xnshijwh \            
   --additional-environment-variables '{"LANG": "ja_JP.UTF-8"}'
```

# 鼠标移动处理
<a name="sdk-mouse-movement"></a>

鼠标移动处理对于在流媒体应用程序中提供响应式和直观的用户体验至关重要。Amazon GameLift Streams 会根据应用程序的光标行为自动优化鼠标输入传输，确保无论光标是隐藏还是可见，鼠标移动都感觉自然。了解 Amazon GameLift Streams 如何处理鼠标事件有助于您设计与流媒体服务无缝配合并提供最佳用户体验的应用程序。

## 鼠标输入模式
<a name="sdk-mouse-input-modes"></a>

Amazon GameLift Streams 使用两种不同的模式将鼠标事件传输到您的应用程序，根据光标可见性自动选择适当的模式：

相对模式  
在相对模式下，鼠标更新以与先前位置的微小增量差异进行传输。此模式非常适合需要精确、连续的鼠标移动跟踪的应用程序，例如第一人称射击游戏 (FPS) 游戏或使用 3D 方向的界面。当操作系统光标隐藏或完全透明时，Amazon GameLift Streams 使用相对模式。

绝对模式  
在绝对模式下，鼠标光标位置以精确的屏幕坐标形式传输。此模式非常适合依赖精确光标定位的应用程序，例如 point-and-click游戏或任何带有可点击元素的用户界面。当操作系统光标可见时，即使您的应用程序显示自定义光标图像，Amazon GameLift Streams 也会使用绝对模式。

这种自动选择可确保不同应用程序类型的最佳性能，而无需手动配置。

## 指针锁
<a name="sdk-pointer-lock"></a>

指针锁定是一项 Web API 功能，它可以在特定元素内捕获鼠标光标，隐藏光标并防止其离开指定区域。对于需要不受限制地移动鼠标来控制摄像机或瞄准，而不会受到可见光标的干扰或无法到达窗口边缘的限制的游戏来说，此功能特别有用。

Amazon GameLift Streams 通过 Web SDK `InputConfiguration` 界面中的`autoPointerLock`属性提供自动指针锁定功能。此功能与 [requestPointerLock API](https://developer.mozilla.org/en-US/docs/Web/API/Element/requestPointerLock) 集成，可提供直观且可感知上下文的鼠标捕获。

### 自动指针锁定行为
<a name="sdk-pointer-lock-behavior"></a>

当应用程序处于全屏模式且远程光标在 GameLift 直播主机上不可见时，Amazon Streams 会自动启用指针锁定。这种行为与常见的游戏开发模式非常吻合：
+ **FPS/TPS 游戏和 3D 方向控制** ——指针会自动锁定，光标被隐藏，从而提供 FPS 游戏必不可少的不受限制的摄像机控制。
+ **Point-and-click 游戏和用户界面控制**-当游戏在菜单交互或策略游戏中使光标可见时，指针保持可见和解锁状态，从而保持预期的用户体验。

### 配置选项
<a name="sdk-pointer-lock-configuration"></a>

该`autoPointerLock`属性接受以下值：

`true`  
当远程光标不可见时，总是会捕获鼠标。

`false`  
无论光标是否可见，都不会捕捉到鼠标。

`'fullscreen'`（默认值）  
只有当视频元素处于全屏模式且远程光标不可见时，才会捕获鼠标。

**重要**  
`autoPointerLock`由于平台限制，在 Safari 浏览器或 iOS 平台上不起作用。

## 最佳实践
<a name="sdk-mouse-best-practices"></a>

要确保流式传输应用程序中的鼠标操作效果最佳，请执行以下操作：
+ **始终全屏直播**-您的应用程序应该已经在全屏模式下运行，才能在我们的服务上正常运行。此外，我们建议使用浏览器支持将直播设置为全屏元素，以获得最佳的最终用户体验。这将有助于避免诸如系统光标和软件光标之间的对齐问题之类的问题。
+ **隐藏相对运动的光标-如果您的应用程序需要相对**的鼠标移动（例如 FPS 风格的摄像机控件或基于拖动的交互），请在这些交互过程中隐藏操作系统的光标。在某些情况下，您可能需要在鼠标向下移动时隐藏光标，然后在鼠标向上移动时再次显示光标。
+ **显示光标进行绝对定位**-当您的应用程序需要精确的光标定位以进行用户界面交互时，请确保操作系统的光标保持可见以启用绝对坐标模式。
+ **测试不同的输入场景**-验证您的应用程序是否正确处理相对鼠标模式和绝对鼠标模式，因为 Amazon GameLift Streams 可能会根据您的光标可见性变化在模式之间切换。
+ **测试不同的窗口模式**-在窗口模式和全屏模式下测试应用程序的鼠标操作（如果适用）。确定哪个`autoPointerLock`设置最适合您的输入配置。

# 应用程序和 Web 客户端之间的数据通道通信
<a name="data-channels"></a>

 数据通道允许您在 Amazon GameLift Streams 应用程序和 Web 客户端（在最终用户的网络浏览器中运行的 JavaScript 代码）之间安全地传输任意消息。这允许最终用户通过观看直播的网络浏览器与 Amazon GameLift Streams 正在流式传输的应用程序进行交互。

以下是 Amazon GameLift Streams 中数据通道的一些示例用例：
+ 用户可以在其本地浏览器 URLs 中打开应用程序。
+ 用户可以将剪贴板中的内容来回传递给应用程序。
+ 用户可以将内容从本地计算机上传到应用程序。
+ 开发人员可以在向应用程序发送命令的浏览器中实现 UI。
+ 用户可以传递架构来控制可视化图层的显示。

## 特征
<a name="data-channels-features"></a>

**邮件大小限制**  
Amazon GameLift Streams Web SDK 对每条消息的最大大小限制为 64 KB（65536 字节）。这样可以确保消息大小限制与大多数浏览器兼容，并且通信对流总带宽的影响很小。

**指标**  
 直播会话结束时，有关您的数据通道使用情况的指标将发送到您的 AWS 账户。有关更多信息，请参阅 “*监控 Amazon GameLift Streams*” 一节中的。[数据渠道](monitoring-cloudwatch.md#monitoring-data-channels)

## 使用数据通道
<a name="data-channels-using"></a>

Amazon GameLift Streams Web SDK 提供了将消息作为字节数组发送到应用程序的`sendApplicationMessage`功能。消息由您定义的回调函数处理。`clientConnection.applicationMessage`

如果客户端在应用程序连接到数据通道端口之前发送消息，则消息将排队。然后，当应用程序连接时，它会收到消息。但是，如果应用程序在客户端连接到数据通道端口之前发送消息，则消息将丢失。在发送消息之前，应用程序必须检查客户端的连接状态。

## 在客户端
<a name="data-channels-using-client"></a>

在 Web 客户端应用程序中编写以下代码。

1.  定义回调函数以接收来自应用程序的传入消息。

   ```
   function streamApplicationMessageCallback(message) {
       console.log('Received ' + message.length + ' bytes of message from 
       Application');
   }
   ```

1.  设置`clientConnection.applicationMessage`为你的回调函数。

   ```
   clientConnection: {
       connectionState: streamConnectionStateCallback,
       channelError: streamChannelErrorCallback,
       serverDisconnect: streamServerDisconnectCallback,
       applicationMessage: streamApplicationMessageCallback,
   }
   ```

1.  调用该`GameLiftStreams.sendApplicationMessage`函数向您的应用程序发送消息。只要直播会话处于活动状态并且附加了输入，您就可以随时调用此方法。

例如，请参阅 Amazon GameLift Streams Web SDK 示例客户端，该客户端演示了如何在客户端设置简单的数据通道。

## 在应用程序方面
<a name="data-channels-using-application"></a>

在您的应用程序中编写以下逻辑。

### 第 1 步：Connect 连接到数据信道端口
<a name="data-channels-using-application-1"></a>

当您的应用程序启动时，连接到 `40712` on 端口`localhost`。您的应用程序应在整个执行期间保持此连接。如果应用程序关闭连接，则无法重新打开连接。

### 第 2 步：收听活动
<a name="data-channels-using-application-2"></a>

事件以固定大小的标头开头，后面是可变长度的关联数据。当您的应用程序收到事件时，请解析该事件以检索信息。

**事件格式**
+ **标题**：格式为 4 字节的标题 `abcc`
  +  `a`: 客户端 ID 字节。如果有多个连接（由于断开连接和重新连接），这将标识特定的客户端连接。
  +  `b`: 事件类型字节。 `0`-客户端已连接，`1`-客户端已断开连接，`2`-客户端发送消息。将来的 Amazon GameLift Streams 服务更新可能会收到其他类型的事件，因此应将其忽略。
  +  `cc`：关联事件数据的长度。这表示为 2 个字节，按大端顺序排列（第一个字节最重要）。如果事件类型为 2，则事件数据表示来自客户端的消息内容。
+ **数据**：其余字节包含事件数据，例如客户端消息。标题中用表示`cc`数据的长度。

**监听事件**

1. 读取四个标头字节以检索客户端 ID、事件类型和事件数据的长度。

1. 根据标题中描述的长度读取可变长度的事件数据，无论客户端 ID 和事件类型如何。无条件读取数据很重要，这样事件数据就永远不会留在缓冲区中，因为缓冲区可能会与下一个事件标头混淆。不要根据事件类型对数据长度做出假设。

1. 如果您的应用程序可以识别，则根据事件类型采取适当的措施。此操作可能包括记录传入的连接或断开连接，或者解析客户端消息和触发应用程序逻辑。

### 第 3 步：向客户端传输消息
<a name="data-channels-using-application-3"></a>

应用程序应使用与传入事件相同的四字节标头格式来传输消息。

**向客户端传送消息**

1. 使用以下属性编写标题：

   1. `a`: 客户端 ID 字节。如果您的消息是对客户端消息的响应，则它应重复使用与传入的客户端消息相同的客户端 ID，以避免竞争条件，例如将来自旧客户端连接的响应传送到新重新连接的客户端。如果您的应用程序向客户端发送未经请求的消息，则应将客户端 ID 设置为与最近的 “客户端连接” 事件（事件类型 0）相匹配。

   1. `b`：传出消息的事件类型必须始终为 2。客户端会忽略其他事件类型的消息。

   1. `cc`：消息的长度，以字节为单位。

1. 写入消息字节。

除非客户端断开连接，否则该消息将传送到指定的客户端。当断开连接的客户端重新连接时，将通过连接客户端的事件分配新的客户端 ID。所有未传送的旧客户端 ID 的消息都将被丢弃。

**Example**  
以下伪代码演示了在应用程序端传送消息的逻辑。有关使用 Winsock 的完整示例，请参阅 Windows Sockets 2 [文档中的完整 Winsock 客户端代码](https://learn.microsoft.com/en-us/windows/win32/winsock/complete-client-code)。  

```
connection = connect_to_tcp_socket("localhost:40712")
loop:
    while has_pending_bytes(connection):
        client_id = read_unsigned_byte(connection)
        event_type = read_unsigned_byte(connection)
        event_length = 256 * read_unsigned_byte(connection)
        event_length = event_length + read_unsigned_byte(connection)
        event_data = read_raw_bytes(connection, event_length)
        if message_type == 0:
            app_process_client_connected(client_id)
        else if message_type == 1:
            app_process_client_disconnected(client_id)
        else if message_type == 2:
            app_process_client_message(client_id, event_data)
        else:
            log("ignoring unrecognized event type")
    while app_has_outgoing_messages():
        target_client_id, message_bytes = app_next_outgoing_message()
        message_length = length(message_bytes)
        write_unsigned_byte(connection, target_client_id)
        write_unsigned_byte(connection, 2)
        write_unsigned_byte(connection, message_length / 256)
        write_unsigned_byte(connection, message_length mod 256)
        write_raw_bytes(connection, message_bytes)
```