BT

如何利用碎片时间提升技术认知与能力? 点击获取答案

苹果公司开源的Swift版Netty:SwiftNIO

| 作者 SwiftNIO 关注 0 他的粉丝 ,译者 无明 关注 2 他的粉丝 发布于 2018年4月3日. 估计阅读时间: 15 分钟 | QCon上海2018 关注大数据平台技术选型、搭建、系统迁移和优化的经验。

SwfitNIO是由苹果公司开源的一款基于事件驱动的跨平台网络应用程序开发框架,其目标是帮助开发者快速开发出高性能且易于维护的服务器端和客户端应用协议。

它有点类似Netty,但开发语言使用的是Swift。

基本概念

SwfitNIO实际上是一个底层工具,用于开发高性能的网络应用程序,作为“每连接一个线程”的替代方案。

为了提升性能,SwfitNIO使用了非阻塞IO,这从它的名字就可以看出来。非阻塞IO与阻塞式IO非常不一样,因为不管是往网络上发送数据还是从网络上接收数据,应用程序都无需等待,系统内核会在有可操作的IO时通知SwfitNIO。

SwfitNIO并不会提供类似Web框架那样的解决方案,而是致力于为上层框架提供底层的构建块。在开发Web应用程序时,大部分开发者不会直接使用SwfitNIO,他们会从Swift生态系统众多的Web框架中选择一个。不过,这些框架中的大部分都使用了SwfitNIO。

受支持的平台

SwfitNIO的目标是支持所有可以运行Swift的平台。目前,SwfitNIO可以在macOS和Linux上运行,包括:

  • Ubuntu 14.04+
  • macOS 10.12+

基本架构

SwfitNIO包含了几种基本构建块,所有的SwfitNIO应用程序都是由这几种组件组成的。

  • EventLoopGroup
  • EventLoop
  • Channel
  • ChannelHandler
  • Bootstrap
  • ByteBuffer
  • EventLoopPromise和EventLoopFuture

EventLoopPromise和EventLoopFuture

EventLoop是SwfitNIO最基本的IO原语,它等待事件的发生,在发生事件时触发某种回调操作。在大部分SwfitNIO应用程序中,EventLoop对象的数量并不多,通常每个CPU核数对应一到两个EventLoop对象。一般来说,EventLoop会在应用程序的整个生命周期中存在,进行无限的事件分发。

EventLoop可以组合成EventLoopGroup,EventLoopGroup提供了一种机制用于在各个EventLoop间分发工作负载。例如,服务器在监听外部连接时,用于监听连接的socket会被注册到一个EventLoop上。但我们不希望这个EventLoop承担所有的连接负载,那么就可以通过EventLoopGroup在多个EventLoop间分摊连接负载。

目前,SwiftNIO提供了一个EventLoopGroup实现(MultiThreadedEventLoopGroup)和两个EventLoop实现(SelectableEventLoop和EmbeddedEventLoop)。

MultiThreadedEventLoopGroup会创建多个线程(使用POSIX的pthreads库),并为每个线程分配一个SelectableEventLoop对象。

SelectableEventLoop使用选择器(基于kqueue或epoll)来管理来自文件和网络IO事件。EmbeddedEventLoop是一个空的EventLoop,什么事也不做,主要用于测试。

Channels、ChannelHandler、ChannelPipeline和ChannelHandlerContext

尽管EventLoop非常重要,但大部分开发者并不会与它有太多的交互,最多就是用它创建EventLoopPromise和调度作业。开发者经常用到的是Channel和ChannelHandler。

每个文件描述符对应一个Channel,Channel负责管理文件描述符的生命周期,并处理发生在文件描述符上的事件:每当EventLoop检测到一个与相应的文件描述符相关的事件,就会通知Channel。

ChannelPipeline由一系列ChannelHandler组成,ChannelHandler负责按顺序处理Channel中的事件。ChannelPipeline就像数据处理管道一样,所以才有了这个名字。

ChannelHandler要么是Inbound,要么是Outbound,要么两者兼有。Inbound的ChannelHandler负责处理“inbound”事件,例如从socket读取数据、关闭socket或者其他由远程发起的事件。Outbound的ChannelHandler负责处理“outbound”事件,例如写数据、发起连接以及关闭本地socket。

ChannelHandler按照一定顺序处理事件,例如,读取事件从管道的前面传到后面,而写入事件则从管道的后面传到前面。每个ChannelHandler都会在处理完一个事件后生成一个新的事件给下一个ChannelHandler。

ChannelHandler是高度可重用的组件,所以尽可能设计得轻量级,每个ChannelHandler只处理一种数据转换,这样就可以灵活组合各种ChannelHandler,提升代码的可重用性和封装性。

我们可以通过ChannelHandlerContext来跟踪ChannelHandler在ChannelPipeline中的位置。ChannelHandlerContext包含了当前ChannelHandler到上一个和下一个ChannelHandler的引用,因此,在任何时候,只要ChannelHandler还在管道当中,就能触发新事件。

SwiftNIO内置了多种ChannelHandler,包括HTTP解析器。另外,SwiftNIO还提供了一些Channel实现,比如ServerSocketChannel(用于接收连接)、SocketChannel(用于TCP连接)、DatagramChannel(用于UDP socket)和EmbeddedChannel(用于测试)。

Bootstrap

SwiftNIO提供了一些Bootstrap对象,用于简化Channel的创建。有些Bootstrap对象还提供了其他的一些功能,比如支持Happy Eyeballs。

