SwiftNIO
SwiftNIO 是一个跨平台的异步事件驱动的网络应用程序框架,用于快速开发可维护的高性能协议服务器和客户端。
它就像 Netty,但是是为 Swift 编写的。
仓库组织
SwiftNIO 仓库跨越多个仓库
仓库 | NIO 2 (Swift 5+) | NIO 1 (Swift 4+) |
---|---|---|
https://github.com/apple/swift-nio SwiftNIO 核心 |
版本:从 "2.0.0" |
版本:从 "1.0.0" |
https://github.com/apple/swift-nio-ssl TLS (SSL) 支持 |
版本:从 "2.0.0" |
版本:从 "1.0.0" |
https://github.com/apple/swift-nio-http2 HTTP/2 支持 |
版本:从 "1.0.0" |
版本:从 "0.1.0" |
https://github.com/apple/swift-nio-extras 关于 SwiftNIO 的一些实用补充 |
版本:从 "1.0.0" |
版本:从 "0.1.0" |
https://github.com/apple/swift-nio-transport-services 为 macOS、iOS 和 tvOS 提供一流的支持 |
版本:从 "1.0.0" |
版本:从 "0.1.0" |
协议实现
以下是一些使用 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 项目 |
高级实现
高级实现通常是带有 API 的库,该 API 不公开 SwiftNIO 的 ChannelPipeline
,因此可以几乎不需(或无需)使用 SwiftNIO 特定知识。下面列出的实现都在 SwiftNIO 中执行所有 I/O,并且很好地与 SwiftNIO 生态系统集成。
协议 | 客户端 | 服务器 | 仓库 | 模块 | 评论 |
---|---|---|---|---|---|
HTTP | swift-server/async-http-client | AsyncHTTPClient |
SSWG 社区项目 | ||
gRPC | grpc/grpc-swift | GRPC |
也提供低级 API;社区项目 | ||
APNS | kylebrowning/APNSwift | APNSwift |
SSWG 社区项目 | ||
PostgreSQL | vapor/postgres-nio | PostgresNIO |
SSWG 社区项目 | ||
Redis | mordil/swift-redi-stack | RediStack |
SSWG 社区项目 |
支持的平台
SwiftNIO 致力于支持所有 Swift 支持的平台。目前,它在 macOS 和 Linux 上开发和测试,并已知支持以下操作系统版本
- Ubuntu 14.04+
- macOS 10.12+; (macOS 10.14+、iOS 12+ 或 tvOS 12+ 与 swift-nio-transport-services 一起)
Swift 版本
SwiftNIO 1
最新发布的SwiftNIO 1版本支持Swift 4.0,4.1,4.2和5.0。
SwiftNIO 2
最新发布的SwiftNIO 2版本仅支持Swift 5.0,5.1和5.2。如果您希望将SwiftNIO 1应用程序或库迁移到SwiftNIO 2,请查看我们为您准备的迁移指南。
兼容性
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 并不旨在提供类似网络框架等高级解决方案。相反,SwiftNIO 致力于提供这些高级应用的基础构件。在构建网络应用时,大多数用户并不会直接使用 SwiftNIO:相反,他们会选择在 Swift 生态系统中可用的众多优秀网络框架之一。然而,这些网络框架可能会在内部选择使用 SwiftNIO 来提供它们的网络支持。
以下部分将描述 SwiftNIO 提供的底层工具,并提供如何使用它们的简要概述。如果您对这些概念感到熟悉,那么可以跳转到本 README 中的其他部分。
基本架构
SwiftNIO 的基本构件包括以下 8 种类型的对象:
EventLoopGroup
,一个协议EventLoop
,一个协议Channel
,一个协议ChannelHandler
,一个协议Bootstrap
,几个相关结构ByteBuffer
,一个结构体EventLoopFuture
,一个泛型类EventLoopPromise
,一个泛型结构体。
所有 SwiftNIO 应用程序最终都是由这些各种组件构成的。
事件循环和事件循环组
SwiftNIO 的基本 I/O 原语是事件循环。事件循环是一个对象,它等待事件(通常是 I/O 相关事件,如“数据接收”)发生,并在这些事件发生时触发某种回调。几乎在所有 SwiftNIO 应用程序中,事件循环的数量都相对较少:通常每个应用程序希望使用的 CPU 内核只有一个或两个。一般来说,事件循环会运行整个应用程序的生命周期,以无限循环的形式分配事件。
事件循环被汇集到事件循环 组 中。这些组提供一种机制在事件循环中分配工作。例如,当监听传入连接时,监听套接字将注册在一个事件循环上。但是,我们不希望在该监听套接字上接受的所有连接都注册在同一个事件循环上,因为这可能会导致某个事件循环过载,而其他事件循环则空闲。因此,事件循环组提供了跨多个事件循环分散负载的能力。
在SwiftNIO中,目前有一个EventLoopGroup
实现和两个EventLoop
实现。对于生产应用,存在一个MultiThreadedEventLoopGroup
,这是一个创建一定数量的线程(使用POSIX pthreads
库)并为每个线程放置一个SelectableEventLoop
的EventLoopGroup
。这个SelectableEventLoop
是一个事件循环,它使用一个选择器(在目标系统为kqueue
或epoll
的情况下)来管理来自文件描述符的I/O事件并调度工作。此外,还有一个EmbeddedEventLoop
,这是一个主要用于测试目的的虚构事件循环。
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
中的位置。这些对象包含管道中前一个和下一个通道处理器的引用,确保通道处理器在整个管道运行期间都可以发出事件。
SwiftNIO提供了许多内置的 ChannelHandler
,提供如HTTP解析等有用功能。此外,高性能应用程序还希望尽可能多地将它们的逻辑放在 ChannelHandler
中,因为它有助于避免上下文切换的问题。
此外,SwiftNIO还包括了一些 Channel
实现。特别是包括 ServerSocketChannel
,一个用于接受入站连接的 Channel
;SocketChannel
,一个用于TCP连接的 Channel
;DatagramChannel
,一个用于UDP套接字的 Channel
;还有 EmbeddedChannel
,这是一个主要用于测试的 Channel
。
关于阻塞的说明
关于 ChannelPipeline
的一个重要注释是,它们是线程安全的。这对于编写 SwiftNIO 应用程序非常重要,因为它允许你编写更简单的 ChannelHandler
,因为这些操作不需要同步。
然而,这是通过在 EventLoop
的同一线程上调度所有 ChannelPipeline
代码来实现的。这意味着,作为一般规则,ChannelHandler
不应在不将其调度到后台线程的情况下调用阻塞代码。如果 ChannelHandler
由于任何原因而阻塞,那么连接到父 EventLoop
的所有 Channel
都将无法继续进行,直到阻塞调用完成。
这在编写 SwiftNIO 应用程序时是一个常见的问题。如果你发现有必要以阻塞方式编写代码,那么当你完成管道中的工作后,推荐将工作分派到不同的线程。
Bootstrap
虽然直接配置和注册 Channel
和 EventLoop
是可能的,但通常更有用的是有高级抽象来处理这项工作。
因此,SwiftNIO 提供了一系列 Bootstrap
对象,其目的是简化通道的创建。一些 Bootstrap
对象还提供了其他功能,例如通过 Happy Eyeballs 支持制作 TCP 连接尝试。
目前 SwiftNIO 包括三个 Bootstrap
对象:ServerBootstrap
,用于引导监听通道;ClientBootstrap
,用于引导客户端 TCP 通道;以及 DatagramBootstrap
,用于引导 UDP 通道。
ByteBuffer
SwiftNIO 应用程序的大部分工作都涉及在字节缓冲区之间转移数据。至少,数据以字节数据缓冲区形式发送和接收。因此,拥有一种高性能数据结构来优化 SwiftNIO 应用程序所执行的工作类型非常重要。
因此,SwiftNIO 提供了 ByteBuffer
,这是一种快速的、支持写时复制的字节数据缓冲区,大多数 SwiftNIO 应用程序的关键构建块。
ByteBuffer
提供了多种有用功能,而且还提供了一些钩子,使其以 "不安全" 模式使用。这关闭了边界检查以提升性能,但可能将您的应用程序暴露于潜在的内存正确性问题。
一般情况下,强烈建议您始终在安全模式下使用 ByteBuffer
。
有关 ByteBuffer
API 的更多详细信息,请参阅下面的 API 文档。
承诺(Promises)和未来(Futures)
在编写并发代码与编写同步代码之间有一个重大区别:并非所有操作都会立即完成。例如,当您在通道上写入数据时,事件循环可能无法立即将该写入输出到网络。因此,SwiftNIO 提供了 EventLoopPromise<T>
和 EventLoopFuture<T>
来管理那些 异步 完成的操作。
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旨在作为一个强大的工具来构建网络应用程序和框架,但它并不 intend to be the perfect solution for all levels of abstraction. SwiftNIO专注于在 lowering levels of abstraction 提供基本 I/O 原语和协议实现,而将更具有表达性但较慢的抽象留给更广泛的社区来构建。SwiftNIO将被视为服务器端应用程序的基本构建块,而不一定是这些应用程序将直接使用的框架。
需要从其网络堆栈中获得极高性能的应用程序可以选择直接使用 SwiftNIO 以减少其抽象的开销。这些应用程序应该能够以相对较低的费用保持极高的性能。SwiftNIO还专注于为此用途案例提供有用的抽象,从而使可以构建性能极高的网络服务器。
SwiftNIO的核心存储库将直接包含一些非常重要的协议实现,例如HTTP。然而,我们认为大多数协议实现应该与底层网络堆栈的发布周期分离(因为可能非常不同,要么快得多,要么慢得多)。因此,我们积极鼓励社区在-tree之外开发和维护其协议实现。实际上,一些SwiftNIO协议实现,包括我们的TLS和HTTP/2绑定,都是在-tree之外开发的!
文档
示例用法
目前有几个示例项目演示了如何使用SwiftNIO。
- 聊天客户端 https://github.com/apple/swift-nio/tree/master/Sources/NIOChatClient
- 聊天服务器 https://github.com/apple/swift-nio/tree/master/Sources/NIOChatServer
- 回声客户端 https://github.com/apple/swift-nio/tree/master/Sources/NIOEchoClient
- 回声服务器 https://github.com/apple/swift-nio/tree/master/Sources/NIOEchoServer
- UDP回声客户端 https://github.com/apple/swift-nio/tree/master/Sources/NIOUDPEchoClient
- UDP回声服务器 https://github.com/apple/swift-nio/tree/master/Sources/NIOUDPEchoServer
- HTTP客户端 https://github.com/apple/swift-nio/tree/master/Sources/NIOHTTP1Client
- HTTP服务器 https://github.com/apple/swift-nio/tree/master/Sources/NIOHTTP1Server
- WebSocket客户端 https://github.com/apple/swift-nio/tree/master/Sources/NIOWebSocketClient
- WebSocket服务器 https://github.com/apple/swift-nio/tree/master/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中略有不同。例如,如果您想依赖于NIO
和NIOHTTP1
模块,请指定以下依赖项
swift-tools-version:5.[01]
)
Swift 5.0 和 5.1 (dependencies: ["NIO", "NIOHTTP1"]
swift-tools-version:5.2
)
Swift 5.2 (dependencies: [.product(name: "NIO", package: "swift-nio"),
.product(name: "NIOHTTP1", package: "swift-nio")]
使用Xcode包支持
如果您已将项目设置为Xcode项目并且使用Xcode 11+,则可以通过点击“文件”->“Swift包”->“添加包依赖项”将SwiftNIO作为依赖项添加到Xcode项目中。在即将出现的对话框中,请输入https://github.com/apple/swift-nio.git
并点击两次“下一步”。最后,选择您计划使用的目标(例如NIO
、NIOHTTP1
和NIOFoundationCompat
),然后点击“完成”。现在您可以在项目中导入NIO
(以及您选择的所有其他目标)。
要对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
进行测试。
开发SwiftNIO
注意:本节仅适用于您想自行开发SwiftNIO的情况。如果只是想将SwiftNIO作为SwiftPM包使用,您可以忽略此处的信息。
总的来说,SwiftNIO的开发就像其他任何SwiftPM项目一样简单。但是,在我们贡献之前,有一些流程是值得理解的。有关详细信息,请参阅此存储库中的CONTRIBUTING.md
。
先决条件
SwiftNIO的master
分支是用于SwiftNIO 2下一个版本的开发分支,它仅支持Swift 5。
要能够编译和运行SwiftNIO以及集成测试,您需要在您的系统中安装一些先决条件。
macOS
- Xcode 10.2 或更高版本,建议使用 Xcode 11。
Linux
- Swift 5.0、5.1 或 5.2,可在 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