Skip to content

创建 WebSocket 应用程序

在 Kotlin 中使用 Ktor 创建 WebSocket 应用程序

代码示例 tutorial-server-websockets

使用的插件

Static Content
了解如何提供静态内容,如样式表、脚本、图像等。
Content Negotiation
ContentNegotiation 插件有两个主要用途:在客户端和服务器之间协商媒体类型,以及以特定格式序列化/反序列化内容。
Ktor Server 中的 WebSockets
Websockets 插件允许您在服务器和客户端之间创建多路通信会话。
kotlinx.serialization

本文将指导您如何在 Kotlin 中使用 Ktor 创建 WebSocket 应用程序。它基于

创建 RESTful API
了解如何使用 Kotlin 和 Ktor 构建后端服务,其中包含一个生成 JSON 文件的 RESTful API 示例。
教程中的材料。

本文将教您如何执行以下操作:

  • 创建使用 JSON 序列化的服务。
  • 通过 WebSocket 连接发送和接收内容。
  • 同时向多个客户端广播内容。

先决条件

您可以独立完成本教程,但我们建议您先完成

创建 RESTful API
了解如何使用 Kotlin 和 Ktor 构建后端服务,其中包含一个生成 JSON 文件的 RESTful API 示例。
教程,以熟悉
内容协商
ContentNegotiation 插件有两个主要用途:在客户端和服务器之间协商媒体类型,以及以特定格式序列化/反序列化内容。
和 REST。

我们建议您安装 IntelliJ IDEA,但您也可以使用其他您喜欢的 IDE。

你好 WebSockets

在本教程中,您将通过添加通过 WebSocket 连接与客户端交换 Task 对象的功能,扩展在

创建 RESTful API
了解如何使用 Kotlin 和 Ktor 构建后端服务,其中包含一个生成 JSON 文件的 RESTful API 示例。
教程中开发的任务管理器服务。为此,您需要添加
WebSockets 插件
Websockets 插件允许您在服务器和客户端之间创建多路通信会话。
。虽然您可以手动将其添加到现有项目中,但为了本教程起见,您将从头开始创建一个新项目。

创建带有插件的初始项目

  1. 导航至 Ktor 项目生成器

  2. Project artifact 字段中,输入 com.example.ktor-websockets-task-app 作为项目工件的名称。 在 Ktor 项目生成器中命名项目工件

  3. 在插件部分搜索并通过点击 Add 按钮添加以下插件:

    1. Content Negotiation
    2. kotlinx.serialization
    3. WebSockets
    4. Static Content

    在 Ktor 项目生成器中添加插件

  4. 添加插件后,它们将显示在插件部分的右上角。

    然后您将看到将添加到项目中的所有插件列表: Ktor 项目生成器中的插件列表

  5. 点击 Download 按钮以生成并下载您的 Ktor 项目。

添加起始代码

