SwiftNIO
SwiftNIO 是一个跨平台的异步事件驱动网络应用框架,用于快速开发可维护的高性能协议服务器和客户端。
它与 Netty 类似,但专为 Swift 编写。
代码库组织
SwiftNIO 代码库分布在多个仓库中
代码库 | NIO 2 (Swift 5+) | NIO 1 (Swift 4+) |
---|---|---|
https://github.com/apple/swift-nio SwiftNIO 核心 |
from: "2.0.0" |
from: "1.0.0" |
https://github.com/apple/swift-nio-ssl TLS (SSL) 支持 |
from: "2.0.0" |
from: "1.0.0" |
https://github.com/apple/swift-nio-http2 HTTP/2 支持 |
from: "1.0.0" |
from: "0.1.0" |
https://github.com/apple/swift-nio-extras SwiftNIO 的一些有用扩展 |
from: "1.0.0" |
from: "0.1.0" |
https://github.com/apple/swift-nio-transport-services 支持 macOS、iOS 和 tvOS 的首选支持 |
from: "1.0.0" |
from: "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上开发和测试,并且已知支持以下操作系统版本
- 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 版本开始的所有 SwiftNIO 版本,直至(不包括)3.0.0。SemVer 和 SwiftNIO 的公共 API 保证将在不必须为每个版本进行兼容性测试的情况下,确保程序能够正常工作。
概念概述
SwiftNIO本质上是构建高性能网络应用程序的底层工具,特别针对那些使用“每个连接一个线程”并发模式效率低下或不切实际的场景。这在构建大量相对低使用率连接的服务器时是一个常见的限制,例如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
按顺序逐个处理事件,在它们前进的过程中修改和转换事件。这可以被认为是一个数据处理管道,因此得名ChannelPipeline
。
所有的ChannelHandler
要么是Inbound处理器,要么是Outbound处理器,或者两者都是。Inbound处理器处理“入站”事件,例如从套接字读取数据,读取套接字关闭或其他由远程端发起的事件。Outbound处理器处理“出站”事件,例如写入、连接尝试和本地套接字关闭。
每个处理器按顺序处理事件。例如,读取事件从前端传递到后端,每次一个处理器,而写入事件则从后端传递到前端。每个处理器任何时候都可以生成入站或出站事件,并将这些事件以适当的方向转发给下一个处理器。这允许处理器分割读取、合并写入、延迟连接尝试,并在一般事件转换中执行任意转换。
通常,ChannelHandler
被设计成高度可重用的组件。这意味着它们往往被设计得尽可能小,执行一个特定的数据转换。这使得处理器可以以新颖和灵活的方式组合在一起,有助于代码重用和封装。
ChannelHandler
可以通过使用ChannelHandlerContext
来跟踪它们在ChannelPipeline
中的位置。这些对象包含对管道中前一个和下一个通道处理器的引用,确保当一个ChannelHandler
仍在管道中时,它总是有可能生成事件。
SwiftNIO包含了众多内置的ChannelHandler
,如HTTP解析等实用功能。此外,高性能应用程序希望在尽可能多的ChannelHandler
中提供逻辑,因为它有助于避免上下文切换问题。
此外,SwiftNIO还提供了一些Channel
实现。特别是,它提供了ServerSocketChannel
,这是用于接受入站连接的Channel
;SocketChannel
,用于TCP连接的Channel
;DatagramChannel
,用于UDP套接字的Channel
;以及EmbeddedChannel
,这是一个主要用于测试的Channel
。
关于阻塞的说明
关于ChannelPipeline
的一个重要说明是它们是线程安全的。这对于编写SwiftNIO应用程序非常重要,因为它允许您在知道不需要同步的情况下编写更简单的ChannelHandler
。
然而,这是通过对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
,这是一个快速的copy-on-write字节缓冲区,是大多数SwiftNIO应用程序的关键构建块。
ByteBuffer
提供了一些有用的功能,并且在“不安全”模式下还提供了一些钩子。这会关闭边界检查以提升性能,但可能会使您的应用程序面临内存正确性问题。
通常,我们强烈建议您始终以安全模式使用ByteBuffer
。
有关ByteBuffer
的API的更多详细信息,请参阅以下链接的API文档。
承诺和未来(Promises and Futures)
编写并发代码和编写同步代码的一个主要区别在于,并非所有操作都会立即完成。例如,当您在一个通道上写入数据时,事件循环可能不能立即将该写入操作发送到网络上。因此,SwiftNIO提供了EventLoopPromise<T>
和EventLoopFuture<T>
来管理那些完成在异步模式下操作。
EventLoopFuture<T>
基本上是一个将来在某个时间将填充的函数返回值的容器。每个EventLoopFuture<T>
都有一个相应的EventLoopPromise<T>
,这是一个将结果放入的对象。当承诺成功时,未来将会满足。
如果您必须轮询未来以检测其何时完成,这将非常低效,因此 EventLoopFuture<T>
被设计成具有管理的回调。基本上,您可以在结果可用时执行的未来上挂载回调。甚至 EventLoopFuture<T>
会小心翼翼地进行调度安排,以确保这些回调总是最初创建承诺的事件循环上执行,这有助于确保您不需要太多关于 EventLoopFuture<T>
回调的同步。
另一个需要考虑的重要问题是与传递给 close
的承诺与在 Channel
上的 closeFuture
相比的工作方式之间的差异。例如,传递给 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/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
为了验证它是否工作,你可以使用另一个外壳尝试连接到它
echo "Hello SwiftNIO" | nc localhost 9999
如果一切顺利,你会看到回显的消息。
要在 Xcode 11+ 中在 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