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-mobile 为 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
。这提供了主要的 [EventLoopGroup
],EventLoop
和Channel
组件,用于在基于 POSIX 的系统中使用。这是我们高性能的核心 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。
低级协议实现
低级协议实现通常是多个 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被认为是已结束生命周期 - 强烈建议您切换到更新版本。Core NIO团队目前没有积极维护这个版本。不会再添加该版本的新功能,但会接受修复错误或安全漏洞的PR,直到2022年5月底。
如果您有想要迁移到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应用程序至关重要。
通道,通道处理器,通道管道和通道上下文
虽然 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
。所有这些都由 NIOPosix
模块提供。它还提供了 EmbeddedChannel
,这是一个主要由 NIOEmbedded
模块提供的、主要用于测试的 Channel
。
关于阻塞的说明
ChannelPipeline
的重要注意事项之一是它们是线程安全的。这对于编写 SwiftNIO 应用程序非常重要,因为它允许你编写更简单的 ChannelHandler
,因为这些代码不需要同步。
然而,这是通过将所有代码调度到与 ChannelPipeline
相同的 EventLoop
线程来实现的。这意味着,作为一个普遍规则,ChannelHandler
绝对不可以 在没有将其调度到后台线程的情况下调用阻塞代码。如果 ChannelHandler
因任何原因而阻塞,则所有附加到父 EventLoop
的 Channel
都将在阻塞调用完成之前无法进展。
在编写 SwiftNIO 应用程序时,这是一个常见的担忧。如果你的代码需要在阻塞模式下编写,强烈建议你将工作调度到管道完成后的另一个线程上。
启动过程
虽然你可以直接使用 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文档。
Promise和Future
编写并发代码和同步代码之间的一个主要区别是,并非所有操作都会立即完成。例如,当您在通道上写入数据时,事件循环可能无法立即将该写入操作刷新到网络。因此,SwiftNIO提供了EventLoopPromise<T>
和EventLoopFuture<T>
来管理异步完成的操作。这些类型由NIOCore
模块提供。
EventLoopFuture<T>
基本上是一个容器,用于存储将在未来某个时间点填充的函数的返回值。每个EventLoopFuture<T>
都有一个相应的EventLoopPromise<T>
,这是将结果放入的对象。当承诺失败时,未来将会实现。
如果需要轮询未来以检测其何时完成,则这将非常低效,因此EventLoopFuture
被设计为具有管理回调。本质上,可以在将结果可用时执行的未来上挂载回调。此外,EventLoopFuture
会仔细安排调度以确保这些回调始终在最初创建承诺的事件循环上执行,这有助于确保您不需要过多的同步来处理EventLoopFuture
回调。
另一个需要考虑的重要主题是,与Channel
上的closeFuture
相比,传递给close
的承诺的功能差异。例如,传递给close
的承诺将在Channel
关闭后,但ChannelPipeline
完全清空之前成功。这将允许在需要时在完全清空ChannelPipeline
之前采取行动。如果希望等待Channel
关闭并清空ChannelPipeline
而不执行任何进一步操作,那么更好的选择是等待closeFuture
成功。
根据您想要如何以及何时执行它们,有几个函数可以将回调应用于EventLoopFuture
。这些函数的详细信息留给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,只需将一个dependencies
子句添加到您的Package.swift
中即可。
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或更高版本,您可以通过点击 https://github.com/apple/swift-nio.git
并点击两次Next。最后,选择您打算使用的目标(例如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