SwiftNIO is a cross-platform asynchronous event-driven network application framework for rapid development of maintainable high performance protocol servers & clients.git
It's like Netty, but written for Swift.github
SwfitNIO 是一款基于事件驱动的跨平台网络应用程序开发框架,其目标是帮助开发者快速开发出高性能且易于维护的服务器端和客户端应用协议。编程
对于喜欢探究本源的咱们能够先了解 Netty 的一些概念。swift
本文是篇整理性文章,主要来自:设计模式
Netty 做为异步事件驱动的网络,高性能之处主要来自于其 I/O 模型 和 线程处理模型,前者决定如何 收发数据,后者决定如何 处理数据。api
用什么样的通道将数据发送给对方?Java 中比较流行的 3 种 I/O 模型:安全
Netty 的非阻塞 I/O 的实现关键是基于 I/O 复用模型。服务器
Netty 的 IO 线程 NioEventLoop 因为聚合了多路复用器 Selector,能够同时并发处理成百上千个客户端链接。 当线程从某客户端 Socket 通道进行读写数据时,若没有数据可用时,该线程能够进行其余任务。 线程一般将非阻塞 IO 的空闲时间用于在其余通道上执行 IO 操做,因此单独的线程能够管理多个输入和输出通道。 因为读写操做都是非阻塞的,这就能够充分提高 IO 线程的运行效率,避免因为频繁 I/O 阻塞致使的线程挂起。 一个 I/O 线程能够并发处理 N 个客户端链接和读写操做,这从根本上解决了传统同步阻塞 I/O 一链接一线程模型,架构的性能、弹性伸缩能力和可靠性都获得了极大的提高。网络
传统的 I/O 是面向字节流或字符流的,以流式的方式顺序地从一个 Stream 中读取一个或多个字节, 所以也就不能随意改变读取指针的位置。数据结构
在 NIO 中,抛弃了传统的 I/O 流,而是引入了 Channel 和 Buffer 的概念。在 NIO 中,只能从 Channel 中读取数据到 Buffer 中或将数据从 Buffer 中写入到 Channel。
基于 Buffer 操做不像传统 IO 的顺序操做,NIO 中能够随意地读取任意位置的数据。
设计一个事件处理模型的程序有两种思路。
轮询方式:线程不断轮询访问相关事件发生源有没有发生事件,有发生事件就调用事件处理逻辑;
事件驱动方式:发生事件,主线程把事件放入事件队列,在另外线程不断循环消费事件列表中的事件,调用事件对应的处理逻辑处理事件。事件驱动方式也被称为消息通知方式,实际上是设计模式中观察者模式的思路。
主要包括 4 个基本组件:
事件队列(event queue):接收事件的入口,存储待处理事件;
分发器(event mediator):将不一样的事件分发到不一样的业务逻辑单元;
事件通道(event channel):分发器与处理器之间的联系渠道;
事件处理器(event processor):实现业务逻辑,处理完成后会发出事件,触发下一步操做。
能够看出,相对传统轮询模式,事件驱动有以下优势:
可扩展性好:分布式的异步架构,事件处理器之间高度解耦,能够方便扩展事件处理逻辑;
高性能:基于队列暂存事件,能方便并行异步处理事件。
Reactor 是反应堆的意思,Reactor 模型是指经过一个或多个输入同时传递给服务处理器的服务请求的事件驱动处理模式。
服务端程序处理传入多路请求,并将它们同步分派给请求对应的处理线程,Reactor 模式也叫 Dispatcher 模式,即 I/O 多了复用统一监听事件,收到事件后分发(Dispatch 给某进程),是编写高性能网络服务器的必备技术之一。
Reactor 模型中有 2 个关键组成:
Reactor:Reactor 在一个单独的线程中运行,负责监听和分发事件,分发给适当的处理程序来对 IO 事件作出反应。它就像公司的电话接线员,它接听来自客户的电话并将线路转移到适当的联系人;
Handlers:处理程序执行 I/O 事件要完成的实际事件,相似于客户想要与之交谈的公司中的实际官员。Reactor 经过调度适当的处理程序来响应 I/O 事件,处理程序执行非阻塞操做。
取决于 Reactor 的数量和 Hanndler 线程数量的不一样,Reactor 模型有 3 个变种:
单 Reactor 单线程;
单 Reactor 多线程;
主从 Reactor 多线程。
能够这样理解,Reactor 就是一个执行
while (true) { selector.select(); …}
复制代码
循环的线程,会源源不断的产生新的事件,称做反应堆很贴切。
Netty 主要基于主从 Reactors 多线程模型(以下图)作了必定的修改,其中主从 Reactor 多线程模型有多个 Reactor:
MainReactor 负责客户端的链接请求,并将请求转交给 SubReactor;
SubReactor 负责相应通道的 IO 读写请求;
非 IO 请求(具体逻辑处理)的任务则会直接写入队列,等待 worker threads 进行处理。
异步的概念和同步相对。当一个异步过程调用发出后,调用者不能马上获得结果。实际处理这个调用的部件在完成后,经过状态、通知和回调来通知调用者。
Netty 中的 I/O 操做是异步的,包括 Bind、Write、Connect 等操做会简单的返回一个 ChannelFuture。
调用者并不能马上得到结果,而是经过 Future-Listener 机制,用户能够方便的主动获取或者经过通知机制得到 IO 操做结果。
当 Future 对象刚刚建立时,处于非完成状态,调用者能够经过返回的 ChannelFuture 来获取操做执行的状态,注册监听函数来执行完成后的操做。
常见有以下操做:
经过 isDone 方法来判断当前操做是否完成;
经过 isSuccess 方法来判断已完成的当前操做是否成功;
经过 getCause 方法来获取已完成的当前操做失败的缘由;
经过 isCancelled 方法来判断已完成的当前操做是否被取消;
经过 addListener 方法来注册监听器,当操做已完成(isDone 方法返回完成),将会通知指定的监听器;若是 Future 对象已完成,则理解通知指定的监听器。
相比传统阻塞 I/O,执行 I/O 操做后线程会被阻塞住, 直到操做完成;异步处理的好处是不会形成线程阻塞,线程在 I/O 操做期间能够执行别的程序,在高并发情形下会更稳定和更高的吞吐量。
Server 端包含 1 个 Boss NioEventLoopGroup 和 1 个 Worker NioEventLoopGroup。
NioEventLoopGroup 至关于 1 个事件循环组,这个组里包含多个事件循环 NioEventLoop,每一个 NioEventLoop 包含 1 个 Selector 和 1 个事件循环线程。
每一个 Boss NioEventLoop 循环执行的任务包含 3 步:
每一个 Worker NioEventLoop 循环执行的任务包含 3 步:
EventLoop 是 SwfitNIO 最基本的 IO 原语,它等待事件的发生,在发生事件时触发某种回调操做。在大部分 SwfitNIO 应用程序中,EventLoop 对象的数量并很少,一般每一个 CPU 核数对应一到两个 EventLoop 对象。通常来讲,EventLoop 会在应用程序的整个生命周期中存在,进行无限的事件分发。
EventLoop 能够组合成 EventLoopGroup,EventLoopGroup 提供了一种机制用于在各个 EventLoop 间分发工做负载。例如,服务器在监听外部链接时,用于监听链接的 socket 会被注册到一个 EventLoop 上。但咱们不但愿这个 EventLoop 承担全部的链接负载,那么就能够经过 EventLoopGroup 在多个 EventLoop 间分摊链接负载。
目前,SwiftNIO 提供了一个 EventLoopGroup 实现(MultiThreadedEventLoopGroup)和两个 EventLoop 实现(SelectableEventLoop 和 EmbeddedEventLoop)。
尽管 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(用于测试)。
很重要的一点是:ChannelPipeline 是线程安全的,这就意味不用单独作同步处理。全部 ChannelPipeline 中的 Handlers 都是放到同一个线程经过 EventLoop 处理的,同时也说明,全部的 Handlers 都不能是阻塞的或者说必须是 none blocking 的。若是阻塞,pipeline 中的其余 Handlers 就会一直等待当前 Handler 处理结束。所以,最好将可能会有阻塞,或者可能并发量高的处理放到其余子线程去处理。
SwiftNIO 提供了一些 Bootstrap 对象,用于简化 Channel 的建立。有些 Bootstrap 对象还提供了其余的一些功能,好比支持 Happy Eyeballs。
目前 SwiftNIO 提供了三种 Bootstrap:ServerBootstrap(用于监听 Channel),ClientBootstrap(用于 TCP Channel)和 DatagramBootstrap(用于 UDP Channel)。
SwiftNIO 提供了 ByteBuffer,一种快速的 Copy-On-Write 字节缓冲器,是大部分 SwiftNIO 应用程序的关键构建块。
ByteBuffer 提供了不少有用的特性以及一些“钩子”,经过这些钩子,咱们能够在“unsafe”的模式下使用 ByteBuffer。这种方式能够得到更好的性能,代价是应用程序有可能出现内存问题。在通常状况下,仍是建议在安全模式下使用 ByteBuffer。
并发代码和同步代码之间最主要的区别在于并不是全部的动做都可以当即完成。例如,在向一个 Channel 写入数据时,EventLoop 有可能不会当即将数据冲刷到网络上。为此,SwiftNIO 提供了 EventLoopPromise 和 EventLoopFuture,用于管理异步操做。
EventLoopFuture 其实是一个容器,用于存放函数在将来某个时刻的返回值。每一个 EventLoopFuture 对象都有一个对应的 EventLoopPromise,用于存放实际的结果。只要 EventLoopPromise 执行成功,EventLoopFuture 也就完成了。
经过轮询的方式检查 EventLoopFuture 是否完成是一种很是低效的方式,因此 EventLoopFuture 被设计成能够接收回调函数。也就是说,在有结果的时候回调函数会被执行。
EventLoopFuture 负责处理调度工做,确保回调函数是在最初建立 EventLoopPromise 的那个 EventLoop 上执行,因此就没有必要再针对回调函数作任何同步操做。