目前SwiftNIO提供了三种Bootstrap:ServerBootstrap(用于监听Channel),ClientBootstrap(用于TCP Channel)和DatagramBootstrap(用于UDP Channel)。

ByteBuffer

SwiftNIO提供了ByteBuffer,一种快速的Copy-On-Write字节缓冲器,是大部分SwiftNIO应用程序的关键构建块。

ByteBuffer提供了很多有用的特性以及一些“钩子”,通过这些钩子,我们可以在“unsafe”的模式下使用ByteBuffer。这种方式可以获得更好的性能,代价是应用程序有可能出现内存问题。在一般情况下,还是建议在安全模式下使用ByteBuffer。

EventLoopPromise和EventLoopFuture

并发代码和同步代码之间最主要的区别在于并非所有的动作都能够立即完成。例如,在向一个Channel写入数据时,EventLoop有可能不会立即将数据冲刷到网络上。为此,SwiftNIO提供了EventLoopPromise<T>和EventLoopFuture<T>,用于管理异步操作。

EventLoopFuture<T>实际上是一个容器,用于存放函数在未来某个时刻的返回值。每个EventLoopFuture<T>对象都有一个对应的EventLoopPromise<T>,用于存放实际的结果。只要EventLoopPromise执行成功,EventLoopFuture也就完成了。

通过轮询的方式检查EventLoopFuture是否完成是一种非常低效的方式,所以EventLoopFuture被设计成可以接收回调函数。也就是说,在有结果的时候回调函数会被执行。

EventLoopFuture<T>负责处理调度工作,确保回调函数是在最初创建EventLoopPromise的那个EventLoop上执行,所以就没有必要再针对回调函数做任何同步操作。

SwiftNIO的设计哲学

SwiftNIO的目标是要成为强大的网络应用程序开发框架,但并不想为所有的层次抽象提供完美的解决方案。SwiftNIO主要专注在基本的IO原语和底层的协议实现上,将其他层次的抽象留给广大的社区去构建。SwiftNIO将成为服务器端应用程序的构建块,但不一定就是应用程序直接拿来使用的框架。

对性能有很高要求的应用程序可能会直接使用SwiftNIO,减少上层抽象所带来的开销。SwiftNIO可以帮助这些应用程序在提升性能的同时降低维护成本。SwiftNIO还为某些场景提供了有用的抽象,高性能的网络服务器可以直接使用这些抽象。

SwiftNIO的核心仓库提供了一些非常重要的协议实现,比如HTTP。不过,我们认为,大部分协议的实现应该要与底层的网络栈分开,因为它们的发布节奏是很不一样的。为此,我们鼓励社区自己去实现和维护他们的协议实现。实际上,SwiftNIO提供的一些协议实现最初就是由社区开发的,比如TLS和HTTP/2。

有用的协议文档

下面的项目提供了非常有用的协议实现文档:

文档

API文档(https://apple.github.io/swift-nio/docs/current/NIO/index.html)。

运行示例

目前有一些例子,演示了如何使用SwiftNIO。

开始使用SwiftNIO

SwiftNIO使用SwiftPM作为构建工具,要想在项目中使用SwiftNIO,只需要在Package.swift中加入依赖:

dependencies: [
  .package(url: "https://github.com/apple/swift-nio.git", from: "1.0.0")
]

然后将相应的SwiftNIO模块加入到依赖列表当中即可。

可以将SwiftNIO代码库复制到本地,然后使用SwiftPM来构建它。例如,通过以下的命令来编译和运行Echo服务器:

swift build
swift test
swift run NIOEchoServer

然后通过控制台验证服务器是否运行正常:

echo "Hello SwiftNIO" | nc localhost 9999

如果一切正常,就会看到控制台上打印出来的消息。

另外,还可以通过docker-compose来构建和运行示例代码。

首先要确保在本地安装好了Docker,然后切换到docker目录,并运行下面的命令:

docker-compose up test

这个命令将创建一个包含Swift 4.0的基础镜像,并编译SwiftNIO以及运行测试用例。

docker-compose up echo

这个命令将创建一个基础镜像,编译SwiftNIO,并在localhost:9999上运行NIOEchoServer。可以通过echo Hellp SwiftNIO | nc localhost 9999来测试它。

docker-compose up http

这个命令将创建一个基础镜像,编译SwiftNIO,并在localhost:8888上运行NIOHTTP1Server。可以通过curl http://localhost:8888来测试它。

原文链接https://github.com/apple/swift-nio

感谢覃云对本文的审校。

评价本文

专业度
风格

您好,朋友!

您需要 注册一个InfoQ账号 或者 才能进行评论。在您完成注册后还需要进行一些设置。

获得来自InfoQ的更多体验。

告诉我们您的想法

允许的HTML标签: a,b,br,blockquote,i,li,pre,u,ul,p

当有人回复此评论时请E-mail通知我
社区评论

允许的HTML标签: a,b,br,blockquote,i,li,pre,u,ul,p

当有人回复此评论时请E-mail通知我

允许的HTML标签: a,b,br,blockquote,i,li,pre,u,ul,p

当有人回复此评论时请E-mail通知我

讨论

登陆InfoQ,与你最关心的话题互动。


找回密码....

Follow

关注你最喜爱的话题和作者

快速浏览网站内你所感兴趣话题的精选内容。

Like

内容自由定制

选择想要阅读的主题和喜爱的作者定制自己的新闻源。

Notifications

获取更新

设置通知机制以获取内容更新对您而言是否重要

BT