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 的核心抽象和类型(更多详细信息请参阅"概念概述")。大多数提供像新EventLoop
s和Channel
s或新协议实现的东西的 NIO 扩展项目只需要依赖于NIOCore
。NIOPosix
。它提供了主要的[EventLoopGroup
],EventLoop
,以及Channel
,用于POSIX系统。这是我们高性能的核心I/O层。一般来说,这应该只由计划进行实际I/O的项目导入,例如高级协议实现或应用。NIOEmbedded
。它提供了EmbeddedChannel
和EmbeddedEventLoop
的实现,提供了对它们的执行进行精细控制的抽象。这些通常用于测试,但也可用来自动协议实现,完全独立于网络。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完成的协议实现的列表。这并不是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项目 |
高级实现
高级实现通常是带有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以上。2.29.0和更早版本支持Swift 5.0以上。
SwiftNIO 1
SwiftNIO 1版本已进入末期 - 强烈建议您升级到新版本。核心NIO团队不再积极工作于此版本。不会为此版本添加新功能,但会接受until the end of May 2022修复错误或安全漏洞的Pull Requests。
如果您要将SwiftNIO 1应用程序或库迁移到SwiftNIO 2,请查看我们为您准备的迁移指南。
最新发布的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的公共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
,这是一个由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
中提供它们自己的逻辑,因为这将有助于避免上下文切换的问题。
此外,SwiftNIO附带了一些Channel
实现。特别是,它附带ServerSocketChannel
,这是一个用于接受传入连接的Channel
;SocketChannel
,这是一个用于TCP连接的Channel
;以及DatagramChannel
,这是一个用于UDP套接字的Channel
。所有这些均由NIOPosix
模块提供。它还提供EmbeddedChannel
,这是一个主要用于测试的Channel
,由NIOEmbedded
模块提供。
关于阻塞的说明
ChannelPipeline
的一个重要注意事项是它们是线程安全的。这对于编写SwiftNIO应用程序非常重要,因为它允许你在知道它们不需要同步的情况下编写更简单的ChannelHandler
。
然而,这是通过在EventLoop
的同一个线程上调度所有ChannelPipeline
上的代码来实现的。这意味着,作为一个一般规则,ChannelHandler
必须在调度到后台线程之前不要在阻塞代码上调用代码。如果任何ChannelHandler
由于任何原因而阻塞,所有连接到父EventLoop
的Channel
将无法继续直到阻塞调用完成。
在编写SwiftNIO应用程序时,这是一个常见的担忧。如果你需要在管道中编写阻塞代码,强烈建议你将工作调度到不同的线程。
引导
虽然可以直接使用EventLoop
配置和注册Channel
,但通常更有用的方法是拥有一个高级抽象来处理这项工作。
因此,SwiftNIO附带了一些Bootstrap
对象,其目的是简化通道的创建。一些Bootstrap
对象还提供了其他功能,例如对Happy Eyeballs的支持,以便进行TCP连接尝试。
目前SwiftNIO在NIOPosix模块中包含三个Bootstrap
对象:用于启动监听通道的ServerBootstrap
,用于启动客户端TCP通道的ClientBootstrap
,以及用于启动UDP通道的DatagramBootstrap
。
ByteBuffer
在SwiftNIO应用中,大部分的工作涉及到字节数据的移动。至少,通过网络发送和接收数据都是用字节数据的缓冲区形式实现的。因此,拥有一个针对SwiftNIO应用操作进行优化的高性能数据结构是非常重要的。
因此,SwiftNIO提供了ByteBuffer
,这是一个快速的copy-on-write字节区,它是大多数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>
》还会小心翼翼地安排调度,以确保这些回调始终在最初创建承诺的事件循环上执行,这有助于确保您不需要过多地同步《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
将会创建一个包含 Swift 运行时和其他构建和测试依赖的基础镜像,编译 SwiftNIO,并在
localhost:9999
上运行一个示例NIOEchoServer
。通过echo Hello SwiftNIO | nc localhost 9999
来测试。 -
docker-compose -f docker/docker-compose.yaml up http
将会创建一个包含 Swift 运行时和其他构建和测试依赖的基础镜像,编译 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