Zme*_*mey 5 network-programming libevent grand-central-dispatch swift swift3
我正在Swift 3中创建一个服务器端应用程序.我选择了libevent来实现网络代码,因为它是跨平台的并且不会遇到C10k问题.Libevent实现了它自己的事件循环,但我想保持CFRunLoop和GCD(DispatchQueue.main.after
等)功能,所以我需要以某种方式粘贴它们.
这就是我想出的:
var terminated = false
DispatchQueue.main.after(when: DispatchTime.now() + 3) {
print("Dispatch works!")
terminated = true
}
while !terminated {
switch event_base_loop(eventBase, EVLOOP_NONBLOCK) { // libevent
case 1:
break // No events were processed
case 0:
print("DEBUG: Libevent processed one or more events")
default: // -1
print("Unhandled error in network backend")
exit(1)
}
RunLoop.current().run(mode: RunLoopMode.defaultRunLoopMode,
before: Date(timeIntervalSinceNow: 0.01))
}
Run Code Online (Sandbox Code Playgroud)
这有效,但引入了0.01秒的延迟.当RunLoop正在休眠时,libevent将无法处理事件.当应用程序空闲时,降低此超时会显着增加CPU使用率.
我也在考虑只使用libevent,但是项目中的第三方库可以在内部使用dispatch_async,因此这可能会有问题.
在另一个线程中运行libevent的循环会使同步变得更加复杂,这是解决此延迟问题的唯一方法吗?
LINUX更新.上面的代码在Linux上不起作用(2016-07-25-a Swift snapshot),RunLoop.current().run
存在错误.下面是一个工作的Linux版本,重新实现了计时器和dispatch_main
.它遇到了相同的延迟问题:
let queue = dispatch_get_main_queue()
let timer = dispatch_source_create(DISPATCH_SOURCE_TYPE_TIMER, 0, 0, queue)
let interval = 0.01
let block: () -> () = {
guard !terminated else {
print("Quitting")
exit(0)
}
switch server.loop() {
case 1: break // Just idling
case 0: break //print("Libevent: processed event(s)")
default: // -1
print("Unhandled error in network backend")
exit(1)
}
}
block()
let fireTime = dispatch_time(DISPATCH_TIME_NOW, Int64(interval * Double(NSEC_PER_SEC)))
dispatch_source_set_timer(timer, fireTime, UInt64(interval * Double(NSEC_PER_SEC)), UInt64(NSEC_PER_SEC) / 10)
dispatch_source_set_event_handler(timer, block)
dispatch_resume(timer)
dispatch_main()
Run Code Online (Sandbox Code Playgroud)
在 GitHub 上快速搜索开源 Swift 基金会库就会发现,CFRunLoop
不同平台上的支持(也许很明显)实现方式不同。从本质上讲,这意味着RunLoop
和libevent
就跨平台而言,只是实现同一目标的不同方式。我可以看到这种想法背后的想法,这libevent
可能更适合服务器实现,因为它CFRunLoop
并不是带着这个特定目标成长的,但就跨平台而言,它们都在同一棵树上吠叫。
也就是说,RunLoop
和使用的底层同步原语本质libevent
上是私有的实现细节,也许更重要的是,平台之间是不同的。从源代码来看,它看起来像在 Linux 上RunLoop
使用,但在 macOS/iOS/etc 上,将使用 Mach 端口作为其基本原语,但看起来它将使用. 经过足够的努力,您可能能够制作一个与给定平台的源相关的混合体,但这可能非常脆弱,并且通常不明智,原因如下:首先,它将基于其私有实现细节不属于公共 API 的一部分,因此可能随时更改,恕不另行通知。其次,假设您没有对 Swift 和 都支持的每个平台都执行此操作,那么您就会破坏它的跨平台性,这也是您首先声明的原因之一。epoll
libevent
RunLoop
libevent
kqueue
RunLoopSource
libevent
RunLoop
libevent
libevent
您可能没有考虑过的另一种选择是单独使用 GCD,而不使用RunLoops
. 查看 的文档dispatch_main
。在服务器应用程序中,“主线程”(通常)没有什么特别的,因此分派到“主队列”应该足够好(如果需要的话)。您可以使用调度“源”来管理您的连接等。我个人无法谈论调度源如何扩展到 C10K/C100K/等。级别,但根据我的经验,它们看起来相当轻量级且开销低。我还怀疑像这样使用 GCD 可能是用Swift 编写服务器应用程序的最惯用的方法。我写了一个基于 GCD 的 TCP 回显服务器的小示例,作为此处另一个答案的一部分。
如果您必须并决定在同一个应用程序中使用两者RunLoop
,libevent
那么正如您所猜测的那样,最好为其提供libevent
自己的单独线程,但我认为它并不像您想象的那么复杂。您应该能够自由地dispatch_async
进行libevent
回调,并且类似地相当轻松地从 GCD 托管线程编组回复以libevent
使用 的libevent
多线程机制(即通过锁定运行,或将您的调用编组为libevent
事件本身。)类似地,第三即使您选择使用 libevent 的循环结构,使用 GCD 的聚会库也不应该成为问题。GCD 管理自己的线程池,并且无法执行 的libevent
主循环等。
您还可以考虑构建应用程序,以便使用什么并发和连接处理库并不重要。然后,您可以根据给定情况或部署最有效的方式更换libevent
、GCD、CFStreams 等(或混合搭配)。选择并发方法很重要,但理想情况下,您不会将自己与它结合得太紧密,以至于在情况需要时无法切换。
当您拥有这样的架构时,我通常喜欢使用最高级别的抽象来完成工作,并且仅在特定情况需要时才降低到较低级别的抽象。在这种情况下,这可能意味着使用CFStreams
和RunLoops
来启动,并切换到“裸”GCD 或libevent
更高版本,如果您碰壁并且还确定(通过经验测量)它是传输层而不是应用程序层。限制因素。很少有重要的应用程序真正解决传输层中的 C10K 问题;事情往往必须首先在应用程序层“扩展”,至少对于比基本消息传递更复杂的应用程序来说是这样。