SwiftNIO 是一个跨平台的异步事件驱动网络应用框架,用于快速开发可维护的高性能协议服务器和客户端。
它就像 Netty,但它是用 Swift 编写的。
SwiftNIO 项目被拆分为多个仓库
仓库 | NIO 2 (Swift 5.7+) |
---|---|
https://github.com/apple/swift-nio SwiftNIO 核心 |
from: "2.0.0" |
https://github.com/apple/swift-nio-ssl TLS (SSL) 支持 |
from: "2.0.0" |
https://github.com/apple/swift-nio-http2 HTTP/2 支持 |
from: "1.0.0" |
https://github.com/apple/swift-nio-extras 围绕 SwiftNIO 的有用补充 |
from: "1.0.0" |
https://github.com/apple/swift-nio-transport-services 对 macOS、iOS、tvOS 和 watchOS 的一流支持 |
from: "1.0.0" |
https://github.com/apple/swift-nio-ssh SSH 支持 |
.upToNextMinor(from: "0.2.0") |
NIO 2.29.0 及更早版本支持 Swift 5.0+,NIO 2.39.0 及更早版本支持 Swift 5.2+。
在这个仓库中,我们有许多提供不同功能的产品。 此软件包包含以下产品
NIO
。 这是一个 umbrella 模块,导出了 NIOCore
、NIOEmbedded
和 NIOPosix
。NIOCore
。 这为使用 SwiftNIO 提供了核心抽象和类型(有关更多详细信息,请参见“概念概述”)。 大多数 NIO 扩展项目(提供诸如新的 EventLoop
和 Channel
或新的协议实现)应该只需要依赖 NIOCore
。NIOPosix
。 这为基于 POSIX 的系统提供了主要的 [EventLoopGroup
]、EventLoop
和 Channel
。 这是我们的高性能核心 I/O 层。 通常,这应该仅由计划进行一些实际 I/O 的项目导入,例如高级协议实现或应用程序。NIOEmbedded
。 这提供了 EmbeddedChannel
和 EmbeddedEventLoop
,它们是 NIOCore
抽象的实现,可以对它们的执行进行细粒度的控制。 这些最常用于测试,但也可以用于以与网络完全分离的方式驱动协议实现。NIOConcurrencyHelpers
。 这提供了一些 NIO 实现使用的低级并发原语,例如锁和原子操作。NIOFoundationCompat
。 这扩展了许多 NIO 类型,以更好地与 Foundation 数据类型互操作。 如果您正在使用 Foundation 数据类型(例如 Data
),则应导入此模块。NIOTLS
。 这为使用多个 TLS 实现提供了一些常见的抽象类型。 请注意,这本身并不提供 TLS:请研究 swift-nio-ssl 和 swift-nio-transport-services 以获取具体的实现。NIOHTTP1
。 这提供了低级 HTTP/1.1 协议实现。NIOWebSocket
。 这提供了低级 WebSocket 协议实现。NIOTestUtils
。 这为使用 SwiftNIO 的测试项目提供了许多帮助程序。NIOFileSystem
。 这提供了用于与文件系统交互的 async
API。下面您可以找到一些使用 SwiftNIO 完成的协议实现的列表。 这是 SwiftNIO 项目的一部分或被接受到 SSWG 孵化过程中的协议的非详尽列表。 下面列出的所有库都使用 SwiftNIO 以非阻塞方式执行其所有 I/O。
底层协议实现通常是 ChannelHandler
的集合,它们实现了协议,但仍然要求用户对 SwiftNIO 有很好的理解。 通常,底层协议实现将被包装在具有更好、更用户友好的 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 项目 |
SSH | ✅ | ✅ | apple/swift-nio-ssh | NIOSSH |
官方 NIO 项目 |
高级实现通常是带有 API 的库,该 API 不公开 SwiftNIO 的 ChannelPipeline
,因此可以在很少(或没有)SwiftNIO 特定知识的情况下使用。 下面列出的实现仍然在 SwiftNIO 中执行其所有 I/O,并且与 SwiftNIO 生态系统集成得非常好。
协议 | 客户端 (发送请求) |
服务器 (响应请求) |
仓库 | 模块 | 注释 |
---|---|---|---|---|---|
HTTP | ✅ | ❌ | swift-server/async-http-client | AsyncHTTPClient |
SSWG 社区项目 |
gRPC | ✅ | ✅ | grpc/grpc-swift | GRPC |
还提供了一个低级 API; SSWG 社区项目 |
APNS | ✅ | ❌ | swift-server-community/APNSwift | APNSwift |
SSWG 社区项目 |
PostgreSQL | ✅ | ❌ | vapor/postgres-nio | PostgresNIO |
SSWG 社区项目 |
Redis | ✅ | ❌ | swift-server/RediStack | RediStack |
SSWG 社区项目 |
这是 SwiftNIO 的当前版本,将在可预见的未来得到支持。
我们承诺支持最新发布的 swift 版本(目前为 5.10)以及之前的两个次要版本,除非在一个代码库中无法做到这一点。 此外,还会针对最新的测试版(如果有)以及 nightly swift 构建运行检查,目的是让这些检查通过。
最新版本的 SwiftNIO 支持 Swift 5.9 及更高版本。 SwiftNIO 版本支持的最低 Swift 版本在下面详细说明
SwiftNIO | 最低 Swift 版本 |
---|---|
2.0.0 ..< 2.30.0 |
5.0 |
2.30.0 ..< 2.40.0 |
5.2 |
2.40.0 ..< 2.43.0 |
5.4 |
2.43.0 ..< 2.51.0 |
5.5.2 |
2.51.0 ..< 2.60.0 |
5.6 |
2.60.0 ..< 2.65.0 |
5.7 |
2.65.0 ..< 2.76.0 |
5.8 |
2.76.0 ... |
5.9 |
SwiftNIO 1 已被视为生命周期结束 - 强烈建议您迁移到更新的版本。 Core NIO 团队不积极从事此版本的工作。 不会向此版本添加任何新功能,但在 2022 年 5 月底之前,我们将接受修复错误或安全漏洞的 PR。
如果您有想要迁移到 SwiftNIO 2 的 SwiftNIO 1 应用程序或库,请查看我们为您准备的迁移指南。
最新发布的 SwiftNIO 1 版本支持 Swift 4.0、4.1、4.2 和 5.0。
SwiftNIO 旨在支持所有 Swift 支持的平台。 目前,它在 macOS 和 Linux 上开发和测试,并且已知支持以下操作系统版本
SwiftNIO 遵循 SemVer 2.0.0,并附带一份单独的文档声明 SwiftNIO 的公共 API。
这对您意味着,您应该使用一个版本范围依赖于 SwiftNIO,该范围涵盖从您需要的最低 SwiftNIO 版本到下一个主要版本的所有内容。 在 SwiftPM 中,可以通过指定例如 from: "2.0.0"
轻松地做到这一点,这意味着您支持从 2.0.0 到(不包括)3.0.0 的每个版本的 SwiftNIO。 SemVer 和 SwiftNIO 的公共 API 保证应该会产生一个可工作的程序,而无需担心测试每个版本的兼容性。
SwiftNIO 从根本上来说是一个用于在 Swift 中构建高性能网络应用程序的底层工具。 它特别针对那些使用“每个连接一个线程”的并发模型效率低下或不可行的用例。 这是在构建使用大量相对低利用率连接的服务器(例如 HTTP 服务器)时常见的限制。
为了实现其目标,SwiftNIO 广泛使用“非阻塞 I/O”:因此得名! 非阻塞 I/O 与更常见的阻塞 I/O 模型不同,因为应用程序不等待数据发送到网络或从网络接收数据:相反,SwiftNIO 要求内核在可以在不等待的情况下执行 I/O 操作时通知它。
SwiftNIO 并不旨在提供像 Web 框架那样的高级解决方案。 相反,SwiftNIO 专注于为这些更高级别的应用程序提供底层构建块。 在构建 Web 应用程序时,大多数用户不希望直接使用 SwiftNIO:相反,他们希望使用 Swift 生态系统中可用的许多出色的 Web 框架之一。 但是,这些 Web 框架可能会选择在底层使用 SwiftNIO 来提供其网络支持。
以下各节将描述 SwiftNIO 提供的底层工具,并快速概述如何使用它们。 如果您对这些概念感到满意,则可以跳到本 README 的其他部分。
SwiftNIO 的基本构建块是以下 8 种类型的对象
EventLoopGroup
,一个协议,由 NIOCore
提供。EventLoop
,一个协议,由 NIOCore
提供。Channel
,一个协议,由 NIOCore
提供。ChannelHandler
,一个协议,由 NIOCore
提供。Bootstrap
,几个相关的结构,由 NIOCore
提供。ByteBuffer
,一个结构体,由 NIOCore
提供。EventLoopFuture
,一个泛型类,由 NIOCore
提供。EventLoopPromise
,一个泛型结构体,由 NIOCore
提供。所有 SwiftNIO 应用程序最终都是由这些各种组件构建而成。
SwiftNIO 的基本 I/O 原语是事件循环。 事件循环是一个对象,它等待事件(通常是与 I/O 相关的事件,例如“接收到数据”)发生,然后在事件发生时触发某种回调。 在几乎所有 SwiftNIO 应用程序中,事件循环相对较少:通常每个应用程序要使用的 CPU 核心只有一到两个。 一般来说,事件循环在应用程序的整个生命周期内运行,在一个无尽的循环中分派事件。
事件循环被收集到事件循环组中。 这些组提供了一种机制来分配事件循环周围的工作。 例如,在侦听入站连接时,侦听套接字将在一个事件循环上注册。 但是,我们不希望在该侦听套接字上接受的所有连接都注册到同一个事件循环,因为这可能会使一个事件循环过载,而使其他事件循环空闲。 因此,事件循环组提供了跨多个事件循环分配负载的能力。
在今天的 SwiftNIO 中,有一个 EventLoopGroup
实现,和两个 EventLoop
实现。对于生产环境应用,有 MultiThreadedEventLoopGroup
,这是一个 EventLoopGroup
,它会创建多个线程(使用 POSIX 的 pthreads
库),并在每个线程上放置一个 SelectableEventLoop
。SelectableEventLoop
是一个事件循环,它使用选择器(根据目标系统,可以是 kqueue
或者 epoll
)来管理文件描述符的 I/O 事件并分派工作。这些 EventLoop
和 EventLoopGroup
由 NIOPosix
模块提供。此外,还有 EmbeddedEventLoop
,这是一个用于测试目的的虚拟事件循环,由 NIOEmbedded
模块提供。
EventLoop
具有许多重要的属性。最重要的是,它们是在 SwiftNIO 应用程序中完成所有工作的方式。为了确保线程安全,任何想要在 SwiftNIO 中的几乎任何其他对象上完成的工作都必须通过 EventLoop
分派。EventLoop
对象拥有 SwiftNIO 应用程序中几乎所有其他对象,并且理解它们的执行模型对于构建高性能 SwiftNIO 应用程序至关重要。
虽然 EventLoop
对于 SwiftNIO 的工作方式至关重要,但大多数用户与之交互的方式不会超出请求它们创建 EventLoopPromise
并安排工作。 SwiftNIO 应用程序中大多数用户花费最多时间交互的部分是 Channel
和 ChannelHandler
。
在 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
,这是一个用于接受入站连接的套接字的 Channel
; SocketChannel
,一个用于 TCP 连接的 Channel
; 以及 DatagramChannel
,一个用于 UDP 套接字的 Channel
。 所有这些都由 NIOPosix
模块提供。 它还提供 EmbeddedChannel
,这是一个主要用于测试的 Channel
,由 NIOEmbedded
模块提供。
关于 ChannelPipeline
的一个重要说明是它们是线程安全的。 这对于编写 SwiftNIO 应用程序非常重要,因为它允许您编写更简单的 ChannelHandler
,因为您知道它们不需要同步。
但是,这是通过在与 EventLoop
相同的线程上分派 ChannelPipeline
上的所有代码来实现的。 这意味着,作为一般规则,ChannelHandler
**绝不能** 在不将其分派到后台线程的情况下调用阻塞代码。 如果 ChannelHandler
由于任何原因而阻塞,则连接到父 EventLoop
的所有 Channel
将无法继续,直到阻塞调用完成。
这是编写 SwiftNIO 应用程序时一个常见的问题。 如果以阻塞样式编写代码很有用,则强烈建议您在管道中完成工作后将工作分派到不同的线程。
虽然可以直接使用 EventLoop
配置和注册 Channel
,但通常使用更高级别的抽象来处理此工作更有用。
因此,SwiftNIO 附带了许多 Bootstrap
对象,其目的是简化通道的创建。 一些 Bootstrap
对象还提供其他功能,例如支持 Happy Eyeballs 以进行 TCP 连接尝试。
目前,SwiftNIO 在 NIOPosix
模块中附带了三个 Bootstrap
对象: ServerBootstrap
,用于引导监听通道; ClientBootstrap
,用于引导客户端 TCP 通道; 以及 DatagramBootstrap
,用于引导 UDP 通道。
SwiftNIO 应用程序中的大部分工作都涉及来回传递字节缓冲区。 至少,数据以字节缓冲区的形式发送和接收到网络或从网络发送和接收。 因此,拥有一个针对 SwiftNIO 应用程序执行的那种工作进行优化的高性能数据结构非常重要。
因此,SwiftNIO 提供了 ByteBuffer
,这是一个快速的写时复制字节缓冲区,是大多数 SwiftNIO 应用程序的关键构建块。此类型由 NIOCore
模块提供。
ByteBuffer
提供了许多有用的特性,此外还提供了许多钩子,以便在“不安全”模式下使用它。这会关闭边界检查以提高性能,但代价是可能会使您的应用程序面临内存正确性问题。
通常,强烈建议您始终在安全模式下使用 ByteBuffer
。
有关 ByteBuffer
的 API 的更多详细信息,请参阅我们的 API 文档,链接如下。
编写并发代码和编写同步代码的一个主要区别是,并非所有操作都会立即完成。 例如,当您在通道上写入数据时,事件循环可能无法立即将该写入刷新到网络。 因此,SwiftNIO 提供了 EventLoopPromise<T>
和 EventLoopFuture<T>
来管理异步完成的操作。 这些类型由 NIOCore
模块提供。
EventLoopFuture<T>
本质上是函数返回值的容器,该返回值将在未来的某个时间填充。 每个 EventLoopFuture<T>
都有一个对应的 EventLoopPromise<T>
,它是结果将被放入的对象。 当 promise 成功时,future 将会被实现。
如果您必须轮询 future 以检测它何时完成,那将非常低效,因此 EventLoopFuture<T>
被设计为具有托管回调。 本质上,您可以从 future 链接回调,这些回调将在结果可用时执行。 EventLoopFuture<T>
甚至会仔细安排调度,以确保这些回调始终在最初创建 promise 的事件循环上执行,这有助于确保您不需要围绕 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,只需将 dependencies
子句添加到您的 Package.swift
中即可
dependencies: [
.package(url: "https://github.com/apple/swift-nio.git", from: "2.0.0")
]
然后将相应的 SwiftNIO 模块添加到您的目标依赖项。 添加目标依赖项的语法在 Swift 版本之间略有不同。 例如,如果您想依赖 NIOCore
、NIOPosix
和 NIOHTTP1
模块,请指定以下依赖项
dependencies: [.product(name: "NIOCore", package: "swift-nio"),
.product(name: "NIOPosix", package: "swift-nio"),
.product(name: "NIOHTTP1", package: "swift-nio")]
如果您的项目设置为 Xcode 项目并且您正在使用 Xcode 11+,您可以通过单击 File -> Swift Packages -> Add Package Dependency 将 SwiftNIO 添加为 Xcode 项目的依赖项。 在即将出现的对话框中,请输入 https://github.com/apple/swift-nio.git
并单击 Next 两次。 最后,选择您计划使用的目标(例如 NIOCore
、NIOHTTP1
和 NIOFoundationCompat
)并单击完成。 现在您将能够在您的项目中 import NIOCore
(以及您选择的所有其他目标)。
要处理 SwiftNIO 本身,或调查一些演示应用程序,您可以直接克隆存储库并使用 SwiftPM 帮助构建它。 例如,您可以运行以下命令来编译和运行示例 echo 服务器
swift build
swift test
swift run NIOEchoServer
要验证它是否工作,您可以使用另一个 shell 尝试连接到它
echo "Hello SwiftNIO" | nc localhost 9999
如果一切顺利,您将看到消息回显给您。
要在 Xcode 中处理 SwiftNIO,您可以直接在 Xcode 中打开 Package.swift
文件并使用 Xcode 对 SwiftPM Package 的支持。
或者,您可能想使用 docker-compose
进行开发或测试。
首先确保您已安装 Docker,接下来运行以下命令
docker-compose -f docker/docker-compose.yaml run test
将创建一个具有 Swift 运行时和其他构建和测试依赖项的基础镜像,编译 SwiftNIO 并运行单元和集成测试
docker-compose -f docker/docker-compose.yaml up echo
将创建一个基础镜像,编译 SwiftNIO,并在 localhost:9999
上运行示例 NIOEchoServer
。 通过 echo Hello SwiftNIO | nc localhost 9999
进行测试。
docker-compose -f docker/docker-compose.yaml up http
将创建一个基础镜像,编译 SwiftNIO,并在 localhost:8888
上运行示例 NIOHTTP1Server
。 通过 curl https://:8888
进行测试
docker-compose -f docker/docker-compose.yaml -f docker/docker-compose.2204.57.yaml run test
将使用 Ubuntu 22.04 和 Swift 5.7 创建一个基础镜像,编译 SwiftNIO 并运行单元和集成测试。 docker 目录中存在适用于其他 Ubuntu 和 swift 版本的文件。
注意:如果您想自己开发 SwiftNIO,则本节仅相关。 如果您只想将 SwiftNIO 用作 SwiftPM 包,则可以忽略此处的信息。
在大多数情况下,SwiftNIO 开发与任何其他 SwiftPM 项目一样简单。 话虽如此,我们在您贡献之前确实有一些值得理解的流程。 有关详细信息,请参阅此存储库中的 CONTRIBUTING.md
。
SwiftNIO 的 main
分支是 SwiftNIO 2 的下一个版本的开发分支,它仅限 Swift 5 使用。
为了能够编译和运行 SwiftNIO 和集成测试,您需要在您的系统上安装一些先决条件。
# install swift tarball from https://swiftlang.cn/downloads
apt-get install -y git curl libatomic1 libxml2 netcat-openbsd lsof perl
dnf install swift-lang /usr/bin/nc /usr/bin/lsof /usr/bin/shasum
swift-nio
的基准测试位于本仓库 Benchmarks
子文件夹中的一个独立的 Swift Package 中。它们使用 package-benchmark
插件。基准测试依赖于 jemalloc
内存分配库,package-benchmark
使用该库来捕获内存分配统计信息。安装指南可以在 package-benchmark
的 入门文章 中找到。之后,您可以通过转到 Benchmarks
子文件夹(例如 cd Benchmarks
)并调用以下命令,从 CLI 运行基准测试:
swift package benchmark
有关更多信息,请参阅 swift package benchmark --help
或 package-benchmark
的文档。