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 | 不可用 | 官方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 已到生命周期末尾 - 强烈建议您升级到新版本。核心 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开始的每个版本(不包括)3.0.0。SemVer和SwiftNIO的公共API保证将对兼容性的每个版本进行测试不需要担心。
概念概述
SwiftNIO是一个低级工具,旨在用Swift构建高性能网络应用。它特别针对使用“每个连接一个线程”的并发模型效率低下或不切实际的使用场景。这在构建使用大量相对低利用率连接的服务器时是一个常见限制,例如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应用程序至关重要。
Channels,Channel Handlers,Channel Pipelines,and Channel Contexts
虽然 EventLoop
对于SwiftNIO的工作至关重要,但大多数用户不会与它们进行实质性交互,只是请求它们创建 EventLoopPromise
并安排工作。用户将在SwiftNIO应用程序中花费最多时间的部分是 Channel
和 ChannelHandler
。
在SwiftNIO程序中,几乎每个用户交互的文件描述符都关联到单个 Channel
。这个 Channel
拥有此文件描述符,并负责管理其生命周期。它还负责在此文件描述符上处理传入和传出事件:每当事件循环有与文件描述符对应的事件时,它将通知该文件描述符的 Channel
。
然而,本身Channel
并没有用处。毕竟,很少有应用不希望对在套接字上发送或接收的数据进行处理!因此,Channel
的另一个重要部分就是ChannelPipeline
。
ChannelPipeline
是一系列对象,称为ChannelHandler
,它们在Channel
上处理事件。这些ChannelHandler
按顺序逐个处理事件,修改和转换事件。这可以被认为是一个数据处理管道,因此得名ChannelPipeline
。
所有ChannelHandler
要么是入站处理器,要么是出站处理器,或者两者都是。入站处理器处理“入站”事件,例如从套接字读取数据、读取套接字关闭或其他远程主发起的事件。出站处理器处理“出站”事件,如写入、连接尝试和本地套接字关闭。
每个处理器按顺序处理事件。例如,读取事件从前端管道传递到后端,一次一个处理器,而写入事件则从后端管道传递到前端。每个处理器在任何时候都可能生成入站或出站事件,这些事件将按适当方向发送到下一个处理器。这允许处理器拆分读取、合并写入、延迟连接尝试,以及通常执行事件的任意转换。
一般来说,ChannelHandler
被设计成高度可重用的组件。这意味着它们倾向于尽可能地小巧,执行一种特定数据转换。这允许处理器以新颖和灵活的方式组合在一起,有助于代码重用和封装。
ChannelHandler
可以使用ChannelHandlerContext
跟踪其在ChannelPipeline
中的位置。这些对象包含对管道中前后ChannelHandler
的引用,确保ChannelHandler
始终可以在管道中发出事件。
SwiftNIO配备了许多内置的有用功能的ChannelHandler
,如HTTP解析。此外,高性能应用程序希望尽可能将其逻辑放入ChannelHandler
中,因为这有助于避免上下文切换问题。
此外,SwiftNIO 包含了一些 Channel
实现。特别是,它包含 ServerSocketChannel
,用于接受传入连接的 Channel
;SocketChannel
,用于 TCP 连接的 Channel
;以及 DatagramChannel
,用于 UDP 套接字的 Channel
。所有这些都是由 NIOPosix
模块提供的。它还提供 EmbeddedChannel
,这是一个主要由 NIOEmbedded
模块提供、主要用于测试的 Channel
。
关于阻塞性的说明
ChannelPipeline
的一个重要说明是它们是线程安全的。这对于编写 SwiftNIO 应用程序非常重要,因为它允许您编写不需要同步的更简单的 ChannelHandler
。
然而,这是通过将所有代码调度到与 ChannelPipeline
相同的线程上的 EventLoop
来实现的。这意味着,作为一个一般规则,ChannelHandler
必须 将代码调度到后台线程,而不是直接调用阻塞代码。如果 ChannelHandler
由于任何原因而阻塞,则连接到父 EventLoop
的所有 Channel
都将无法进步,直到阻塞调用完成。
在编写 SwiftNIO 应用程序时,这是一个常见的担忧。如果要以阻塞方式编写代码,在完成管道中的工作后,强烈建议将工作调度到不同的线程。
引导(Bootstrap)
虽然可以直接使用 Channel
配置和注册 EventLoop
,但通常有一个更高层次的抽象来处理这项工作更有用。
因此,SwiftNIO 提供了多个 Bootstrap
对象,其目的是简化通道的创建。其中一些 Bootstrap
对象还提供其他功能,例如支持 Happy Eyeballs 以进行 TCP 连接尝试。
目前,SwiftNIO 与 NIOPosix
模块一起提供了三个 Bootstrap
对象:用于启动监听通道的 ServerBootstrap
,用于启动客户端 TCP 通道的 ClientBootstrap
,以及用于启动 UDP 通道的 DatagramBootstrap
。
ByteBuffer
SwiftNIO 应用中的主要工作涉及移动字节数据的缓冲区。至少,数据是以缓冲区形式通过网络发送和接收的。因此,拥有一个针对 SwiftNIO 应用程序执行的任务进行优化的高性能数据结构非常重要。
因此,SwiftNIO 提供了 ByteBuffer
,这是一种快速的可写拷贝的字节缓冲区,它是大多数 SwiftNIO 应用的基石。此类型由 NIOCore
模块提供。
ByteBuffer
提供了多个有用的功能,并且还提供了一些钩子,可以使用它以“非安全”模式运行。这关闭了边界检查,从而提高了性能,但同时可能会使您的应用程序面临内存正确性问题。
通常,强烈建议您始终以安全模式使用 ByteBuffer
。
有关 ByteBuffer
的 API 更多详细信息,请参阅以下链接中的 API 文档。
Promises 和 Futures
在编写并发代码与同步代码之间的一个主要区别是,并非所有操作都会立即完成。例如,当您在通道上写入数据时,事件循环可能无法立即将该写入操作刷新到网络上。因此,SwiftNIO 为管理异步完成的操作提供了 EventLoopPromise&T
和 EventLoopFuture&T
。这些类型由 NIOCore
模块提供。
EventLoopFuture&T
实质上是一个功能返回值的容器,该返回值将在将来某个时间点填充。每个 EventLoopFuture&T
都有一个相应的 EventLoopPromise&T
,它是将结果放入的对象。当承诺成功时,未来将会实现。
如果要轮询未来以检测其何时完成,这将是相当低效的,因此 `EventLoopFuture
另一个需要考虑的重要主题是,与`Channel`上的`closeFuture`相比,传递给`close`的承诺的工作方式有何不同。例如,传递到`close`的承诺将在`Channel`关闭后、在`ChannelPipeline`完全清除之前成功。这将允许您在需要的情况下,在`ChannelPipeline`完全清除之前采取行动。如果希望等待`Channel`关闭以及`ChannelPipeline`清除而无需进一步操作,则等待`closeFuture`成功将是一个更好的选择。
有多种函数可以将回调应用于`EventLoopFuture
设计哲学
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 Packages” -> “添加包依赖”将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,并在本地主机:9999上运行一个示例
NIOEchoServer
。通过echo Hello SwiftNIO | nc localhost 9999
进行测试。 -
docker-compose -f docker/docker-compose.yaml up http
将会创建一个基础镜像,编译SwiftNIO,并在本地主机: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