下载完成后,在 IntelliJ IDEA 中打开您的项目并按照以下步骤操作:

  1. 导航至 src/main/kotlin 并创建一个名为 model 的新子软件包。
  2. model 软件包内创建一个新的 Task.kt 文件。

  3. 打开 Task.kt 文件并添加一个 enum 来表示优先级,以及一个 data class 来表示任务:

    kotlin

    请注意,Task 类使用了来自 kotlinx.serialization 库的 Serializable 注解。这意味着实例可以转换为 JSON 以及从 JSON 转换,从而允许通过网络传输其内容。

    因为您包含了 WebSockets 插件,生成器已经在 src/main/kotlin 目录下的 Websockets.kt 文件中添加了配置,并在 Routing.kt 文件中添加了 webSocket 路由。

  4. 打开 Websockets.kt 文件,并将现有的 .configureWebsockets() 函数替换为以下内容:
    kotlin
    • 安装 WebSockets 插件并使用标准设置进行配置。
    • 设置了 contentConverter 属性,使插件能够通过 kotlinx.serialization 库对发送和接收的对象进行序列化。
  5. 打开 Routing.kt 文件,并将现有的 Application.configureRouting() 函数替换为下面的实现:

    kotlin
    • 路由配置了一个单一端点,其相对 URL 为 /tasks
    • 在收到请求后,任务列表会通过 WebSocket 连接进行序列化发送。
    • 一旦所有项目发送完毕,服务器将关闭连接。

    出于演示目的,在发送任务之间引入了一秒钟的延迟。这允许您观察任务在客户端中逐步出现的过程。如果没有这个延迟,该示例看起来将与之前文章中开发的

    RESTful 服务
    了解如何使用 Kotlin 和 Ktor 构建后端服务,其中包含一个生成 JSON 文件的 RESTful API 示例。
    Web 应用程序
    了解如何使用 Kotlin、Ktor 和 Thymeleaf 模板构建网站。
    完全相同。

    此迭代的最后一步是为此端点创建一个客户端。因为您包含了

    Static Content
    了解如何提供静态内容,如样式表、脚本、图像等。
    插件,Ktor 项目生成器已在 src/main/resources/static 中添加了一个 index.html 文件。

  6. 打开 index.html 文件并将现有内容替换为以下内容:

    html

    该页面使用了所有现代浏览器中都可用的 WebSocket 类型。您在 JavaScript 中创建此对象,并将端点的 URL 传递到构造函数中。随后,您为 onopenoncloseonmessage 事件附加事件处理程序。在触发 onmessage 事件时,您使用 document 对象的方法向表格追加一行。

  7. 在 IntelliJ IDEA 中,点击运行按钮 (IntelliJ IDEA 运行图标) 以启动应用程序。

  8. 导航至 http://0.0.0.0:8080/static/index.html。您应该会看到一个带有一个按钮的表单和一个空表格:

    显示包含一个按钮的 HTML 表单的网页浏览器页面

    当您点击表单时,任务会从服务器加载,并以每秒一个的速度出现。因此,表格会被增量填充。您还可以通过打开浏览器 Developer Tools 中的 JavaScript Console 来查看记录的消息。

    点击按钮时显示列表项的网页浏览器页面

    至此,服务的表现符合预期。WebSocket 连接已打开,项目已发送到客户端,然后连接关闭。底层网络中存在很多复杂性,但 Ktor 默认处理了所有这些复杂性。

了解 WebSockets

在进行下一次迭代之前,回顾一下 WebSockets 的一些基础知识可能会有所帮助。如果您已经熟悉 WebSockets,可以继续 改进服务的设计

在之前的教程中,您的客户端发送 HTTP 请求并接收 HTTP 响应。这种模式运行良好,使互联网具备了可扩展性和弹性。

然而,它不适用于以下场景:

  • 内容是随时间增量生成的。
  • 内容根据事件频繁更改。
  • 客户端需要在内容产生时与服务器交互。
  • 一个客户端发送的数据需要迅速传播给其他客户端。

这些场景的示例包括股票交易、购买电影和音乐会门票、在线拍卖出价以及社交媒体中的聊天功能。WebSockets 的开发就是为了处理这些情况。

WebSocket 连接是建立在 TCP 之上的,可以持续很长时间。该连接提供全双工通信,这意味着客户端可以同时向服务器发送消息并从中接收消息。

WebSocket API 定义了四个事件(open、message、close 和 error)和两个操作(send 和 close)。如何访问此功能可能因不同的语言和库而异。例如,在 Kotlin 中,您可以将传入消息序列作为 Flow 来消费。

改进设计

接下来,您将重构现有代码,为更高级的示例腾出空间。

  1. model 软件包中,创建一个新的 TaskRepository.kt 文件。

  2. 打开 TaskRepository.kt 并添加 TaskRepository 类型:

    kotlin

    您可能还记得之前教程中的这段代码。

  3. 导航至 src/main/kotlin 并打开 Routing.kt 文件。
  4. 您现在可以通过利用 TaskRepository 来简化 Application.configureRouting() 中的路由:

    kotlin

通过 WebSockets 发送消息

