SwiftNIO 2.40.0

SwiftNIO 2.40.0

Daniel AlmPeter AdamsGeorge BarnettCory BenfieldJohannes WeissJake PrickettDavid Evans 维护。



 
依赖
CNIOWindows= 2.40.0
CNIOLinux= 2.40.0
SwiftNIOCore= 2.40.0
_NIODataStructures= 2.40.0
SwiftNIOPosix= 2.40.0
CNIOAtomics= 2.40.0
SwiftNIOEmbedded= 2.40.0
SwiftNIOConcurrencyHelpers= 2.40.0
CNIODarwin= 2.40.0
 

SwiftNIO 2.40.0

  • 作者:
  • 苹果公司 Inc.

sswg:graduated|104x20

SwiftNIO

SwiftNIO 是一个跨平台的异步事件驱动网络应用程序框架,用于快速开发可维护的高性能协议服务器和客户端。

它类似于 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+。

在本仓库中,我们拥有多个产品,它们提供了不同的功能。本软件包包含以下产品

协议实现

以下是一些使用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 无可用品 官方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团队不再积极开发此版本。不会向此版本添加新功能,但直到2022年5月底将接受修复错误或安全漏洞的PR。

如果您想要将SwiftNIO 1应用程序或库迁移到SwiftNIO 2,请查阅我们为您准备的迁移指南

最新发布的SwiftNIO 1版本支持Swift 4.0、4.1、4.2和5.0。

支持的平台

SwiftNIO旨在支持所有Swift支持的平台。目前,它已在macOS和Linux上开发和测试,并已知支持以下操作系统版本

兼容性

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:相反,他们可能想使用可用的许多优秀的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 库),每个线程上都放置一个 SelectableEventLoopSelectableEventLoop 是一个使用选择器(根据目标系统是 kqueue 还是 epoll)来管理来自文件描述符的 I/O 事件并调度工作的事件循环。这些 EventLoopEventLoopGroupNIOPosix 模块提供。此外,还有一个 EmbeddedEventLoop,这是一个主要用于测试的虚拟事件循环,由 NIOEmbedded 模块提供。

EventLoop 有许多重要属性。最重要的是,它们是 SwiftNIO 应用程序中所有工作的执行方式。为了确保线程安全,几乎所有想在 SwiftNIO 中执行的、几乎在其他任何对象上的工作都必须通过 EventLoop 来调度。这些 EventLoop 对象拥有 SwiftNIO 应用程序中的几乎所有其他对象,理解它们的执行模型对于构建高性能的 SwiftNIO 应用程序至关重要。

通道、通道处理器、通道流水线和通道上下文

虽然 EventLoop 对于 SwiftNIO 的工作方式至关重要,但大多数用户不会与它们实质性交互,而只是要求它们创建 EventLoopPromise 并安排工作。大多数用户将与 SwiftNIO 应用程序的一部分花费最多的时间是:ChannelChannelHandler

几乎每个用户在 SwiftNIO 程序中与之交互的文件描述符都与单个 Channel 相关联。这个 Channel 拥有该文件描述符,并负责管理其生命周期。它还负责处理该文件描述符上的入站和出站事件:只要事件循环有对应文件描述符的事件,它就会通知拥有该文件描述符的 Channel

Channel 本身并没有什么用处。毕竟,很少有人不想处理在套接字上发送或接收的数据!所以,Channel 的另一个重要部分就是 ChannelPipeline

ChannelPipeline 是一系列称为 ChannelHandler 的对象,这些对象会在 Channel 上处理事件。这些 ChannelHandler 将按顺序处理这些事件,并随着事件的传播对其进行修改和转换。这可以被视为一个数据处理管道;因此得名 ChannelPipeline

所有 ChannelHandler 都是 Inbound 或 Outbound 处理器,或者两者兼备。Inbound 处理器处理“入站”事件,例如从套接字读取数据、读取套接字关闭或其他由远程对端发起的事件。Outbound 处理器处理“出站”事件,如写入、连接尝试和本地套接字关闭。

