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 操作。
低级协议实现
低级协议实现通常是一系列实现某种协议但仍然需要用户对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项目 |
高级实现
高级实现通常是一些提供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开始的每个版本,直到(不包括)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
来调度。在 SwiftNIO 应用程序中,EventLoop
对象拥有几乎所有其他对象,理解它们的执行模型对于构建高性能的 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
(一种接受入站连接的套接字通道)、SocketChannel
(一种 TCP 连接通道)、DatagramChannel
(一种 UDP 套接字通道)以及 EmbeddedChannel
(主要用于测试的通道)。
关于阻塞的注意事项
ChannelPipeline
的一个重要注意事项是它们是线程安全的。这对于编写 SwiftNIO 应用的代码非常重要,因为它允许您在知道它们不需要同步的情况下编写更简单的 ChannelHandler
。
然而,这是通过将所有代码调度到与 EventLoop
相同的线程上实现的。这意味着,作为一般规则,ChannelHandler
必须在不将其调度到后台线程的情况下调用阻塞代码。如果 ChannelHandler
由于任何原因而阻塞,则与其父 EventLoop
关联的所有 Channel
都将无法前进,直到阻塞调用完成。
这是在编写 SwiftNIO 应用时常见的一个问题。如果需要在阻塞风格中编写代码,强烈建议在您的管道中完成工作后将其调度到不同的线程。
Bootstrap
尽管可以与 EventLoop
直接配置和注册 Channel
,但通常有一个更高级的抽象来处理这项工作会更有用。
因此,SwiftNIO 提供了一些 Bootstrap
对象,其目的是简化通道的创建。一些 Bootstrap
对象还提供其他功能,例如对 Happy Eyeballs 的支持,以进行 TCP 连接尝试。
目前 SwiftNIO 配备了三个 Bootstrap
对象:用于启动监听通道的 ServerBootstrap
,用于启动客户端 TCP 通道的 ClientBootstrap
,以及用于启动 UDP 通道的 DatagramBootstrap
。
ByteBuffer
在SwiftNIO应用程序中,大部分工作涉及对字节缓冲区进行重新排列。至少,数据在网络中以字节缓冲区的形式发送和接收。因此,拥有一个针对SwiftNIO应用程序所执行的工作进行优化的高性能数据结构非常重要。
出于这个原因,SwiftNIO提供了ByteBuffer
,这是一个快速的写时复制字节缓冲区,构成了大多数SwiftNIO应用程序的关键构建块。
ByteBuffer
提供了一些有用的功能,并且还提供了一些钩子,用于将其以“不安全”模式使用。这关闭了边界检查,以提升性能,但可能使您的应用程序面临内存正确性问题。
总的来说,我们强烈建议您始终在安全模式下使用ByteBuffer
。
关于ByteBuffer
的API的更多详细信息,请参阅以下链接中的API文档。
承诺与未来
编写并发的代码与编写同步代码之间的一大区别是,并非所有操作都会立即完成。例如,当您在通道上写入数据时,事件循环可能无法立即将该写入操作 flush 到网络中。为此,SwiftNIO提供了EventLoopPromise<T>
和EventLoopFuture<T>
来管理异步完成的操作。
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/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
)并点击“完成”。现在您可以在项目中 import NIO
(以及您选择的其它目标)。
要直接在 SwiftNIO 上进行工作,或调查一些演示应用程序,您可以克隆存储库并使用 SwiftPM 帮助构建它。例如,您可以运行以下命令来编译和运行示例 echo 服务器:
swift build
swift test
swift run NIOEchoServer
为了验证它是否正常工作,您可以另开一个 shells 尝试连接到它
echo "Hello SwiftNIO" | nc localhost 9999
如果一切顺利,您将看到 Echoed 消息返回给您。
要在 Xcode 11+ 中在 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
将创建一个包含 Swift 运行时和其他构建和测试依赖项的基本镜像,编译 SwiftNIO 并在
localhost:9999
上运行一个样本NIOEchoServer
。通过以下命令测试它:echo Hello SwiftNIO | nc localhost 9999
。 -
docker-compose -f docker/docker-compose.yaml up http
将创建一个包含 Swift 运行时和其他构建和测试依赖项的基本镜像,编译 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