为了展示 WebSockets 的强大功能,您将创建一个新端点,其中:

  • 当客户端启动时,它会收到所有现有任务。
  • 客户端可以创建并发送任务。
  • 当一个客户端发送任务时,其他客户端会收到通知。
  1. Routing.kt 文件中,将当前的 .configureRouting() 方法替换为下面的实现:

    kotlin

    通过这段代码,您完成了以下工作:

    • 将发送所有现有任务的功能重构为一个辅助方法。
    • routing {} 块中,您创建了一个线程安全的 session 对象列表,以跟踪所有客户端。
    • 添加了一个相对 URL 为 /tasks2 的新端点。当客户端连接到此端点时,相应的 session 对象将被添加到列表中。然后服务器进入无限循环,等待接收新任务。收到新任务后,服务器将其存储在仓库中,并将副本发送给所有客户端(包括当前客户端)。

    为了测试此功能,您将创建一个扩展 index.html 功能的新页面。

  2. src/main/resources/static 内创建一个名为 wsClient.html 的新 HTML 文件。

  3. 打开 wsClient.html 并添加以下内容:

    html

    这个新页面引入了一个 HTML 表单,用户可以在其中输入新任务的信息。提交表单后,将调用 sendTaskToServer() 事件处理程序。这将构建一个包含表单数据的 JavaScript 对象,并使用 WebSocket 对象的 .send() 方法将其发送到服务器。

  4. 在 IntelliJ IDEA 中,点击重新运行按钮 (IntelliJ IDEA 重新运行图标) 以重启应用程序。

  5. 要测试此功能,请并排打开两个浏览器,并按照以下步骤操作。

    1. 在浏览器 A 中,导航至 http://0.0.0.0:8080/static/wsClient.html。您应该会看到显示的默认任务。
    2. 在浏览器 A 中添加一个新任务。新任务应该出现在该页面的表格中。
    3. 在浏览器 B 中,导航至 http://0.0.0.0:8080/static/wsClient.html。您应该会看到默认任务,以及您在浏览器 A 中添加的任何新任务。
    4. 在任一浏览器中添加任务。您应该会看到新项目同时出现在两个页面上。
    并排显示两个网页浏览器页面,演示通过 HTML 表单创建新任务

添加自动化测试

为了简化您的 QA 流程并使其快速、可复现且无需人工干预,您可以使用 Ktor 内置的

自动化测试支持
了解如何使用特殊的测试引擎测试您的服务器应用程序。
。请按照以下步骤操作:

  1. 将以下依赖项添加到 build.gradle.kts 中,以便您在 Ktor Client 中配置对

    内容协商
    ContentNegotiation 插件有两个主要用途:在客户端和服务器之间协商媒体类型,以及以特定格式序列化/反序列化内容。
    的支持:

    kotlin
  2. 在 IntelliJ IDEA 中,点击编辑器右侧的 Gradle 通知图标 (IntelliJ IDEA Gradle 图标) 以加载 Gradle 更改。

  3. 导航至 src/test/kotlin 并打开 ServerTest.kt 文件。

  4. 将生成的测试类替换为以下实现:

    kotlin

    通过此设置,您可以:

    • 配置您的服务在测试环境中运行,并启用与生产环境相同的功能,包括 JSON 序列化和 WebSockets。
    • Ktor Client
      了解如何创建和配置 Ktor 客户端。
      中配置内容协商和 WebSocket 支持。如果没有这些配置,客户端在通过 WebSocket 连接时将不知道如何将对象 (反) 序列化为 JSON。
    • 声明您期望服务返回的 Tasks 列表。
    • 使用 client 对象的 .webSocket 函数向 /tasks 发送请求。
    • 将传入的任务作为 Flow 消费,并将其增量添加到列表中。
    • 收到所有任务后,以常规方式将 expectedTasksactualTasks 进行比较。

后续步骤

做得好!通过将 WebSocket 通信和 Ktor Client 的自动化测试结合起来,您已经显著增强了任务管理器服务。

继续阅读

下一篇教程
了解使用 Exposed SQL 库将 Ktor 服务连接到数据库仓库的过程。
,探索您的服务如何使用 Exposed 库与关系型数据库无缝交互。