SwiftNIOUDPEchoClient 2.14.0

SwiftNIOUDPEchoClient 2.14.0

Daniel AlmGeorge BarnettJake PrickettCory BenfieldJohannes Weiss 维护。



  • 苹果公司,Inc.

SwiftNIO

SwiftNIO 是一个跨平台的异步事件驱动网络应用程序框架,可直接快速开发易于维护的高性能协议服务器和客户端。

它就像是 Netty,但是是用 Swift 编写的。

仓库结构

SwiftNIO项目跨多个仓库分割。

仓库 NIO 2 (Swift 5+) NIO 1 (Swift 4+)
https://github.com/apple/swift-nio
SwiftNIO 核心
从: "2.0.0" 从: "1.0.0"
https://github.com/apple/swift-nio-ssl
TLS (SSL) 支持
从: "2.0.0" 从: "1.0.0"
https://github.com/apple/swift-nio-http2
HTTP/2 支持
从: "1.0.0" 从: "0.1.0"
https://github.com/apple/swift-nio-extras
围绕 SwiftNIO 的有用补充
从: "1.0.0" 从: "0.1.0"
https://github.com/apple/swift-nio-transport-services
为 macOS、iOS 和 tvOS 提供一级支持
从: "1.0.0" 从: "0.1.0"

协议实现

以下是一些用 SwiftNIO 实现的协议列表。这是一份非详尽的列表,包括了 SwiftNIO 项目的一部分或被接受到 SSWG 的孵化流程中的协议。以下列出的所有库都使用 SwiftNIO 进行所有 I/O 操作,且以非阻塞方式执行。

低级协议实现

低级协议实现通常是一组实现一个协议但仍然需要用户对SwiftNIO有良好的理解的 ChannelHandler。通常,低级协议实现将被封装在具有更优雅、更用户友好的API的高级库中。

协议 客户端 服务器 仓库 模块 注释
HTTP/1 apple/swift-nio NIOHTTP1 官方NIO项目
HTTP/2 apple/swift-nio-http2 NIOHTTP2 官方NIO项目
WebSocket apple/swift-nio NIOWebSocket 官方NIO项目
TLS apple/swift-nio-ssl NIOSSL 官方NIO项目

高级实现

高级实现通常是带有API的库,该API不会公开SwiftNIO的 ChannelPipeline,因此可以几乎不需要(或不需要)SwiftNIO特定知识。列出的以下实现仍在SwiftNIO中执行所有I/O操作,并且与SwiftNIO生态系统高度集成。

协议 客户端 服务器 仓库 模块 注释
HTTP swift-server/async-http-client AsyncHTTPClient SSWG社区项目
gRPC grpc/grpc-swift GRPC 也提供低级API;社区项目
APNS kylebrowning/APNSwift APNSwift SSWG社区项目
PostgreSQL vapor/postgres-nio PostgresNIO SSWG社区项目
Redis mordil/swift-redi-stack RediStack SSWG社区项目

支持的平台

SwiftNIO旨在支持所有支持Swift的平台。目前,它在macOS和Linux上开发和测试,并且已知支持以下操作系统版本

Swift版本

SwiftNIO 1

最新发布的SwiftNIO 1版本支持Swift 4.0、4.1、4.2和5.0。

SwiftNIO 2

最新发布的SwiftNIO 2版本仅支持Swift 5.0、5.1和5.2。如果您希望将SwiftNIO 1的应用或库迁移到SwiftNIO 2,请查阅我们为您准备的迁移指南

兼容性

SwiftNIO遵循SemVer 2.0.0,并有一个单独的文档声明了SwiftNIO的公共API

这意味着您应该依赖于SwiftNIO,版本范围从您需要的最低SwiftNIO版本到下一个主要版本。在SwiftPM中,可以通过以下方式轻松实现:例如指定from: "2.0.0",这意味着您支持从2.0.0开始(不包括)3.0.0的每个版本。SemVer和SwiftNIO的公共API保证将导致一个无需担心兼容性的有效程序。