每个处理器都会按顺序处理事件。例如,读取事件从管道前端传递到后端,一个个处理器传递,而写入事件则从后端传递到前端。每个处理器在任何时候都可以生成入站或出站事件,这些事件将以适当的方向发送到下一个处理器。这允许处理器拆分读取、合并写入、延迟连接尝试以及一般地对事件进行任意转换。

通常,ChannelHandler 被设计成高度可重用的组件。这意味着它们倾向于尽可能小,执行一个特定的数据转换。这允许处理器以新颖和灵活的方式组合在一起,有助于代码重用和封装。

ChannelHandler 可以通过使用 ChannelHandlerContext 来跟踪其在 ChannelPipeline 中的位置。这些对象包含对管道中先前和下一个通道处理器的引用,确保通道处理器在管道中保持位置时始终能够发出事件。

SwiftNIO自带了许多有用的内置 ChannelHandler,例如 HTTP 解析。此外,高性能应用程序将希望尽可能在地 ChannelHandler 中提供其逻辑,因为它有助于避免上下文切换问题。

此外,SwiftNIO还包含一些《Channel》实现。具体来说,它提供了《Channel》《Channel》实现,用于接受传入的连接;《Channel》《SocketChannel》实现,用于TCP连接;以及《Channel》《DatagramChannel》实现,用于UDP套接字。所有这些均由NIOPosix模块提供。它还提供了一个由NIOEmbedded模块提供的《EmbeddedChannel》《Channel》,该《Channel》主要用于测试。

关于阻塞的注意事项

关于《ChannelPipeline》的重要注意事项之一是它们是线程安全的。这对于编写SwiftNIO应用程序非常重要,因为它允许你编写简单的《ChannelHandler》,而不需要同步。

然而,这是通过将所有代码调度到与《EventLoop》相同的线程来实现的。这意味着,作为一般规则,《ChannelHandler》必须在将任务调度到后台线程之前不得调用阻塞代码。如果任何《ChannelHandler》因为任何原因而阻塞,则连接到父《EventLoop》的所有《Channel》将无法继续进度,直到阻塞调用完成。

在编写SwiftNIO应用程序时,这是一个常见的担忧。如果你在使用阻塞方式编写代码时发现它很有用,那么在你的管道中完成代码后将其调度到不同的线程是非常推荐的。

引导(Bootstrap)

尽管可以直接通过《Channel》《EventLoop》配置并注册,但通常使用更高层次的抽象来处理这项工作更有用。

因此,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文档。

Promises and Futures

编写并发代码与编写同步代码之间的一个主要差异是,并非所有操作都会立即完成。例如,当你在通道上写入数据时,事件循环可能无法立即将写入操作刷新到网络。因此,SwiftNIO提供了EventLoopPromise<T>EventLoopFuture<T>来管理异步操作。这些类型由NIOCore模块提供。

EventLoopFuture<T>本质上是一个将返回值存储的结构,这些值将在将来的某个时刻被填充。每个EventLoopFuture<T>都有一个相应的EventLoopPromise<T>,这是结果将被放入的对象。当承诺被成功时,未来将会被实现。

如果必须轮询未来以检测其何时完成,则这将非常低效,因此EventLoopFuture被设计为具有管理的回调。本质上,您可以在未来挂载回调,当有结果时将执行这些回调。《EventLoopFuture》甚至将仔细安排调度,以确保这些回调始终在最初创建承诺的事件循环上执行,这有助于确保您在《EventLoopFuture》回调周围不需要太多的同步。

