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
和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 | 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 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本质上是用于在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
,这是一个通过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 处理器处理“ inbound”事件,例如从套接字读取数据、读取套接字关闭或其他远程同伴启动的事件。Outbound 处理器处理“ outbound”事件,例如写入数据、连接尝试和本地套接字关闭。
每个处理器按顺序处理事件。例如,读取事件从一个处理器传到另一个处理器,从管道的前端到后端,而写入事件则相反,从后端前端传递。每个处理器可能随时生成 inbound 或 outbound 事件,并将其发送给下一个处理器的合适方向。这允许处理器拆分读取,合并写入,延迟连接尝试,并通常对事件进行任意转换。
一般来说,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
。
然而,这是通过将所有代码在 ChannelPipeline
上的同一线程中调度来实现的。这意味着作为一条一般规则,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
,这是一个快速的字节缓冲区,以“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
的promise与在Channel
上的closeFuture
之间的区别。例如,传递给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
)然后点击“完成”。现在你可以在项目中 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