SwiftNIO
SwiftNIO 是一个跨平台异步事件驱动的网络应用程序框架,用于快速开发可维护的高性能协议服务器和客户端。
类似于 Netty,但专为 Swift 编写。
代码库组织
SwiftNIO 项目分散在多个代码库中
代码库 | NIO 2 (Swift 5.2+) |
---|---|
https://github.com/apple/swift-nio SwiftNIO 核心 |
版本: "2.0.0" |
https://github.com/apple/swift-nio-ssl 支持 TLS (SSL) |
版本: "2.0.0" |
https://github.com/apple/swift-nio-http2 支持 HTTP/2 |
版本: "1.0.0" |
https://github.com/apple/swift-nio-extras 围绕 SwiftNIO 的有用补充 |
版本: "1.0.0" |
https://github.com/apple/swift-nio-transport-services 对 macOS、iOS、tvOS 和 watchOS 的第一级支持 |
版本: "1.0.0" |
https://github.com/apple/swift-nio-ssh 支持 SSH |
.upToNextMinor(from: "0.2.0") |
NIO 2.29.0 及更早版本支持 Swift 5.0+。
在此代码库中,我们有一些提供不同功能的产品。本软件包包含以下产品
NIO
。这是一个伞形模块,导出NIOCore
、NIOEmbedded
和NIOPosix
。NIOCore
。它提供了使用 SwiftNIO 的核心抽象和类型(有关更多详细信息,请参阅 "概念概述")。大多数提供新EventLoop
和Channel
或新协议实现等内容的 NIO 扩展项目只需依赖于NIOCore
即可。NIOPosix
。它提供了主要用于 POSIX-based 系统的 [ 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 的测试项目提供了许多辅助工具。
协议实现
下面你可以找到一些使用 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 | n/a | 官方 NIO 项目 |
高级实现
高级实现通常是一组库,带有不暴露 SwiftNIO 的 ChannelPipeline
的 API,因此可以 仅需要非常少(或没有)SwiftNIO 特定知识即可使用。下面列出的实现仍然完全使用 SwiftNIO 进行 I/O 操作,并且与 SwiftNIO 生态系统集成得很好。
协议 | 客户端 | 服务器 | 代码库 | 模块 | 评论 |
---|---|---|---|---|---|
HTTP | swift-server/async-http-client | AsyncHTTPClient |
SSWG 社区项目 | ||
gRPC | grpc/grpc-swift | GRPC |
还提供低级 API;SSWG 社区项目 | ||
APNS | kylebrowning/APNSwift | APNSwift |
SSWG 社区项目 | ||
PostgreSQL | vapor/postgres-nio | PostgresNIO |
SSWG 社区项目 | ||
Redis | mordil/swift-redi-stack | RediStack |
SSWG 社区项目 |
支持版本
SwiftNIO 2
这是 SwiftNIO 的当前版本,未来还将得到支持。
最新发布的 SwiftNIO 2 版本支持 Swift 5.2+。NIO 2.29.0 及旧版本支持 Swift 5.0+。
SwiftNIO 1
SwiftNIO 1 已被认为是已到生命周期的最后阶段 - 强烈建议您迁移到 newer version. Core NIO 团队不会积极开发此版本。不会添加该版本的新功能,但将接受直到 2022 年 5 月底的修复错误或安全漏洞的 PR。
如果您有一个想要迁移到 SwiftNIO 2 的 SwiftNIO 1 应用程序或库,请查看我们为您准备的 迁移指南。
最新发布的 SwiftNIO 1 版本支持 Swift 4.0,4.1,4.2 和 5.0。
支持的平台
SwiftNIO旨在支持所有支持Swift的平台。目前,它在macOS和Linux上进行开发和测试,并知道支持以下操作系统版本
- Ubuntu 18.04+
- macOS 10.9+, iOS 7+; (macOS 10.14+, iOS 12+, tvOS 12+或watchOS 6+ 与 swift-nio-transport-services 一起)
兼容性
SwiftNIO遵循 SemVer 2.0.0 并有一个单独的文档声明了 SwiftNIO的公共API。
这意味着您应该使用一个版本范围来依赖SwiftNIO,它从您要求的最低SwiftNIO版本开始,直到下一个主要版本。在SwiftPM中,这可以通过指定例如from: "2.0.0"
来实现,这意味着您支持从2.0.0开始的每个版本,直到(不包括)3.0.0。SemVer和SwiftNIO的公共API担保应确保程序的正常运行,无需担心每个版本的兼容性问题。
概念概述
SwiftNIO本质上是用于在Swift中构建高性能网络应用的低级工具。它特别针对那些在并发度中)使用“每个连接一个线程”模型效率低或不可行的情况。这在构建使用大量相对低频使用的连接的服务器时是一个常见的限制,例如HTTP服务器。
为了实现其目标,SwiftNIO广泛使用“非阻塞I/O”——正是名字的含义!非阻塞I/O与更常见的阻塞I/O模型的区别在于,应用程序不会等待将数据发送到网络或从网络接收数据:相反,SwiftNIO请求内核在I/O操作可以执行而不需要等待时通知它。
SwiftNIO并不旨在提供类似于网页框架的高层解决方案。相反,SwiftNIO专注于为这些更高级的应用程序提供低级构建块。在构建网页应用程序时,大多数用户不会想直接使用SwiftNIO:相反,他们想使用在Swift生态系统中有许多优秀的网页框架。然而,这些网页框架可能会选择在底层使用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
都可能是 Inbound 或 Outbound 处理器,或者两者都是。Inbound 处理器处理“进入”事件:例如,从套接字读取数据、读取套接字关闭或其他由远程对端发起的事件。Outbound 处理器处理“外出”事件,如写入、连接尝试和本地套接字关闭。
每个处理器按顺序处理事件。例如,读取事件从前端管道向后端管道传递,每次一个处理器,而写入事件则从后端管道向前端管道传递。每个处理器可能在任何时间生成进入或外出事件,并将事件发送到下一个处理器,方向恰当即可。这样允许处理器拆分读取、合并写入、延迟连接尝试,以及在事件上进行任意转换。
总的来说,ChannelHandler
被设计成高度可重用的组件。这意味着它们往往被设计得尽可能小,执行一个特定的数据转换。这使得处理器以新颖和灵活的方式组合在一起,有助于代码重用和封装。
ChannelHandler
能够通过使用 ChannelHandlerContext
来跟踪其在 ChannelPipeline
中的位置。这些对象包含对管道中前一个和下一个通道处理器的引用,确保通道处理器在管道中可以随时发出事件。
SwiftNIO自带了许多内置的 ChannelHandler
,它们提供了有用的功能,例如 HTTP 解析。此外,高性能应用将想要尽可能将其逻辑放在 ChannelHandler
中,因为它有助于避免上下文切换的问题。
此外,SwiftNIO还自带了一些Channel
实现。特别是,它自带了ServerSocketChannel
,这是一个用于接受传入连接的Channel
;SocketChannel
,这是一个用于TCP连接的Channel
;以及DatagramChannel
,这是一个用于UDP套接字的Channel
。所有这些都由 NIOPosix
模块提供。它还提供了EmbeddedChannel
,一个主要由NIOEmbedded
模块提供的用于测试的Channel
。
关于阻塞的说明
关于ChannelPipeline
的一个重要注意事项是它们是线程安全的。这对于编写SwiftNIO应用程序非常重要,因为它允许您在不需要进行同步的情况下编写更简单的ChannelHandler
。
然而,这是通过在同一个线程中调度所有与ChannelPipeline
相关的代码来实现的。这意味着,作为一般规则,ChannelHandler
必须不调用阻塞代码,除非将它们调度到后台线程。如果由于任何原因,ChannelHandler
发生阻塞,则附加到父EventLoop
的所有Channel
都将无法继续直到阻塞调用完成。
在编写SwiftNIO应用程序时,这是一个常见的问题。如果您需要以阻塞方式编写代码,强烈建议您在您的pipeline中处理完毕后将工作调度到不同的线程。
启动
虽然可以直接使用Channel
与EventLoop
进行配置和注册,但通常更实用的是有一个更高层次的抽象来处理这项工作。
因此,SwiftNIO提供了一系列Bootstrap
对象,其目的是简化通道创建。一些Bootstrap
对象还提供了其他功能,例如通过支持Happy Eyeballs来实现TCP连接尝试。
当前SwiftNIO在NIOPosix模块中包含三个Bootstrap
对象:ServerBootstrap
,用于初始化监听通道;ClientBootstrap
,用于初始化客户端TCP通道;以及DatagramBootstrap
,用于初始化UDP通道。
ByteBuffer
在SwiftNIO应用程序中,大部分工作涉及到在字节缓冲区之间进行转换。至少,数据以字节缓冲区的形式在网络中进行发送和接收。因此,有一个高性能的数据结构,该结构针对SwiftNIO应用程序所执行的工作类型进行了优化,这一点非常重要。
因此,SwiftNIO提供ByteBuffer
——一个快速的copy-on-write字节缓冲区,它是大多数SwiftNIO应用程序的关键构建块。这个类型由NIOCore
模块提供。
ByteBuffer
提供了一些有用功能,并且还提供了几个钩子,以便在“不安全”模式下使用。这会关闭边界检查,以改善性能,但可能会使应用程序面临内存正确性问题的风险。
一般情况下,强烈建议您始终以安全模式使用ByteBuffer
。
有关ByteBuffer
的API的更多详细信息,请参阅下面的API文档。
承诺和未来
编写并发代码和同步代码之间的一个主要区别是,并非所有操作都会立即完成。例如,当你向通道写入数据时,事件循环可能无法立即将该写入刷新到网络。因此,SwiftNIO提供EventLoopPromise<T>
和EventLoopFuture<T>
来管理异步完成操作。这些类型由NIOCore
模块提供。
EventLoopFuture<T>
本质上是函数返回值的容器,该函数会在将来的某个时间填充。每个EventLoopFuture<T>
都有一个相应的EventLoopPromise<T>
,结果是放置在这个对象中。当承诺成功时,未来就会得到兑现。
如果需要轮询未来来检测何时完成,则这种方法将非常低效,因此 EventLoopFuture<T>
被设计成具有管理的回调功能。本质上,您可以在未来上挂接回调函数,并在结果可用时执行。甚至 EventLoopFuture<T>
还会仔细安排调度以确保这些回调总是在最初创建承诺的事件循环上执行,这有助于确保您不需要围绕 EventLoopFuture<T>
回调进行过多的同步。
另一个需要考虑的重要话题是,与传递给 Channel
的 closeFuture
相比,close
传递的承诺有何不同。例如,传递给 close
的承诺在关闭 Channel
后将成功,但在 ChannelPipeline
完全清除之前。这将允许您在清除之前采取对 ChannelPipeline
的措施。如果需要等待 Channel
关闭且 ChannelPipeline
清除,而不采取任何进一步操作,则更好的选择是等待 closeFuture
成功。
根据您希望它们如何以及何时执行,有几个函数可以将回调应用于 EventLoopFuture<T>
。这些函数的详细信息留待 API 文档。
设计理念
SwiftNIO 被设计成是构建网络应用程序和框架的强大工具,但不是所有抽象级别的完美解决方案。SwiftNIO 集中于提供低抽象层的基本 I/O 原语和协议实现,将更多表达性但更慢的抽象留给更广泛的社区来构建。目的是使 SwiftNIO 成为服务器端应用程序的构建块,而不是应用程序必须直接使用的框架。
需要在他们的网络堆栈中获得极高性能的应用程序可以选择直接使用 SwiftNIO 以减少其抽象的开销。这些应用程序应该能够以相对较低的维护成本保持极高的性能。SwiftNIO 还专注于为此用途案例提供有用的抽象,从而可以直接构建出极高性能的网络服务器。
核心 SwiftNIO 存储库将包含一些极端重要的协议实现,如 HTTP,直接在树中。然而,我们认为大多数协议实现应与底层网络堆栈的发布周期解耦,因为发布周期可能非常不同(要么速度非常快,要么非常慢)。因此,我们积极鼓励社区开发和维护他们的协议实现作为外部树。事实上,一些第一方 SwiftNIO 协议实现,包括我们的 TLS 和 HTTP/2 绑定,都是作为外部树开发的!
文档
使用示例
当前有几个示例项目演示了如何使用SwiftNIO。
- 聊天客户端 https://github.com/apple/swift-nio/tree/main/Sources/NIOChatClient
- 聊天服务器 https://github.com/apple/swift-nio/tree/main/Sources/NIOChatServer
- 回声客户端 https://github.com/apple/swift-nio/tree/main/Sources/NIOEchoClient
- 回声服务器 https://github.com/apple/swift-nio/tree/main/Sources/NIOEchoServer
- UDP 回声客户端 https://github.com/apple/swift-nio/tree/main/Sources/NIOUDPEchoClient
- UDP 回声服务器 https://github.com/apple/swift-nio/tree/main/Sources/NIOUDPEchoServer
- HTTP 客户端 https://github.com/apple/swift-nio/tree/main/Sources/NIOHTTP1Client
- HTTP 服务器 https://github.com/apple/swift-nio/tree/main/Sources/NIOHTTP1Server
- WebSocket 客户端 https://github.com/apple/swift-nio/tree/main/Sources/NIOWebSocketClient
- WebSocket 服务器 https://github.com/apple/swift-nio/tree/main/Sources/NIOWebSocketServer
要编译和运行它们,请运行以下命令,将 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的不同版本之间略有差异。例如,如果您想依赖 NIOCore
、NIOPosix
和 NIOHTTP1
模块,指定以下依赖
swift-tools-version:5.2
)
Swift 5.2 及更高版本(dependencies: [.product(name: "NIOCore", package: "swift-nio"),
.product(name: "NIOPosix", package: "swift-nio"),
.product(name: "NIOHTTP1", package: "swift-nio")]
使用 Xcode 项目支持
如果您的项目设置为 Xcode 项目且您使用 Xcode 11 或更高版本,可以通过点击 File -> Swift Packages -> Add Package Dependency 将 SwiftNIO 添加为 Xcode 项目的依赖项。在即将出现的对话框中,请输入 https://github.com/apple/swift-nio.git
并连续点击两次 Next。最后,选择您计划使用的目标(例如 NIOCore
、NIOHTTP1
和 NIOFoundationCompat
),然后点击完成。现在您可以在项目中导入 NIOCore
(以及您选择的其它所有目标)。
要工作于 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
将会创建一个基础镜像,编译 SwiftNIO,并在本地主机上的 9999 端口运行一个示例
NIOEchoServer
。通过执行命令echo Hello SwiftNIO | nc localhost 9999
进行测试。 -
docker-compose -f docker/docker-compose.yaml up http
将会创建一个基础镜像,编译 SwiftNIO,并在本地主机上的 8888 端口运行一个示例
NIOHTTP1Server
。通过执行命令curl http://localhost:8888
进行测试。 -
docker-compose -f docker/docker-compose.yaml -f docker/docker-compose.2004.54.yaml run test
将使用Ubuntu 20.04和Swift 5.4创建一个基础镜像,编译SwiftNIO并运行单元和集成测试。Docker目录中存在其他Ubuntu和Swift版本的文件。
开发SwiftNIO
注意:本节仅在您想要自己开发SwiftNIO时相关。如果您只需将SwiftNIO作为SwiftPM包使用,可以忽略此处的信息。
就大部分而言,SwiftNIO的开发与其他任何SwiftPM项目一样简单。但在此前提下,我们有一些流程在您贡献之前是值得了解的。有关详细信息,请参见此存储库中的CONTRIBUTING.md
文件。
先决条件
SwiftNIO的main
分支是SwiftNIO 2下一个版本的开发分支,它只支持Swift 5。
为了能够编译和运行SwiftNIO以及集成测试,您需要在系统上安装一些先决条件。
macOS
- Xcode 11.4或更高版本,推荐使用Xcode 12。
Linux
- 从swift.org/download安装Swift 5.2、5.3或5.4。我们始终推荐使用最新的发布版本。
- 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
加速测试
可以在并行下运行测试套件,如果你有一台大型的多核机器,这将节省大量时间。只需在运行测试时加上 --parallel
参数。这可以将测试套件的运行时间加快30倍以上。
swift test --parallel