使用Swift-NIO开发基本的Swift Echo Server

我不是Java或JVM类型的开发人员。 这可能是我从未感到需要尝试Netty框架的原因之一。 我一直在用Erlang,Elixir或Go开发所有高性能服务器代码,并对工具感到满意。

但是,Apple最近发布了Swift-NIO,这是一个用于开发跨平台服务器和客户端应用程序的新库和框架。

我不是前端开发人员,而是更具可伸缩性的后端开发人员。 就是说,我喜欢Swift编程语言,并且自从Apple在2014年发布它以来就一直关注着它的进步。这就是为什么这个新框架引起了我的注意。 编写服务器端软件有多好?

由于Swift-NIO是Netty的一个端口,它是由Netty杰出贡献者Norman Maurer领导的团队开发的,因此我首先研究了Netty设计,以更好地了解Swift-NIO的工作方式。 我喜欢我所读到的东西。

Netty的概念提供了很好的通用抽象,这是好的网络应用程序所共有的。 它是一个参考框架,在Java世界中用于构建许多非常高级的服务器和客户端工具。

这些概念与Swift编程语言非常吻合。 很好的契合度让我认为这确实可以加速Swift服务器端开发和跨平台推广。

我敢打赌它可能会产生很大的影响,并帮助Swift继续迅速崛起为顶级编程语言之一。

Swift-NIO依赖于非阻塞IO。 这意味着您可以通过使中间层分派准备好处理到工作线程的网络操作的方式,来管理相对数量的线程,从而管理大量的网络连接。

因此,从处理线程的角度来看,网络操作是无阻塞的。 他们可以完全使用CPU,因为它们可以共享大量套接字的网络操作,而无需等待。

在Swift-NIO措辞中,阻塞操作被委派给通道。 通道将网络操作上的事件触发到负责管理通道的事件循环。 开发人员将服务器或客户端的逻辑作为处理程序提供给事件循环。 处理程序是实现触发网络事件时执行的操作的代码段。

可以将它们组合在处理程序管道中以提供额外的灵活性。 这为去耦增加了一层,并使处理程序更可重用。

在客户端服务器环境中,“ Hello World”应用程序通常是“ Echo”服务器。 服务器接收客户端发送的内容,并将其发送回客户端。

使用Swift-NIO非常容易实现。 让我们看看如何做到。

请注意,以下步骤已在MacOS上进行了测试,但是如果您已安装Swift,则它们也应该在Linux上也可以使用。

您可以使用Swift命令行引导项目:

  $ mkdir EchoServer 
$ cd EchoServer /
$ swift package init —键入可执行文件
创建可执行程序包:EchoServer
创建Package.swift
创建README.md
创建.gitignore
创建源/
创建Sources / EchoServer / main.swift
创建测试/

要更改项目配置,您将需要编辑Package.swift文件。

