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
,它们是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。
低级协议实现
低级协议实现通常是实现协议但仍然需要用户对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项目 | ||
SSH | apple/swift-nio-ssh | n/a | 官方NIO项目 |
《高级实现》
高级实现通常是带有API的库,该API没有公开SwiftNIO的ChannelPipeline
,因此可以无需或只需很少的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 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版本开始支持SwiftNIO的每一个版本,直到(不包括)3.0.0。SemVer和SwiftNIO的Public API保证意味着无需担心对每个版本进行测试以检查兼容性。
概念概述
SwiftNIO基本是一个用于构建高效网络应用程序的低级别工具,特别关注那些使用“每个连接一个线程”的并发模型效率低下或不可行的用例。这通常在构建使用大量相对低使用率的连接的服务器时是一个常见的限制,比如HTTP服务器。
为了实现其目标,SwiftNIO大量使用“非阻塞I/O”:这就是其名称的由来!非阻塞I/O与更常见的阻塞I/O模型不同,因为应用不会等待数据被发送到或接收自网络:相反,SwiftNIO会请求内核在I/O操作无需等待时通知它。
SwiftNIO并不旨在提供类似于web框架的高级别解决方案。相反,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
都可以是入站或出站处理器,或者同时是两者。入站处理器处理“入站”事件:例如从套接字读取数据、读取套接字关闭或其他由远程对端发起的事件。出站处理器处理“出站”事件,如写入、连接尝试和本地套接字关闭。
每个处理器按顺序处理事件。例如,读取事件从前端到后端,逐个处理器传递,而写入事件从后端到前端传递。每个处理器可以随时生成接收或发送事件,这些事件将根据适当的方向传递给下一个处理器。这使得处理器可以分割读取,组合写入,延迟连接尝试,并对事件进行任意转换。
通常,ChannelHandler
被设计为高度可重用的组件。这意味着它们倾向于尽量小,执行一个具体的数据转换。这使得处理器可以以新颖和灵活的方式组合在一起,这有助于代码重用和封装。
ChannelHandler
通过使用 ChannelHandlerContext
来跟踪它们在 ChannelPipeline
中的位置。这些对象包含对管道中先前和下一个通道处理器的引用,确保处理器始终可以在管道中生成事件。
SwiftNIO提供了许多内置的 ChannelHandler
,这些处理器提供了有用的功能,例如 HTTP 解析。此外,高性能的应用程序可能会希望在 ChannelHandler
中提供尽可能多的逻辑,因为它有助于避免上下文切换问题。
此外,SwiftNIO附带了一些Channel
实现。特别是,它附带ServerSocketChannel
,一个用于接受传入连接的Channel
;SocketChannel
,一个用于TCP连接的Channel
;以及DatagramChannel
,一个用于UDP套接字的Channel
。所有这些都是由NIOPosix
模块提供的。它还提供了EmbeddedChannel
,这是一个主要用于测试的Channel
,由NIOEmbedded
模块提供。
关于阻塞的说明
ChannelPipeline
的一个重要注意事项是它们是线程安全的。这对于编写SwiftNIO应用程序非常重要,因为它允许你在知道它们不需要同步的情况下编写更简单的ChannelHandler
。
然而,这是通过将所有代码都调度到与EventLoop
相同的线程上实现的。这意味着,作为一个一般规则,ChannelHandler
在未将其调度到后台线程的情况下绝对不应该调用阻塞代码。如果ChannelHandler
由于任何原因而阻塞,则附加到父EventLoop
的所有Channel
都无法进步,直到阻塞调用完成。
在编写SwiftNIO应用程序时,这是一个常见的关注点。如果你喜欢用阻塞的方式来编写代码,强烈建议你在你的pipelines完成后将工作调度到不同的线程。
引导
虽然可以 直接使用Channel
和EventLoop
进行配置和注册,但通常更有用,有更高层次的抽象来处理这项工作。
因此,SwiftNIO附带了一些Bootstrap
对象,这些对象的作用是简化创建通道的过程。一些Bootstrap
对象还提供其他功能,例如支持Happy Eyeballs来尝试TCP连接。
目前SwiftNIO在NIOPosix
模块中包含三个Bootstrap
对象:用于启动监听通道的ServerBootstrap
,用于启动客户端TCP通道的ClientBootstrap
,以及用于启动UDP通道的DatagramBootstrap
。
ByteBuffer
在SwiftNIO应用程序中,大部分工作都涉及移动字节数据缓冲区。至少,数据是在网络中以缓冲区字节的形式发送和接收的。因此,具有高性能并且优化了SwiftNIO应用程序执行的这类工作,是非常重要的数据结构。
因此,SwiftNIO提供了ByteBuffer
,这是一个快速复制-on-write的字节缓冲区,是大多数SwiftNIO应用程序的一个关键构建模块。该类型由NIOCore
模块提供。
除了提供一系列有用的功能外,ByteBuffer
还提供了一些用于在“不安全”模式下使用的钩子。这通过关闭边界检查来提高性能,但可能会让您的应用程序暴露于潜在的内存正确性问题。
通常,强烈建议您始终使用ByteBuffer
的安全模式。
有关ByteBuffer
API的更多详细信息,请参阅以下链接中的API文档。
承诺(Promises)和未来(Futures)
编写并发代码与编写同步代码之间的一个主要区别是,并非所有操作都会立即完成。例如,当你在通道上写入数据时,可能会发生事件循环无法立即将这次写入刷新到网络的情况。因此,SwiftNIO提供了管理操作完成(异步)的EventLoopPromise<T>
和EventLoopFuture<T>
,这两个类型都由NIOCore
模块提供。
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/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