SwiftNIO
SwiftNIO 是一个适用于 Swift 的高性能跨平台异步事件驱动网络应用框架,旨在快速开发可维护的协议服务器和客户端。
它类似于 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
。这提供了主要的 [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+。NIO 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
模块提供。另外,还有由NIOEmbedded
模块提供的EmbeddedEventLoop
,这是一个主要用于测试目的的虚拟事件循环。
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
在管道中发出事件。
SwiftNIO附带了许多内置的 ChannelHandler
,这些处理器提供有用的功能,例如 HTTP 解析。此外,高性能应用希望在尽可能多的 ChannelHandler
中实现它们的逻辑,因为这有助于避免上下文切换问题。
此外,SwiftNIO附带了一些Channel
实现。具体来说,它附带ServerSocketChannel
,这是一个用于接受传入连接的Channel
;SocketChannel
,这是一个用于TCP连接的Channel
;以及DatagramChannel
,这是一个用于UDP套接字的Channel
。所有这些都由NIOPosix
模块提供。它还提供EmbeddedChannel
,这是一个主要由测试使用的Channel
,由NIOEmbedded
模块提供。
关于阻塞的说明
关于ChannelPipeline
的一个重要注意事项是它们是线程安全的。这对于编写SwiftNIO应用程序非常重要,因为它允许你写更简单的ChannelHandler
,因为你知道它们不需要同步。
然而,这是通过将在同一线程上调度的所有代码放置到ChannelPipeline
上实现的EventLoop
来实现的。这意味着,作为一般规则,ChannelHandler
必须在不将其调度到后台线程的情况下调用阻塞代码。如果ChannelHandler
由于任何原因而阻塞,则直到阻塞调用完成,所有与父EventLoop
关联的Channel
都将无法前进。
这在使用SwiftNIO编写应用程序时是一个常见的问题。如果要在管道中编写阻塞样式的代码,强烈建议在完成工作后将其调度到不同的线程。
引导
虽然直接使用EventLoop
配置和注册Channel
是可能的,但通常有一个更高层次的抽象来处理这项工作会更有用。
因此,SwiftNIO 提供了一系列 Bootstrap
对象,其目的是简化通道的创建。一些 Bootstrap
对象还提供了其他功能,例如支持 Happy Eyeballs 以进行 TCP 连接尝试。
当前 SwiftNIO 在 NIOPosix
模块中提供了三个 Bootstrap
对象:ServerBootstrap
,用于启动监听通道;ClientBootstrap
,用于启动客户端 TCP 通道;以及 DatagramBootstrap
,用于启动 UDP 通道。
ByteBuffer
SwiftNIO 应用中的大部分工作都涉及到字节缓冲区的移动。至少,数据在网络中以缓冲区形式发送和接收。因此,拥有一种高性能的数据结构,它针对 SwiftNIO 应用执行的工作进行优化,非常重要。
因此,SwiftNIO 提供了 ByteBuffer
,一个快速的 copy-on-write 字节缓冲区,这是大多数 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>
回调进行过多的同步。
另一个需要考虑的重要话题是传递给 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包”->“添加包依赖项”将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,并在本地主机(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