SwiftNIO 原理 封面
Vapor 大型教程

SwiftNIO 原理

Vapor 跑在 SwiftNIO 之上。本文带你俯瞰 SwiftNIO 的架构:事件循环、Channel 流水线、Bootstrap、ByteBuffer,以及 Promise/Future 的协作方式。

SwiftNIO 概述

Vapor 是基于 SwiftNIO 构建的,所以最好还是对它进行一些了解,不过这也不是必需的,如果感兴趣,可以了解一下。不感兴趣也可以直接跳过本页内容。

SwiftNIO 是一个跨平台异步事件驱动的网络应用框架,基于它可以快速开发高性能可维护的协议服务器和客户端。SwiftNIO 是 Swift 生态中等价于 Java 生态下 Netty 的存在。

简介

SwiftNIO 是使用 Swift 语言构建高性能网络应用的基础层工具。主要是针对那些使用 每个网络链接对应一个线程 的并发模型,因为这种并发模型在网络链接并发量很大时会变的相对低效。SwiftNIO 为了解决这种低效问题,大量使用了非阻塞型 IO 模式,与常见的阻塞型 IO 模式相比,应用可以不必等待网络把数据发送出去或者接收回来后再进行操作,而是让操作系统内核在 IO 操作可以执行时通知 SwiftNIO,来实现异步操作处理。

SwiftNIO 的目标不是提供类似于 Web 框架的高级应用层解决方案,而是专注于为这些高级应用层提供底层网络能力。当要构建一个 Web 应用时,用户一般不会直接使用 SwiftNIO,他们会使用一些 Swift 生态中提供的 Web 框架完成任务,这些 Web 框架可能下层依赖的网络层基础能力就是 SwiftNIO 提供的。

SwiftNIO 的基础架构

SwiftNIO 通过 NIOCore 模块中定义的 8 种类型提供基础能力:

类型种类
EventLoopGroup协议
EventLoop协议
Channel协议
ChannelHandler协议
Bootstrap一组相关结构体
ByteBuffer结构体
EventLoopFuture范型类
EventLoopPromise范型结构体

所有的 SwiftNIO 应用最终都是通过上面的 8 种类型构建出来的。

EventLoops & EventLoopGroups

EventLoop 是 SwiftNIO 中的基本元素,它是用来等待 IO 事件的对象,当对应 IO 事件完成时,它会进行一些回调操作。通常使用 SwiftNIO 的应用会使用相对少量的 EventLoop,一般单个 CPU 核心对应一到两个 EventLoop,EventLoop 通常在应用的整个生命周期中一直保持运行,不断的循环执行并分发事件。

多个 EventLoop 可以被集合成 EventLoopGroup,EventLoopGroup 可以为它管理的多个 EventLoop 分配任务,平衡各 EventLoop 的工作量,避免其中一些 EventLoop 过载,而另外一些 EventLoop 空闲。

目前 SwiftNIO 提供了 EventLoopGroup 协议的一种实现,以及 EventLoop 协议的两种实现。

Channels & Channel Handlers & Channel Pipelines & Channel Contexts

SwiftNIO 用户使用 EventLoop 的主要场景是创建 EventLoopPromise 类型或者调度任务,但他们需要花大量精力来处理 ChannelChannelHandler

在 SwiftNIO 应用中,每一个被处理的文件描述符都需要与一个 Channel 结合,Channel 拥有这个文件描述符,并负责管理它的生命周期,同时也需要处理这个文件描述符相关的事件。当操作系统内核通知 EventLoop 一个文件描述符相关的事件时,EventLoop 就会通知拥有这个文件描述符的 Channel 对象。

Channel 本身并没有太大作用,它主要用来传递数据。ChannelPipeline 是由一系列 ChannelHander 对象组成,ChannelPipeline 用来处理 Channel 上传递过来的数据,相当于数据处理流水线。

ChannelHandler 可以通过 ChannelHandlerContext 了解自己在 ChannelPipeline 中的执行环境,获取自己前后结点对应的 ChannelHander 对象,保证事件可以在 ChannelPipeline 中双向流动。SwiftNIO 中内置了一些 ChannelHandler,用来进行特定目的的数据处理。

SwiftNIO 也内置了一些 Channel 协议的实现,例如:ServerSocketChannel 可以用来接受远端的 Socket 连接,SocketChannel 用来处理 TCP 连接,DatagramChannel 用来处理 UDP 数据包。EmbeddedChannel 用来写测试用例。

ChannelPipeline 是线程安全的,ChannelPipeline 中的所有代码逻辑都被放到同一个线程中执行,所以为了不阻塞 ChannelPipeline 的执行,需要每一个 ChannelHandler 中的逻辑都不能执行阻塞型的代码逻辑,否则会影响整个 ChannelPipeline 的执行效率。

Bootstrap

使用 EventLoop 也可以直接注册和配置 Channel,SwiftNIO 为了简化 Channel 的创建,也提供了一些内置对象,例如 ServerBootstrap 用来配置监听 Channel,ClientBootstrap 用来注册客户端 TCP 通道,DatagramBootstrap 用来注册 UDP 通道。

ByteBuffer

SwiftNIO 工作过程中,涉及到很多数据缓存区的处理操作,为了处理方便,提供了一些高性能数据结构。ByteBuffer 就是一种快速的支持写时复制特性的数据缓存区数据结构。ByteBuffer 提供了很多有用的特性,可以在非安全模式下使用,也可以关闭边界检查来提高执行性能。大部分场景,还是优先推荐在安全模式下使用 ByteBuffer 数据结构。

Promises & Futures

并发代码和同步代码主要的区别就是执行结果不能立刻返回。SwiftNIO 提供了 EventLoopPromise 和 EventLoopFuture 来处理需要异步返回的操作。

EventLoopFuture 本质上是函数返回值的占位容器。每一个 EventLoopFuture 都对应一个 EventLoopPromise。当函数的返回值确定下来时,会通过 EventLoopPromise 把函数实际的返回值塞入 EventLoopFuture 对象中。

如果通过轮询的方式检查 EventLoopFuture 是否完成,会非常低效。因此 EventLoopFuture 内部维护了一个回调列表。用户可以把自己的回调塞入 EventLoopFuture 内部维护的回调列表中,当 EventLoopFuture 获取一个返回结果时,会执行回调列表中的回调代码逻辑,这样用户就能获取到函数执行的结果了。同时为了线程安全,EventLoopFuture 的回调列表会被保证在与其对应的 EventLoopPromise 相同的 EventLoop 上执行,这样就能保证数据的同步访问,避免多线程问题。

如果看完这篇介绍,还是有点懵懂的话,下一步,就可以拉下 swift-nio 的仓库源码,看看 EventLoopPromise 和 EventLoopFuture 的具体实现了。写代码就像写文章一样,能够读懂优秀仓库的代码,就相当于可以读懂名著一样,对个人提升来说是最快和最直接的。

本系列其他文章