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 的核心抽象和类型(有关更多详细信息,请参阅“概念概述”)。大多数 NIO 扩展项目(例如提供新EventLoop
和Channel
或新的协议实现的项目)仅需要依赖NIOCore
。NIOPosix
。这提供了POSIX系统上使用的核心地址:EventLoopGroup
、EventLoop
以及Channel
。这是我们的高性能核心I/O层。通常,这应该只由计划进行实际I/O的跨项目导入,例如高级协议实现或应用程序。NIOEmbedded
。它提供了EmbeddedChannel
和EmbeddedEventLoop
,这些是提供精细执行控制的NIO核心抽象实现。这些通常用于测试,但也可用于以解耦于网络的方式推动协议实现。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 已达到生命周期的终点 - 强烈建议您升级到较新版本。Core NIO 团队不再积极开发此版本。不再添加新功能,但直到 2022 年 5 月底,将接受修复错误或安全漏洞的 PR。
如果您有一个希望从 SwiftNIO 1 迁移到 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中构建高性能网络应用的低级工具。它特别针对那些使用“线程-per-连接”并发模型不高效或不切实际的用例。这在构建使用大量相对低利用率连接的服务器时是一个常见的限制,如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
中实现其逻辑,因为这有助于避免上下文切换的问题。
关于阻塞的说明
关于 ChannelPipeline
的一个重要注意事项是它们是线程安全的。这对于编写 SwiftNIO 应用程序非常重要,因为它允许你编写无需同步的更简单的 ChannelHandler
。
Bootstrap
目前SwiftNIO在NIOPosix
模块中提供了三个Bootstrap
对象:用于启动监听通道的ServerBootstrap
,用于启动客户端TCP通道的ClientBootstrap
,以及用于启动UDP通道的DatagramBootstrap
。
ByteBuffer
在SwiftNIO应用中,大部分的工作涉及在字节缓冲区之间移动字节。至少,数据以字节的缓冲区形式在网络中被发送和接收。因此,拥有一个高性能的数据结构,该数据结构针对SwiftNIO应用执行的工作进行了优化,非常重要。
因此,SwiftNIO提供了ByteBuffer
,这是一个快速的双写字节缓冲区,是大多数SwiftNIO应用的关键构建块。该类型由NIOCore
模块提供。
ByteBuffer
提供了许多有用的功能,并提供了一些钩子,以在“不安全”模式下使用。这关闭了边界检查以提高性能,但可能使您的应用程序面临内存正确性问题。
通常,强烈建议您始终以安全模式使用ByteBuffer
。
有关ByteBuffer
的API的更多详细信息,请参阅以下链接中的API文档。
Promises and Futures
编写并发代码与编写同步代码之间的一个主要区别是,并不是所有操作都会立即完成。例如,当您在通道上写入数据时,事件循环可能无法立即将写入操作刷新到网络上。因此,SwiftNIO提供了EventLoopPromise<T>
和EventLoopFuture<T>
来管理异步完成的操作。这些类型由NIOCore
模块提供。
EventLoopFuture<T>
基本上是一个函数返回值的容器,该函数将在未来的某个时间点填充。每个EventLoopFuture<T>
都有一个相应的EventLoopPromise<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/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 或更高版本,您可以通过点击“文件”-》“Swift 包”-》“添加包依赖项”将 SwiftNIO 添加为您的 Xcode 项目的依赖项。在即将出现的对话框中,请输入 https://github.com/apple/swift-nio.git
并连续点击两次“下一步”。最后,选择您打算使用的目标(例如 NIOCore
、NIOHTTP1
和 NIOFoundationCompat
)然后点击完成。现在您就可以在项目中 import 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 的基础镜像,在
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
测试它。 -
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