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版本开始的每个版本支持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
进行调度。几乎在 SwiftNIO 应用程序中,EventLoop
对象拥有其他所有对象,理解其执行模型对于构建高性能 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
;以及 EmbeddedChannel
,它主要用于测试的 Channel
。
关于阻塞的说明
关于 ChannelPipeline
的一个重要注意事项是它们是多线程安全的。这对编写 SwiftNIO 应用程序非常重要,因为这允许你编写更简单的 ChannelHandler
,而无需进行同步。
然而,这是通过对 ChannelPipeline
上的所有代码进行调度来在同一个线程上完成的,该线程与 EventLoop
相同。这意味着,作为一个一般规则,ChannelHandler
必须 不在将代码调度到后台线程的情况下调用阻塞代码。如果 ChannelHandler
因任何原因而阻塞,则附加到父 EventLoop
的所有 Channel
将无法继续执行,直到阻塞调用完成。
在编写 SwiftNIO 应用程序时,这是一个常见的问题。如果你发现要以阻塞的方式编写代码很有用,强烈建议你在你的管道中完成工作时将工作调度到另一个线程。
Bootstrap
虽然可以配置并直接使用 Channel
与 EventLoop
,但通常会更有用有一个高级抽象来处理这项工作。
因此,SwiftNIO 提供了一些 Bootstrap
对象,其目的是简化通道的创建。一些 Bootstrap
对象还提供其他功能,例如对 Happy Eyeballs 的支持,用于进行 TCP 连接尝试。
目前 SwiftNIO 带有三种 Bootstrap
对象:用于引导监听通道的 ServerBootstrap
,用于引导客户端 TCP 通道的 ClientBootstrap
,以及用于引导 UDP 通道的 DatagramBootstrap
。
ByteBuffer
SwiftNIO 应用程序的大多数工作都涉及到字节缓冲区的转换。至少,数据通过网络以字节缓冲区形式发送和接收。因此,有一种高性能数据结构对 SwiftNIO 应用程序执行的工作进行了优化是非常重要的。
因此,SwiftNIO 提供了 ByteBuffer
,这是一个快速复制-on-write 字节缓冲区,是大多数 SwiftNIO 应用的关键构建块。
ByteBuffer
提供了许多实用的功能,并且还提供了一些钩子,可将其用于“不安全”模式。这将关闭边界检查以提高性能,但可能会使您的应用程序面临内存正确性问题。
通常强烈建议您始终以安全模式使用 ByteBuffer
。
有关 ByteBuffer
的 API 详述,请参阅以下链接中我们的 API 文档。
承诺和未来
编写并发代码与编写同步代码之间的一个主要区别是,并非所有操作都会立即完成。例如,当您在信道上写入数据时,可能事件循环无法立即将该写入刷新到网络上。因此,SwiftNIO提供了 EventLoopPromise<T>
和 EventLoopFuture<T>
来管理异步完成的操作。
EventLoopFuture<T>
实质上是一个容器,用于存储函数的返回值,这些返回值将在将来的某个时间填充。每个 EventLoopFuture<T>
都有一个对应的 EventLoopPromise<T>
,这是将结果放入的对象。当承诺成功时,未来将会得到满足。
如果需要轮询未来以检测其何时完成,这将会非常低效,因此 EventLoopFuture<T>
被设计为具有管理回调。本质上,您可以在未来上挂载回调,以便在结果可用时执行这些回调。 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。
- 聊天客户端 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
要构建和运行它们,请运行以下命令,用./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
)并点击“完成”。现在您可以在项目中 import 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 runtime 和其他构建和测试依赖项的基础镜像,编译 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