概念概述

SwiftNIO本质上是一个用于构建高性能网络应用的低级工具。它特别针对那些使用“每个连接一个线程”的并发模型效率低下或不可行的用例。当构建使用大量相对低使用率的连接的服务器时(如HTTP服务器),这是一个常见的限制。

为了实现其目标,SwiftNIO大量使用“非阻塞I/O”:因此得名!非阻塞I/O与众不同的地方在于,应用不等待数据在网络中发送或接收:相反,SwiftNIO请求内核通知它何时可以执行I/O操作而不需要等待。

SwiftNIO 并不旨在提供类似于网络框架之类的高级解决方案。相反,SwiftNIO 更加专注于为这些高级应用提供底层构建块。在构建网络应用时,大多数用户不会直接使用 SwiftNIO:他们会希望使用 Swift 生态系统中可用的众多优秀网络框架之一。然而,这些网络框架可能会选择在内层使用 SwiftNIO 来提供他们的网络支持。

以下章节将介绍 SwiftNIO 提供的底层工具,以及如何快速使用它们的概述。如果您对这些概念感到熟悉,那么您可以跳转到本 README 的其他部分。

基本架构

SwiftNIO 的基本构建块包括以下 8 种类型的对象

所有 SwiftNIO 应用最终都是由这些各种组件构成的。

事件循环和事件循环组

SwiftNIO 的基本 I/O 原语是事件循环。事件循环是一个对象,它等待事件(通常是 I/O 相关事件,如“数据接收”)发生,并在它们发生时触发某种类型的回调。几乎所有 SwiftNIO 应用中都会有相对较少的事件循环:通常每个应用希望使用的 CPU 内核只有一两个。总的来说,事件循环会运行整个应用的生命周期,无限循环地分发事件。

事件循环会被收集到事件循环 中。这些组提供了在工作之间进行分配的机制。例如,在监听入站连接时,监听套接字将在一个事件循环上注册。但是,我们不希望在该监听套接字上接受的所有连接都注册到同一个事件循环,因为这可能会造成一个事件循环过载,而其他则空闲。因此,事件循环组提供了在每个事件循环之间分散负载的能力。

在 SwiftNIO 当中,目前有一个 EventLoopGroup 实现,以及两个 EventLoop 实现。对于生产应用程序,有 MultiThreadedEventLoopGroup,这是一个创建多个线程的 EventLoopGroup(使用 POSIX 的 pthreads 库),在每个线程上放置一个 SelectableEventLoop。`SelectableEventLoop` 是一个事件循环,它使用选择器(根据目标系统是 kqueueepoll)来管理来自文件描述符的 I/O 事件并分派任务。此外,还有 EmbeddedEventLoop,这是一个主要用于测试目的的虚拟事件循环。

EventLoop 具有许多重要属性。最重要的是,它们是 SwiftNIO 应用程序中所有工作的执行方式。为了确保线程安全,几乎任何想要在 SwiftNIO 的其他对象上执行的工作必须通过一个 EventLoop 来分派。`EventLoop` 对象几乎拥有 SwiftNIO 应用程序中的所有其他对象,并且理解它们的执行模型对于构建高性能的 SwiftNIO 应用程序至关重要。

通道、通道处理器、通道流水线和通道上下文

虽然 EventLoop 对 SwiftNIO 的工作至关重要,但大部分用户将与它们进行实质性的交互仅仅限于请求它们创建 EventLoopPromise 以及安排工作。SwiftNIO 应用程序中大部分用户将花费最多时间与之互动的部分是 ChannelChannelHandler

在 SwiftNIO 程序中与用户交互的大多数文件描述符都与单个 Channel 相关联。该 Channel 拥有这个文件描述符,并负责管理其生命周期。它还负责处理该文件描述符上的入站和出站事件:每当事件循环具有与文件描述符对应的事件时,它将通知拥有该文件描述符的 Channel

