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 支持 |
.upToNextMinimal(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
,这些是对NIOPosix
核心抽象的实现,提供了对执行过程的精细控制。这些通常用于测试,但也可用于以与网络完全分离的方式驱动协议实现。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。
低级协议实现
低级协议实现通常是一组实现协议但仍然需要用户对SwiftNIO有良好理解的ChannelHandler
。通常,低级协议实现会被包裹在高级的库中,拥有更友好、更易于使用的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以上。NIO 2.29.0及更早版本支持Swift 5.0以上。
SwiftNIO 1
SwiftNIO 1版本已达到生命周期的终点 - 强烈建议您迁移到更新的版本。核心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开始的所有版本的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
,这是一个通过 POSIX pthreads
库创建多个线程的 EventLoopGroup
,在每个线程上放置一个 SelectableEventLoop
。该 SelectableEventLoop
是一个使用选择器(根据目标系统是 kqueue
还是 epoll
)来管理文件描述符的 I/O 事件并调度工作的事件循环。这些 EventLoop
和 EventLoopGroup
由 NIOPosix
模块提供。此外,还有 EmbeddedEventLoop
,它是一个主要用于测试目的的虚拟事件循环,由 NIOEmbedded
模块提供。
EventLoop
拥有许多重要的属性。至关重要的是,它们是 SwiftNIO 应用中所有工作执行的方式。为了保证线程安全,几乎需要在 SwiftNIO 的其他任何对象上执行的工作都必须通过 EventLoop
进行调度。这些 EventLoop
对象拥有许多 SwiftNIO 应用中的其他对象,理解它们的执行模型对于构建高性能的 SwiftNIO 应用至关重要。
Channels,Channel Handlers,Channel Pipelines,以及 Channel Contexts
虽然 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
可以通过使用 ChannelPipeline
中的 ChannelHandlerContext
来追踪它们在 ChannelPipeline
中的位置。这些对象包含对管道中前一个和下一个通道处理程序的引用,确保 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
虽然您可以直接使用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文档。
承诺(Promises)和未来(Futures)
编写并发代码与编写同步代码之间的一个主要区别是,并非所有操作都会立即完成。例如,当您在通道上写入数据时,事件循环可能无法立即将写入操作刷新到网络上。因此,SwiftNIO提供了EventLoopPromise
和EventLoopFuture
来管理异步完成的操作。这些类型由NIOCore
模块提供。
EventLoopFuture
实际上是一个函数返回值的容器,这些返回值将在某个未来时间点被填充。每个EventLoopFuture
都有一个相应的EventLoopPromise
,该对象将是结果存储的容器。当承诺达成时,未来将得到满足。
如果您需要通过轮询未来来检测其何时完成,这将非常低效,因此 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 包包”->“添加包依赖”向你的 Xcode 项目添加 SwiftNIO 依赖。在即将出现的对话框中,请输入 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+ 在 Xcode 中工作,只需在 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 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
加速测试
可以在并行的情况下运行测试套件,如果你有一台大型的多核机器,这将大大节省时间,只需在运行测试时添加 --parallel
参数。这可以提高测试套件的运行时间,速度最多能提升30倍或更多。
swift test --parallel