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所需的核心抽象和类型(更多细节请参见"概念概述")。大多数NIO扩展项目,如提供新的EventLoop
和Channel
或新的协议实现,只需要依赖于NIOCore
。NIOPosix
。它提供了主要的EventLoopGroup
、EventLoop
以及Channel
,供POSIX系统使用。这是我们高性能的核心I/O层。一般情况下,只有计划进行实际I/O操作的项目,如高级协议实现或应用程序,才应该导入此组件。NIOEmbedded
。它提供了EmbeddedChannel
和EmbeddedEventLoop
,这些都是对执行有细粒度控制的核心NIO抽象实现。这些通常用于测试,但也可以用于以解耦网络的方式驱动协议实现。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 | 不适用 | 官方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的版本范围,从你需要的最低版本到下一个主版本的版本。在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
要么是入站处理程序,要么是出站处理程序,或者两者都是。入站处理程序处理“入站”事件:像从套接字读取数据、读取套接字关闭或其他由远程对端发起的事件。出站处理程序处理“出站”事件,如写入、连接尝试和本地套接字关闭。
每个处理器按顺序处理事件。例如,读事件从管道的前端传递到后端,每次传递一个处理器,而写事件从管道的后端传递到前端。每个处理器在任何时候都可以生成入站或出站事件,并将它们发送到下一个处理器,方向取决于具体情况。这使得处理器可以分解读取、合并写入、延迟连接尝试,以及通常对事件进行任意转换。
通常,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 应用程序时是一个常见的担忧。如果你需要以阻塞样式编写代码,在 pipeline 中完成代码后将其调度到其他线程是非常推荐的。
Bootstrap
尽管可以直接使用 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 文档。
承诺和未来
编写并发代码与编写同步代码之间的一个主要区别是并非所有操作都会立即完成。例如,当您在通道上写入数据时,事件循环可能无法立即将写入操作发送到网络。因此,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 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