然而,单独的 Channel 本身并没有什么用处。毕竟,很少有应用程序不想对其在套接字上发送或接收的数据进行任何操作!因此,另一个重要的 Channel 部分是 ChannelPipeline

一个ChannelPipeline是一系列称为ChannelHandler的对象序列,这些对象处理Channel上的事件。这些ChannelHandler按顺序处理这些事件,在每个处理过程中修改和转换事件。这可以被看作是一个数据处理管道;因此得名ChannelPipeline

所有ChannelHandler要么是入站处理器,要么是出站处理器,或者两者兼备。入站处理器处理“入站”事件:例如从套接字读取数据、读取套接字关闭或其他由远程对端发起的事件。出站处理器处理“出站”事件,例如写入、连接尝试和本地套接字关闭。

每个处理器按顺序处理事件。例如,读取事件从前面的管道依次传到后面,一次一个处理器,而写入事件则是从管道后面传到前面。每个处理器在任何时候都可能生成入站或出站事件,并将这些事件按照适当的方向发送给下一个处理器。这使得处理器可以分割读取、合并写入、延迟连接尝试以及通常对事件进行任意转换。

一般来说,ChannelHandler被设计成高度可重用的组件。这意味着它们往往是设计成尽可能小,只执行一种特定的数据转换。这允许处理器以新颖和灵活的方式组合在一起,从而有助于代码重用和封装。

ChannelHandler可以通过使用ChannelHandlerContext来跟踪它们在ChannelPipeline中的位置。这些对象包含对管道中前一个和下一个通道处理器的引用,确保一个ChannelHandler在管道中始终能够发出事件。

SwiftNIO内置了许多ChannelHandler,它们提供了有用的功能,例如HTTP解析。此外,高性能应用程序应该将它们的尽可能多的逻辑放在ChannelHandler中,因为它有助于避免上下文切换问题。

此外,SwiftNIO还提供了一些Channel实现。特别是,它提供了ServerSocketChannel,一个用于接受入站连接的ChannelSocketChannel,一个用于TCP连接的ChannelDatagramChannel,一个用于UDP套接字的Channel;以及EmbeddedChannel,一个主要用于测试的Channel

关于阻塞的一些说明

关于ChannelPipeline的一个重要注意事项是它们是线程安全的。这对于编写SwiftNIO应用程序非常重要,因为它允许你编写更简单的ChannelHandler,而无需担心同步。

然而,这是通过在同一个线程上派遣ChannelPipeline上的所有代码来实现的。这意味着,作为一般规则,ChannelHandler在调用未经派遣到后台线程的阻塞代码时,*严禁*。如果ChannelHandler由于任何原因而阻塞,则所有连接到父EventLoopChannel将无法继续前进,直到阻塞调用完成。

这是在编写SwiftNIO应用程序时的一大常见问题。如果你有用阻塞风格编写代码的需求,强烈建议你在管道中完成工作后将其派遣到不同的线程。

Bootstrap

尽管可以直接使用EventLoop配置和注册Channel,但通常更有用有一个更高层次的抽象来处理这项工作。

因此,SwiftNIO提供了一些具有简化通道创建目的的Bootstrap对象。一些Bootstrap对象还提供了其他功能,例如支持Happy Eyeballs进行TCP连接尝试。

目前,SwiftNIO包含三个Bootstrap对象:用于引导监听通道的ServerBootstrap;用于引导客户端TCP通道的ClientBootstrap;以及用于引导UDP通道的DatagramBootstrap

ByteBuffer

SwiftNIO应用程序的大部分工作涉及字节缓冲区的移动。至少,数据以字节缓冲区的形式在网络上发送和接收。因此,拥有一个适用于SwiftNIO应用程序执行工作的高性能数据结构非常重要。

因此,SwiftNIO提供了ByteBuffer,这是一个快速同步写入的字节缓冲区,构成了大多数SwiftNIO应用程序的关键构建模块。

