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开始的每个版本(直到不包括3.0.0)。SemVer和SwiftNIO的公共API担保应确保程序能够正常工作,无需担心测试每个版本的兼容性。
概念概述
SwiftNIO本质上是构建高性能网络应用程序的低级工具,尤其针对使用“每个连接一个线程”的并发模型效率低下或不切实际的情况。这在构建使用大量相对低利用率连接的服务器时是一个常见的限制,例如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
,一个协议EventLoop
,一个协议Channel
,一个协议ChannelHandler
,一个协议Bootstrap
及其相关结构ByteBuffer
,一个结构体EventLoopFuture
,一个泛型类EventLoopPromise
,一个泛型结构体。
所有SwiftNIO应用最终都是由这些各种组件构成的。
事件循环与事件循环组
SwiftNIO的基本I/O原语是事件循环。事件循环是一个等待事件(通常是I/O相关的事件,如“收到数据”)发生的对象,当事件发生时触发某些类型的回调。在几乎所有SwiftNIO应用中,事件循环的数量相对较少:通常为所用CPU核心数的一个或两个。一般而言,事件循环运行整个应用的生命周期,在无限循环中调度事件。
事件循环可以组合成事件循环组。这些组提供了一种机制来在事件循环之间分发工作。例如,在监听入站连接时,监听套接字将在一个事件循环上注册。然而,我们不想将接受该监听套接字的所有连接都注册到同一事件循环上,因为这可能会使一个事件循环超载,而其他事件循环为空。因此,事件循环组提供了一种将负载分散到多个事件循环的能力。
在 SwiftNIO 当今,有一个 EventLoopGroup
实现,以及两个 EventLoop
实现。对于生产应用程序,有 MultiThreadedEventLoopGroup
,这是一个会创建多个线程(使用 POSIX pthreads
库)并在每个线程上放置一个 SelectableEventLoop
的 EventLoopGroup
。在文件描述符上管理 I/O 事件并调度工作的 SelectableEventLoop
是一个事件循环,根据目标系统,它可以使用选择器(要么是 kqueue
,要么是 epoll
)来管理 I/O 事件,并将工作分派给相应的事件循环。此外,还有 EmbeddedEventLoop
,这是一个主要的用于测试目的的虚拟事件循环。
EventLoop
具有许多重要属性。最重要的是,它们是 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
相同的线程上实现的。这意味着作为一名规则,ChannelHandler
必须不获取阻塞代码而没有将其调度到后台线程。如果ChannelHandler
由于任何原因而发生阻塞,则附加到父EventLoop
的所有Channel
都无法进行进度,直到阻塞调用完成。
这在编写SwiftNIO应用程序时是一个常见的顾虑。如果您在管道中将代码以阻塞方式编写,则强烈建议您在处理完毕后将工作调度到不同的线程。
引导
虽然可以直接使用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文档。
承诺(Promises)和未来(Futures)
编写并发代码和同步代码之间的一大区别是,并非所有操作都会立即完成。例如,当您在一个通道上写入数据时,事件循环可能无法立即将该写入操作推送到网络。因此,SwiftNIO提供了EventLoopPromise
和EventLoopFuture
来管理异步完成操作。
一个 EventLoopFuture
实质上是一个在未来的某个时间点将填充的函数返回值的容器。每个 EventLoopFuture
都有一个对应的 EventLoopPromise
,这是一个将结果放进去的对象。当承诺(promise)成功时,未来(future)将会被实现(fulfilled)。
如果您必须轮询未来(future)以检测其何时完成,这将是相当低效的,所以 EventLoopFuture
被设计为有管理的回调。本质上,您可以在未来(future)上挂接回调,当结果可用时执行这些回调。即使在未来(future)结果不可用的情况下,EventLoopFuture
也将会仔细安排调度,以确保这些回调始终在最初创建承诺(promise)的事件循环上执行,这有助于确保您不必在EventLoopFuture
回调周围进行太多同步。
需要考虑的另一个重要问题是,将承诺传递给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/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
为了验证它是否正常工作,您可以使用另一个 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