另一个值得考虑的重要话题是传递给《codeirtual`close`的承诺与在《code虐《a href="https://apple.github.io/swift-nio/docs/current/NIOCore/Protocols/Channel.html" rel="nofollow">Channel》上的《code虐《a href="https://apple.github.io/swift-nio/docs/current/NIOCore/Classes/ChannelPipeline.html" rel="nofollow">ChannelPipeline》的`closeFuture`之间的区别。例如,传递到《code虐《a href="https://apple.github.io/swift-nio/docs/current/NIOCore/Protocols/Channel.html" rel="nofollow">Channel`的承诺在《code虐《a href="https://apple.github.io/swift-nio/docs/current/NIOCore/Protocols/Channel.html" rel="nofollow">Channel`关闭后、但在《code虐《a href="https://apple.github.io/swift-nio/docs/current/NIOCore/Classes/ChannelPipeline.html" rel="nofollow">ChannelPipeline》完全清除之前就会成功。这将允许在《code虐《a href="https://apple.github.io/swift-nio/docs/current/NIOCore/Classes/ChannelPipeline.html" rel="nofollow">ChannelPipeline》完全清除之前对其进行操作(如果需要的话)。如果希望在《code虐《a href="https://apple.github.io/swift-nio/docs/current/NIOCore/Protocols/Channel.html" rel="nofollow">Channel`关闭并《code虐《a href="https://apple.github.io/swift-nio/docs/current/NIOCore/Classes/ChannelPipeline.html" rel="nofollow">ChannelPipeline`清除且不进行任何其他操作的情况下等待,则更好的选择是等待《code虐《a href="https://apple.github.io/swift-nio/docs/current/NIOCore/Classes/Channel.html" rel="nofollow">Channel`关闭》成功。

有几个函数可以将回调应用于《code虐《a href="https://apple.github.io/swift-nio/docs/current/NIOCore/Classes/EventLoopFuture.html" rel="nofollow">EventLoopFuture》,具体取决于您想如何以及何时执行它们。这些函数的详细信息留待API文档中说明。

设计理念

SwiftNIO旨在成为构建网络应用程序和框架的强大工具,但它并不是为所有抽象级别提供完美解决方案的目的。SwiftNIO专注于在低抽象级别提供基本I/O原语和协议实现,将更具表现力但较慢的抽象留给更广泛的社区来构建。目的是使SwiftNIO成为服务器端应用程序的构建块,而不一定是应用程序将直接使用的框架。

需要从其网络堆栈中获得极高性能的应用程序可能选择直接使用SwiftNIO,以减少其抽象的开销。这些应用程序应能够在相对较低的维护成本下保持极高的性能。SwiftNIO还专注于为这种用例提供有用的抽象,以便可以直接构建具有极高性能的网络服务器。

SwiftNIO的核心库将直接在树中包含一些非常重要协议的实现,例如HTTP。然而,我们认为大多数协议实现应该与底层网络栈的发布周期解耦,因为其发布节奏很可能会非常不同(要么快很多,要么慢很多)。因此,我们积极鼓励社区开发并维护他们的协议实现,包括树外。事实上,一些SwiftNIO的第一方协议实现,包括我们的TLS和HTTP/2绑定,都是在树外开发的!

文档

使用示例

目前有几个示例项目展示了如何使用SwiftNIO。

要构建并运行它们,请运行以下命令,将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版本之间略有不同。例如,如果您想依赖于NIOCoreNIOPosixNIOHTTP1模块,指定以下依赖项

Swift 5.2及更高版本(《swift-tools-version: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并点击两次“下一步”。最后,选择你打算使用的目标(例如 NIOCoreNIOHTTP1NIOFoundationCompat)并点击完成。现在你将在项目中能够使用import NIOCore(以及你选择的所有其他目标)。

要修改SwiftNIO本身,或者调查一些演示应用,你可以直接克隆存储库,并使用SwiftPM帮助构建。例如,你可以执行以下命令以编译和运行示例echo服务器

swift build
swift test
swift run NIOEchoServer

为了验证它是否能正常工作,你可以使用另一个shell尝试连接到它

echo "Hello SwiftNIO" | nc localhost 9999

如果一切正常,你将看到消息被回显给你。

要在Xcode 11+中在Xcode中工作,你只需在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,并在本地主机:9999上运行一个示例NIOEcho服务器。通过echo Hello SwiftNIO | nc localhost 9999测试它。

  • docker-compose -f docker/docker-compose.yaml up http

    将创建一个包含Swift运行时和其他构建和测试依赖项的基础镜像,编译SwiftNIO,并在本地主机:8888上运行一个示例NIOHTTP1服务器。通过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