ByteBuffer提供了许多有用的功能,并提供了多个钩子以“不安全模式”使用它。这关闭了边界检查,以提高性能,但可能导致应用程序面临内存正确性问题。

一般来说,强烈建议您始终使用ByteBuffer的安全模式。

有关ByteBuffer的API的更多详细信息,请参见下方的API文档。

承诺与未来

编写并发代码与编写同步代码的一个主要区别是,并非所有操作都会立即完成。例如,当您在通道上写入数据时,事件循环可能无法立即将该写入输出到网络。因此,SwiftNIO提供了EventLoopPromise<T>EventLoopFuture<T>来管理异步完成的操作。

EventLoopFuture<T>本质上是一个将填充为未来某个时间点的函数返回值的容器。每个EventLoopFuture<T>都有一个对应的EventLoopPromise<T>,这是将结果放入的对象。当承诺成功时,未来将得到满足。

如果您需要轮询未来以检测其何时完成,那将非常低效,因此EventLoopFuture<T>被设计为具有管理的回调。基本上,您可以在将结果可用时执行的回调上挂载Future。甚至EventLoopFuture<T>也会仔细安排调度,确保这些回调始终在最初创建承诺的事件循环上执行,这有助于确保您不需要围绕EventLoopFuture<T>回调进行太多同步。

另一个重要的考虑话题是传递给close的promise与在Channel上的closeFuture之间的工作方式有何不同。例如,传递给close的promise在Channel关闭后、在ChannelPipeline完全清空之前会成功执行。这允许在需要时在ChannelPipeline完全清空之前采取行动。如果希望等待Channel关闭并且在不进行其他任何操作的情况下清空ChannelPipeline,则更好的选择是等待closeFuture成功。

根据您希望它们何时以及如何执行,存在几个应用于EventLoopFuture<T>的回调函数。这些函数的详细信息留待API文档说明。

设计理念

SwiftNIO被设计成一个强大的工具,用于构建网络应用程序和框架,但并不是针对所有抽象层次层的完美解决方案。SwiftNIO关注提供低抽象层次的基础I/O原语和协议实现,将更具表现力但速度较慢的抽象留给更广泛的社区来构建。目的是让SwiftNIO成为一个服务器端应用程序的构建模块,而不是必用的框架。

需要从他们的网络堆栈中获得非常高的性能的应用程序可以选择直接使用SwiftNIO来减少抽象的开销。这些应用程序应该能够在相对较低的管理成本下保持极高的性能。SwiftNIO也专注于为这种用例提供有用的抽象,以直接构建极高性能的网络服务器。

SwiftNIO的核心仓库将直接包含一些非常重要的协议实现,如HTTP。然而,我们相信大多数协议实现应与底层数据包络的发布周期解耦,因为发布周期可能大不相同(要么快得多,要么慢得多)。因此,我们积极鼓励社区在不脱离体系结构的情况下开发和维护他们的协议实现。事实上,一些第一方SwiftNIO协议实现,包括我们的TLS和HTTP/2绑定,都是在体系结构之外开发的!

文档

示例用法

目前有几个示例项目演示了如何使用SwiftNIO。

要构建和运行它们,运行以下命令,将 TARGET_NAME 替换为 ./Sources 下的文件夹名称

swift run TARGET_NAME

例如,要运行 NIOHTTP1Server,请运行以下命令

swift run NIOHTTP1Server

入门指南

SwiftNIO主要使用SwiftPM作为其构建工具,因此我们建议使用它。如果您想在自己的项目中依赖SwiftNIO,只需在Package.swift中添加一个dependencies子句即可

dependencies: [
    .package(url: "https://github.com/apple/swift-nio.git", from: "2.0.0")
]

然后将适当的SwiftNIO模块添加到您的目标依赖项中。添加目标依赖项的语法在不同版本的Swift之间略有不同。例如,如果您想依赖NIONIOHTTP1模块,请指定以下依赖项

