Skip to content

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

代码示例: tutorial-server-websockets

已使用的插件:

Routing
路由是用于处理服务器应用程序中传入请求的核心插件。
,
Static Content
了解如何提供静态内容,例如样式表、脚本、图像等。
,
Content Negotiation
ContentNegotiation 插件有两个主要目的:在客户端和服务器之间协商媒体类型,以及以特定格式序列化/反序列化内容。
,
WebSockets in Ktor Server
WebSockets 插件允许您在服务器和客户端之间创建多向通信会话。
, kotlinx.serialization

本文将引导您完成使用 Ktor 在 Kotlin 中创建 WebSocket 应用程序的过程。它建立在

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

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

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

先决条件

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

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

我们建议您安装 IntelliJ IDEA,但您也可以选择其他 IDE。

Hello WebSockets

在本教程中,您将基于

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

使用插件创建初始项目

  1. 导航到 Ktor 项目生成器

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

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

    1. Routing
    2. Content Negotiation
    3. Kotlinx.serialization
    4. WebSockets
    5. Static Content

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

  4. 添加插件后,点击插件部分右上角的 5 plugins 按钮,以显示已添加的插件。

    您将看到一个列表,其中包含将添加到您项目的所有插件: Ktor 项目生成器中的插件列表

  5. 点击 下载 按钮以生成并下载您的 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/com/example/plugins 中已生成了一个 Sockets.kt 文件。

  4. 打开 Sockets.kt 文件,并用以下实现替换现有的 Application.configureSockets() 函数:

    kotlin

    此代码执行以下步骤:

    1. 安装 WebSockets 插件并使用标准设置进行配置。
    2. 设置 contentConverter 属性,使插件能够通过 kotlinx.serialization 库序列化发送和接收的对象。
    3. 路由配置了一个单端点,其相对 URL 为 /tasks
    4. 收到请求后,任务列表通过 WebSocket 连接进行序列化传输。
    5. 所有项发送完毕后,服务器关闭连接。

    为了演示目的,在发送任务之间引入了一秒的延迟。这 使我们能够观察任务在客户端中逐渐出现。如果没有此延迟, 该示例将与先前文章中开发的

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

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

    静态内容
    了解如何提供静态内容,例如样式表、脚本、图像等。
    插件,因此在 src/main/resources/static 中已生成了一个 index.html 文件。

  5. 打开 index.html 文件,并用以下内容替换现有内容:

    html

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

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

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

    一个显示带有按钮的 HTML 表单的网页

    当您点击表单时,任务将从服务器加载,每秒出现一个。因此,表格将逐渐填充。您还可以通过在浏览器的 开发者工具 中打开 JavaScript 控制台 来查看已记录的消息。

    一个在点击按钮时显示列表项的网页

    通过此操作,您可以看到服务按预期运行。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. 导航到 plugins 包并打开 Sockets.kt 文件。
  4. 您现在可以通过利用 TaskRepository 来简化 Application.configureSockets() 中的路由:

    kotlin

通过 WebSockets 发送消息

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

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

    kotlin

    通过此代码,您完成了以下操作:

    • 将发送所有现有任务的功能重构为一个辅助方法。
    • routing 部分,您创建了一个线程安全的 session 对象 list,用于跟踪所有客户端。
    • 添加了一个相对 URL 为 /task2 的新端点。当客户端连接到 此端点时,相应的 session 对象会添加到 list 中。服务器 然后进入无限循环,等待接收新任务。收到新任务后,服务器将其存储在 repository 中,并将副本发送给所有客户端,包括当前客户端。

    为了测试此功能,您将创建一个新页面,扩展 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 中配置

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

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

  3. 导航到 src/test/kotlin/com/example 并打开 ApplicationTest.kt 文件。

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

    kotlin

    通过此设置,您:

    • 将您的服务配置为在测试环境中运行,并启用与生产环境相同的功能,包括路由、JSON 序列化和 WebSockets。
    • Ktor Client
      了解如何创建和配置 Ktor 客户端。
      中配置 Content Negotiation 和 WebSocket 支持。否则,客户端在使用 WebSocket 连接时将不知道如何(反)序列化 JSON 对象。
    • 声明您期望服务返回的 Tasks list。
    • 使用客户端对象的 websocket 方法向 /tasks 发送请求。
    • 将传入任务作为 flow 消费,并将其增量添加到 list 中。
    • 收到所有任务后,以常规方式比较 expectedTasksactualTasks

后续步骤

干得好!通过集成 WebSocket 通信和使用 Ktor Client 进行自动化测试,您显著增强了任务管理器服务。

继续

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