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 版本到下一个主要版本。在 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并不旨在提供高级解决方案,例如,例如,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
库)的EventLoopGroup
,并在每个线程上放置一个SelectableEventLoop
。SelectableEventLoop
是一个使用选择器(根据目标系统是kqueue
还是epoll
)来管理文件描述符的I/O事件并调度工作的事件循环。此外,还有一个EmbeddedEventLoop
,这是一个主要用于测试的虚拟事件循环。
EventLoop
有几个重要的属性。最重要的是,它们是SwiftNIO应用程序中所有工作完成的方式。为了确保线程安全,任何希望在SwiftNIO的几乎所有其他对象上执行的工作都必须通过一个EventLoop
来调度。EventLoop
对象几乎拥有SwiftNIO应用程序中的所有其他对象,了解它们的执行模型对于构建高性能的SwiftNIO应用程序至关重要。
Channels,Channel Handlers,Channel Pipelines,and Channel Contexts
虽然EventLoop
对SwiftNIO的工作至关重要,但大多数用户不会与它们进行实质性交互,而只是要求创建EventLoopPromise
和安排工作。用户将花费最多时间与之交互的SwiftNIO应用程序的部分是最多与Channel
和ChannelHandler
交互。
在SwiftNIO程序中,与用户交互的几乎每个文件描述符都与一个单独的 Channel
相关联。这个 Channel
拥有这个文件描述符,并负责管理其生命周期。它还负责处理那个文件描述符上的传入和传出事件:每当事件循环有一个对应于文件描述符的事件时,它将通知拥有该文件描述符的 Channel
。
然而,仅有 Channel
并没有用途。毕竟,很少有应用程序不希望对其在套接字上发送或接收的数据进行处理!因此,Channel
的另一个重要部分是 ChannelPipeline
。
ChannelPipeline
是一系列对象,称为 ChannelHandler
,用于在 Channel
上处理事件。这些 ChannelHandler
按顺序处理这些事件,变换和转换事件。这可以看作是一个数据处理管道;因此得名 ChannelPipeline
。
所有 ChannelHandler
均为入站或出站处理器,或两者兼有。入站处理器处理“入站”事件:例如从套接字读取数据、读取套接字关闭或其他由远程对端发起的事件。出站处理器处理“出站”事件,例如写入、连接尝试和本地套接字关闭。
每个处理器按顺序处理事件。例如,读取事件从前端依次传递到后端,每个处理器一次,而写入事件从后端依次传递到前端。每个处理器可以在任何时候生成入站或出站事件,这些事件将按照适当的方向发送到下一个处理器。这允许处理器分割读取、合并写入、延迟连接尝试,以及通常对事件进行任意转换。
一般来说,ChannelHandler
设计为高度可重用的组件。这意味着它们往往被设计得尽可能小,执行一项具体的数据转换。这允许处理器以前所未有的和新颖的方式组合在一起,这有助于代码重用和封装。
ChannelHandler
可以通过使用 ChannelHandlerContext
来跟踪它们在 ChannelPipeline
中的位置。这些对象包含对管道中先前和下一个 ChannelHandler
的引用,确保 ChannelHandler
总是在管道中发出事件。
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应用时,这通常是一个常见的问题。如果你有用阻塞风格编写代码的需求,在完成pipeline中的操作后,强烈建议将工作调度到不同的线程。
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文档。
承诺和未来(Promises and Futures)
编写异步代码与编写同步代码之间的一个主要区别是,并非所有操作都会立即完成。例如,当你在通道上写入数据时,事件循环可能无法立即将此写入操作刷新到网络。因此,SwiftNIO提供了用于异步完成操作的EventLoopPromise<T>
和EventLoopFuture<T>
。
一个EventLoopFuture<T>
基本上是一个用于将在未来某个时间填充的函数返回值的容器。每个EventLoopFuture<T>
都有一个对应的EventLoopPromise<T>
,该对象是用来放置结果的对象。当承诺成功时,未来将会得到解决。
如果需要轮询未来以检测其何时完成,这会非常低效,因此 EventLoopFuture<T>
被设计为具有管理回调和。本质上,您可以将回调附加到表示当结果可用时将执行的future。甚至 EventLoopFuture<T>
也将仔细安排调度以确保这些回调解在每个最初创建承诺的事件循环中执行,这有助于确保您在EventLoopFuture<T>
回调和周围不需要太多的同步。
另一个重要的话题是,Channel
的 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,只需将dependencies
子句添加到您的Package.swift
文件中即可
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
要验证它是否工作,你可以使用另一个壳来尝试连接到它
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
将创建一个具有 Swift 运行时的基本镜像,编译 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.org/download安装Swift 5.0、5.1或5.2版本。我们始终推荐使用最新发布的版本。
- 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