Swift 5.0 和 5.1 (swift-tools-version:5.[01])

dependencies: ["NIO", "NIOHTTP1"]

Swift 5.2(《swift-tools-version:5.2》)

dependencies: [.product(name: "NIO", package: "swift-nio"),
               .product(name: "NIOHTTP1", package: "swift-nio")]

使用 Xcode 包支持

如果你的项目设置为 Xcode 项目,并且你正在使用 Xcode 11 或更高版本,你可以通过点击“文件” -> “Swift 包” -> “添加包依赖”将 SwiftNIO 添加为你 Xcode 项目的依赖。在即将出现的对话框中,请输入https://github.com/apple/swift-nio.git并连续点击两次“下一步”。最后,选择你计划使用的目标(例如 NIONIOHTTP1NIOFoundationCompat)并点击完成。现在你可以在项目中导入 NIO(以及你选中的所有其他目标)。

要实际在 SwiftNIO 上进行开发,或者研究一些示例应用程序,您可以直接克隆存储库,并使用 SwiftPM 帮助构建它。例如,您可以使用以下命令编译和运行示例 echo 服务器

swift build
swift test
swift run NIOEchoServer

为了验证它是否正常工作,可以使用另一个 shell 尝试连接到它。

echo "Hello SwiftNIO" | nc localhost 9999

如果一切顺利,您将看到消息被回显。

要在 Xcode 11+ 中开发 SwiftNIO,您只需在 Xcode 中打开 Package.swift 文件,并使用 Xcode 对 SwiftPM 包的支持。

如果您想使用 Xcode 10 开发 SwiftNIO,您必须生成 Xcode 项目。

swift package generate-xcodeproj

替代方案:使用 docker-compose

此外,您可能还想要使用 docker-compose 进行开发和测试。

首先确保您已经安装了 Docker,然后运行以下命令

  • docker-compose -f docker/docker-compose.yaml run test

    会创建一个包含 Swift 运行时和其他构建和测试依赖项的基本映像,编译 SwiftNIO 并运行单元测试和集成测试

  • docker-compose -f docker/docker-compose.yaml up echo

    会创建一个包含 Swift 运行时和其他构建和测试依赖项的基本映像,编译 SwiftNIO,并在 localhost:9999 上运行一个示例 NIOEchoServer。通过 echo Hello SwiftNIO | nc localhost 9999 测试它。

  • docker-compose -f docker/docker-compose.yaml up http

    会创建一个包含 Swift 运行时和其他构建和测试依赖项的基本映像,编译 SwiftNIO,并在 localhost:8888 上运行一个示例 NIOHTTP1Server。通过 curl http://localhost:8888 测试它。

开发 SwiftNIO

注意:本节仅适用于您想自己开发 SwiftNIO 的情况。如果您只想将 SwiftNIO 作为 SwiftPM 包使用,则可以忽略此处信息。

就大部分而言,SwiftNIO 的开发与其他 SwiftPM 项目的开发一样直观。不过,在您贡献之前,还有一些流程需要您理解。具体细节请参阅该仓库中的 CONTRIBUTING.md

先决条件

SwiftNIO 的 master 分支是 SwiftNIO 2 下一个版本的开发分支,它仅支持 Swift 5。

要编译和运行 SwiftNIO 及其集成测试,您需要在您的系统上安装一些先决条件。

macOS

  • Xcode 10.2 或更高版本,推荐使用 Xcode 11。

Linux

  • Swift 5.0, 5.1 或 5.2,从 swift.org/download 下载。我们始终推荐使用最新发布的版本。
  • netcat(仅用于集成测试)
  • lsof(仅用于集成测试)
  • shasum(仅用于集成测试)

Ubuntu 18.04

# install swift tarball from https://swift.org/downloads
apt-get install -y git curl libatomic1 libxml2 netcat-openbsd lsof perl

Fedora 28+

dnf install swift-lang /usr/bin/nc /usr/bin/lsof /usr/bin/shasum