SwiftNIO
SwiftNIO 是一个支持跨平台异步事件驱动的网络应用程序框架,用于快速开发可维护的高性能协议服务器和客户端。
它类似于 Netty,但用 Swift 编写。
代码库组织
SwiftNIO项目分散在多个代码库中
代码库 | NIO 2 (Swift 5.2+) |
---|---|
https://github.com/apple/swift-nio SwiftNIO 核心 |
from: "2.0.0" |
https://github.com/apple/swift-nio-ssl TLS (SSL) 支持 |
from: "2.0.0" |
https://github.com/apple/swift-nio-http2 HTTP/2 支持 |
from: "1.0.0" |
https://github.com/apple/swift-nio-extras 有关 SwiftNIO 的有用补充 |
from: "1.0.0" |
https://github.com/apple/swift-nio-transport-services 对 macOS、iOS、tvOS 和 watchOS 的原生支持 |
from: "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
和Channel
或新协议实现等内容的 NIO 扩展项目只需依赖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。
底层协议实现
底层协议实现通常是实现某一协议的多个 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特定知识的情况下使用。下面列出的实现仍然在其所有I/O中使用SwiftNIO,并且与SwiftNIO生态系统集成得非常好。
协议 | 客户端 | 服务器 | 代码库 | 模块 | 注释 |
---|---|---|---|---|---|
HTTP | swift-server/async-http-client | AsyncHTTPClient |
SSWG社区项目 | ||
gRPC | grpc/grpc-swift | GRPC |
提供底层API;SSWG社区项目 | ||
APNS | kylebrowing/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团队不再积极开发这个版本。不会添加新功能,但直到2022年5月底,将接受修复错误或安全漏洞的PR。
如果您希望将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版本开始的每个版本。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的IO基本原语是事件循环。事件循环是一个对象,它等待事件(通常是I/O相关事件,如“接收到数据”)发生,并在这些事件发生时触发某种回调。在几乎所有的SwiftNIO应用程序中,将会有相对较少的事件循环:通常每个应用程序想要使用的CPU核心只有一个或两个。一般来说,事件循环将运行整个应用程序的生命周期,连续不断地调度事件。
事件循环被组织成事件循环组。这些组提供了一种在事件循环之间分配工作的机制。例如,在监听传入连接时,侦听套接字将注册在一个事件循环上。然而,我们不希望在该侦听套接字上接受的所有连接都与同一个事件循环注册,因为这可能会潜在地超载一个事件循环同时留下其他事件循环为空。因此,事件循环组提供了在多个事件循环之间分配负载的能力。
在SwiftNIO当前版本中,存在一个EventLoopGroup
实现和两个EventLoop
实现。对于生产应用,有一个名为MultiThreadedEventLoopGroup
的实现,这是一个通过POSIXpthreads
库创建多个线程的EventLoopGroup
,并为每个线程放置一个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
中的位置。这些对象包含了管道中前一个和下一个通道处理器的引用,确保一个 ChannelHandler
在管道中始终可以发出事件。
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 应用程序时是一个常见问题。如果你有用阻塞风格编写代码的用途,当你完成你的流水线中的工作时,强烈建议你将工作调度到不同的线程。
引导
虽然可以直接通过 EventLoop
配置和注册 Channel
,但通常更实用的方法是拥有一个更高层次的抽象来处理这项工作。
因此,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文档。
承诺和未来
编写并发代码与编写同步代码之间的一个主要区别是,并非所有操作都会立即完成。例如,当您在通道上写入数据时,事件循环可能无法立即将该写入操作输出到网络。因此,SwiftNIO提供了EventLoopPromise<T>
和EventLoopFuture<T>
来管理那些异步完成的操作。这些类型由NIOCore
模块提供。
EventLoopFuture<T>
基本上是一个容器,用于存储函数在未来某一时刻填充的返回值。每个EventLoopFuture<T>
都有一个相应的EventLoopPromise<T>
,这是将结果放入的对象。当承诺成功时,将实现未来的兑现。
如果必须轮询未来以检测其何时完成,则效率将相当低,因此EventLoopFuture<T>
被设计为具有管理回调。基本上,您可以将回调附加到未来,当结果可用时将执行这些回调。EventLoopFuture<T>
甚至仔细安排调度,确保这些回调始终在最初创建承诺的事件循环上执行,这有助于确保您不必围绕EventLoopFuture<T>
回调进行太多同步。
需要考虑的另一个重要话题是,在相对于Channel
上的closeFuture
而言,传递给close
的promise是如何工作的。例如,传递给close
的promise将在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
)并点击完成。现在你将能在你的项目中导入 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 5.2、5.3或5.4版本(可从swift.org/download获取)。我们一直推荐使用最新发布的版本。
- 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
加速测试
可以在并行模式下运行测试套件,如果您有一个大型的多核机器,这将大大节省时间,只需在运行测试时添加 --并行
选项。这可以将测试套件运行时间提高30倍或更多。
swift test --parallel