您可以在常规包依赖项列表中添加Swift-NIO存储库:

 依赖项:[ 
.package(URL:“ https://github.com/apple/swift-nio.git”,来自:“ 1.1.0”),
],

并在您的EchoServer目标依赖项中添加NIO:

  。目标( 
名称:“ EchoServer”,
依赖项:[“ NIO”]),

完整的Package.swift文件如下:

  // swift-tools-version:4.0 
// swift-tools-version声明构建此软件包所需的最低Swift版本。import PackageDescriptionlet package = Package(
名称:“ EchoServer”,
依赖项:[
//依赖关系声明此程序包依赖的其他程序包。
// .package(url:/ * package url * /,来自:“ 1.0.0”),
.package(URL:“ https://github.com/apple/swift-nio.git”,来自:“ 1.1.0”),
],
目标:[
//目标是包的基本构建块。 目标可以定义模块或测试套件。
//目标可以依赖于此包装中的其他目标,也可以依赖于此包装所依赖的包装中的产品。
。目标(
名称:“ EchoServer”,
依赖项:[“ NIO”]),
]

最后,您可以使用命令swift package resolve下载依赖项:

  $ swift包解析 
正在获取https://github.com/apple/swift-nio.git
正在获取https://github.com/apple/swift-nio-zlib-support.git
克隆https://github.com/apple/swift-nio.git
在1.1.0版本解析https://github.com/apple/swift-nio.git
克隆https://github.com/apple/swift-nio-zlib-support.git
在1.0.0解析https://github.com/apple/swift-nio-zlib-support.git

服务器代码在Sources/EchoServer/main.swift

ChannelInboundHandler

EchoServer代码非常简单。 第一部分(如果要创建ChannelInboundHandler实现)将实现服务器逻辑。

在我们的例子中,我们将仅实现五个回调方法:

  • channelRegistered:在客户端连接上调用。
  • channelUnregistered:在客户端断开连接时调用。
  • channelRead:当从客户端接收到数据时调用。
  • channelReadComplete:当channelRead处理当前读取操作中的所有读取事件时调用。 我们使用它来确保将数据刷新到套接字。
  • errorCaught:发生错误时调用。

另一个需要注意的重要事项是,每个回调方法都会接收一个上下文,该上下文可用于获取会话信息。 例如,它包含有关客户端IP地址的信息。 上下文还提供了例如将数据写回到给定客户端的方法。

 私有最终类EchoHandler:ChannelInboundHandler { 
公共类型别名InboundIn = ByteBuffer
public typealias OutboundOut = ByteBuffer //跟踪单个会话中收到的消息数
//(每个客户端连接都会创建一个EchoHandler)。
私有变量计数:Int = 0 //在客户端连接上调用
public func channelRegistered(ctx:ChannelHandlerContext){
print(“已注册频道:”,ctx.remoteAddress ??“未知”)
} //在客户端断开连接时调用
公共函数channelUnregistered(ctx:ChannelHandlerContext){
打印(“我们已经处理了\(count)条消息”)
} //当从客户端收到数据时调用
公共函数channelRead(ctx:ChannelHandlerContext,数据:NIOAny){
ctx.write(数据,承诺:无)
计数=计数+ 1
} //当channelRead as处理当前读取操作中的所有读取事件时调用
公共函数channelReadComplete(ctx:ChannelHandlerContext){
ctx.flush()
} //发生错误时调用
公共函数errorCaught(ctx:ChannelHandlerContext,错误:错误){
打印(“错误:”,错误)
ctx.close(承诺:无)
}
}

设置服务器

其余代码与设置服务器有关。 Swift-NIO提供了一些“ Bootstrap”助手,以使在常见情况下的过程变得更加简单。

在该示例中,我们使用ServerBootstrap。

  //创建一个多线程事件循环以使用所有系统核心进行处理 
let group = MultiThreadedEventLoopGroup(numThreads:System.coreCount)//使用引导程序设置服务器
让bootstrap = ServerBootstrap(group:group)
//定义积压并启用服务器级别的SO_REUSEADDR选项
.serverChannelOption(ChannelOptions.backlog,值:256)
.serverChannelOption(ChannelOptions.socket(SocketOptionLevel(SOL_SOCKET),SO_REUSEADDR),值:1)//处理程序管道:处理来自接受通道的事件的处理程序
//为了证明我们可以有多个可重用的处理程序,我们从Swift-NIO默认值开始
//处理程序,如果不能跟上
//通过EchoHandler处理。
//这是为了防止服务器过载。
.childChannelInitializer {
channel.pipeline.add(handler:BackPressureHandler())。然后{v in
channel.pipeline.add(处理程序:EchoHandler())
}
} //在通道级别(TCP_NODELAY和SO_REUSEADDR)启用通用套接字选项。
//这些选项适用于接受的频道
.childChannelOption(ChannelOptions.socket(IPPROTO_TCP,TCP_NODELAY),值:1)
.childChannelOption(ChannelOptions.socket(SocketOptionLevel(SOL_SOCKET),SO_REUSEADDR),值:1)
//邮件分组
.childChannelOption(ChannelOptions.maxMessagesPerRead,值:16)
//让Swift-NIO根据实际流量调整缓冲区大小。
.childChannelOption(ChannelOptions.recvAllocator,值:AdaptiveRecvByteBufferAllocator())
推迟{
尝试! group.syncShutdownGracefully()
} //绑定端口并运行服务器
let channel = try bootstrap.bind(host:“ :: 1”,port:9999).wait()print(“服务器启动并在\(channel.localAddress!)上侦听”)//清理(从未调用,因为我们没有代码来决定何时停止
// 服务器)。 我们假设我们将使用Ctrl-C将其杀死。
尝试channel.closeFuture.wait()print(“服务器已终止”)

您可以在Github上查看服务器的完整代码:Sources / EchoServer / main.swift。

您可以使用以下命令运行服务器:

  $ swift包解析 
正在获取https://github.com/apple/swift-nio.git
正在获取https://github.com/apple/swift-nio-zlib-support.git
克隆https://github.com/apple/swift-nio.git
在1.1.0版本解析https://github.com/apple/swift-nio.git
克隆https://github.com/apple/swift-nio-zlib-support.git
在1.0.0解析https://github.com/apple/swift-nio-zlib-support.git
MBP-de-Mickael:EchoServer mremond $ vim来源/EchoServer/main.swift
MBP-de-Mickael:EchoServer mremond $快速运行
编译CNIODarwin shim.c
编译CNIOLinux shim.c
编译CNIOAtomics src / c-atomics.c
编译Swift模块'NIOPriorityQueue'(2个源代码)
编译Swift模块'NIOConcurrencyHelpers'(2个来源)
编译Swift Module'NIO'(47源码)
编译Swift模块'EchoServer'(1个源代码)
链接./.build/x86_64-apple-macosx10.10/debug/EchoServer
服务器启动并在[IPv6] :: 1:9999上侦听

Swift将编译服务器并运行它。

您还可以使用swift build来构建服务器,并从.build目录运行可执行文件:

 $ swift build 
$ .build/debug/EchoServer

您可以使用telnet连接到回显服务器。

  $ telnet本地主机9999 
尝试:: 1 ...
连接到本地主机。
转义字符为'^]'。
你好
你好
再见
再见
^]
telnet>关闭
连接已关闭。

如您所见,所有命令都重复执行,并从EchoServer发送回去。

服务器将在每个连接上打印一条消息,并在每个断开连接的会话期间处理总消息:

 已注册的频道:[IPv6] :: 1:62759 
我们已经处理了2条消息

注意:在最新的MacOS版本上,默认情况下未安装telnet ,但您可以使用自制软件轻松安装它。

Swift-NIO是用于开发服务器和客户端应用程序的非常有趣的框架。 我认为这将有助于在服务器端推广Swift的使用。 Vapor是一个著名的服务器端框架,已经更改了自己的代码以将Swift-NIO用作构建模块,但这仅仅是开始。

Netty是Java世界中许多服务器端组件(Akka,PlayFramework,Gatling,Finagle,Cassandra,Spark,Vert.x等)的核心。 Swift-NIO在Swift中将扮演类似的角色,使其成为可用于后端开发的多种语言中的一个有趣的竞争者。

请给我发送评论和反馈,让我知道您是否对Swift-NIO,服务器端Swift(或一般而言的Swift)的更多内容感兴趣。


最初于 2018 年3月12日 发布在 blog.process-one.net 上。