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。
低级协议实现
低级协议实现通常是一组实现一个协议的 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 项目 |
高级实现
高级实现通常是带有 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 上开发和测试,已知支持以下操作系统版本
- Ubuntu 14.04+
- macOS 10.12+; (macOS 10.14+, iOS 12+, 或 tvOS 12+ 与 swift-nio-transport-services 一起)
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本质上是一个在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
,一个协议EventLoop
,一个协议Channel
,一个协议ChannelHandler
,一个协议Bootstrap
,几个相关的结构ByteBuffer
,一个结构体EventLoopFuture
,一个泛型类EventLoopPromise
,一个泛型结构体。
所有SwiftNIO应用程序最终都由这些各种组件构建而成。
事件循环和事件循环组
SwiftNIO的基本I/O原始对象是事件循环。事件循环是一个等待事件(通常是与I/O相关的事件,如“数据接收”)发生的对象,在事件发生时触发某种回调。几乎在所有SwiftNIO应用中,事件循环的数量相对较少:通常每个应用程序想使用的CPU核心只有一到两个。一般来说,事件循环在整个应用程序的生命周期内运行,无限循环地调度事件。
事件循环被组合成事件循环组。这些组提供了一种在多个事件循环之间分配工作的机制。例如,当监听入站连接时,监听套接字将注册在某个事件循环上。但是,我们不想将所有通过该监听套接字接受的连接都注册在同一个事件循环上,因为这可能会潜在地压倒一个事件循环而留下其他的空闲。因此,事件循环组提供了在多个事件循环之间分散负载的能力。
在SwiftNIO中,目前有一个 EventLoopGroup
实现,以及两个 EventLoop
实现。对于生产应用,有 MultiThreadedEventLoopGroup
,它是一个 EventLoopGroup
,创建了一定数量的线程(使用POSIX的 pthreads
库),并将一个 SelectableEventLoop
放在每个线程中。`SelectableEventLoop` 是一个事件循环,它使用选择器(根据目标系统是 kqueue
或 epoll
)来管理来自文件描述符的I/O事件并调度工作。此外,还有一个 EmbeddedEventLoop
,它是一个用于测试目的的虚拟事件循环。
EventLoop
具有许多重要属性。最重要的是,它们是SwiftNIO应用程序中所有工作的执行方式。为了确保线程安全,任何在SwiftNIO中的其他对象上执行的工作都必须通过 EventLoop
进行调度。EventLoop
对象拥有SwiftNIO应用程序中的几乎所有其他对象,理解它们的执行模型对于构建高性能的SwiftNIO应用程序至关重要。
通道、通道处理器、通道流水线和通道上下文
虽然 EventLoop
对SwiftNIO的工作方式至关重要,但大多数用户只是在要求其创建 EventLoopPromise
和安排工作之外与之实质性互动。用户将花费最多时间的SwiftNIO应用程序的部分是 Channel
和 ChannelHandler
。
在SwiftNIO程序中,几乎每个用户交互的文件描述符都与单个 Channel
关联。`Channel` 拥有这个文件描述符,并负责管理其生命周期。它还负责处理该文件描述符上的输入和输出事件:每当事件循环有与文件描述符对应的事件时,它将通知拥有该文件描述符的 `Channel`。
然而,`Channel` 本身并没有用处。毕竟,很少会有不对其在套接字上发送或接收的数据进行任何操作的应用程序!因此,`Channel` 的另一个重要组成部分是 ChannelPipeline
。
ChannelPipeline(管道通道)是一系列被称为ChannelHandler
(通道处理器)的对象的组合,用于处理Channel
(通道)上的事件。这些ChannelHandler
依次处理事件,按顺序修改和转换事件。这可以被视为一个数据处理管道;因此得名。
所有ChannelHandler
都是内联处理器或外联处理器,或者二者皆是。内联处理器处理“内部”事件:如从套接字读取数据、读取套接字关闭或其他由远程对端发起的事件。外联处理器处理“外部”事件,如写操作、连接尝试和本地套接字关闭。
每个处理器按照顺序处理事件。例如,读事件从管道的前端传递到后端,一个处理器一个处理器地传递,而写事件则是从管道的后端传递到前端。每个处理器都可能在任何时候产生内联或外联事件,并将这些事件发送到下一个处理器,不论哪种方向适宜。这允许处理器分割读取,合并写入,延迟连接尝试,并对事件进行任意转换。
通常,ChannelHandler
设计为高度可重用组件。这意味着它们通常设计得尽可能小,执行一个特定的数据处理转换。这允许处理器以新颖和灵活的方式来组合使用,有助于代码重用和封装。
ChannelHandler
可以通过使用ChannelHandlerContext
来跟踪它们在ChannelPipeline
中的位置。这些对象包含对管道中前一个和下一个通道处理器的引用,确保通道处理器总是可以在管道上进行事件发射。
SwiftNIO内置了许多ChannelHandler
,提供有用的功能,如HTTP解析。此外,高性能应用程序应该尽可能将其逻辑放入ChannelHandler
中,因为它有助于避免上下文切换的问题。
此外,SwiftNIO还包括一些Channel
实现。特别是它包括了ServerSocketChannel
,这是一个接受传入连接的套接字的通道;SocketChannel
,这是一个TCP连接的通道;DatagramChannel
,这是一个UDP套接字的通道;以及EmbeddedChannel
,这是一个主要用于测试的通道。
关于阻塞的注意事项
关于ChannelPipeline
的一个重要注意事项是它们是线程安全的。这对于编写SwiftNIO应用程序非常重要,因为它允许您在知道它们不需要同步的情况下编写更简单的ChannelHandler
。
但是,这是通过将所有代码在同一个线程上派遣到ChannelPipeline
来实现的,这个线程与EventLoop
相同。这意味着,一般来说,ChannelHandler
在将其派遣到后台线程之前不得在没有派遣的情况下调用阻塞代码。如果一个ChannelHandler
因为任何原因而阻塞,所有连接到父EventLoop
的Channel
都将无法进步,直到阻塞调用完成。
这在编写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>
被设计为具有管理的回调。基本上,您可以在未来上挂接回调,这些回调将在结果可用时执行。《EventLoopFutureEventLoopFuture
回调周围进行太多同步。
需要考虑的另一个重要话题是传递给 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。
- 聊天客户端 https://github.com/apple/swift-nio/tree/master/Sources/NIOChatClient
- 聊天服务器 https://github.com/apple/swift-nio/tree/master/Sources/NIOChatServer
- 回声客户端 https://github.com/apple/swift-nio/tree/master/Sources/NIOEchoClient
- 回声服务器 https://github.com/apple/swift-nio/tree/master/Sources/NIOEchoServer
- UDP回声客户端 https://github.com/apple/swift-nio/tree/master/Sources/NIOUDPEchoClient
- UDP回声服务器 https://github.com/apple/swift-nio/tree/master/Sources/NIOUDPEchoServer
- HTTP客户端 https://github.com/apple/swift-nio/tree/master/Sources/NIOHTTP1Client
- HTTP服务器 https://github.com/apple/swift-nio/tree/master/Sources/NIOHTTP1Server
- WebSocket客户端 https://github.com/apple/swift-nio/tree/master/Sources/NIOWebSocketClient
- WebSocket服务器 https://github.com/apple/swift-nio/tree/master/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中略有不同。例如,如果您想依赖NIO
和NIOHTTP1
模块,请指定以下依赖项
swift-tools-version:5.[01]
)
Swift 5.0和5.1(dependencies: ["NIO", "NIOHTTP1"]
swift-tools-version:5.2
)
Swift 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
并连续点击两次“下一步”。最后,选择您计划使用的目标(例如 NIO
、NIOHTTP1
和 NIOFoundationCompat
)并点击完成。现在您可以在项目中导入 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
将创建一个基础镜像,编译 SwiftNIO,并在
localhost:9999
上运行一个示例NIOEchoServer
。通过运行命令echo Hello SwiftNIO | nc localhost 9999
进行测试。 -
docker-compose -f docker/docker-compose.yaml up http
将创建一个基础镜像,编译 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.org/download 下载的 Swift 5.0、5.1 或 5.2。我们始终建议使用最新发布的版本。
- 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