Prorsum

一个类似 Go 的并发系统 + 适用于 Swift 的网络/http 库,可在 Linux 和 Mac 上运行。

为什么选择 Prorsum?

我启动这个项目的原因是,我发现在之前创建的 Slimane 项目中使用 Swift 处理异步 I/O 非常困难。在 Swift 的异步范式中,我们经常需要为闭包妥善使用捕获列表,有时还需要保留对象(Connection 等)以避免被 ARC 释放。然后我认为 Go 的并发/并行和同步机制是 Swift 当前阶段的合适模型(如果你想在多核机器上编写服务器)。因为我们可以轻松地进行异步操作而无需回调链,可以使用简单的语法利用所有核心,并且可以通过 Channel 在线程之间轻松共享内存。

(Prorsum 不是 Goroutine。它没有协程,上下文切换在操作系统层面完成。它只是具有线程安全的共享内存机制(基于 GCD),其灵感来自 Go。)

VS C10K 问题

Prorsum 的 HTTP 服务器架构是事件驱动主进程 + 多线程请求处理程序。在 DispatchQueue 中,你可以使用 go() + Channel<Element> 以同步语法编写异步 I/O。
轻松编写代码解决 C10K 问题,无需回调。

                                                 +-----------------+
                                             |-- | Request Handler |
                                             |   +-----------------+
               +--------+                    |   +-----------------+
----- TCP ---- | master |---Dispatch Queue---|-- | Request Handler |
               +--------+                    |   +-----------------+               
                                             |   +-----------------+
                                             |-- | Request Handler |
                                                 +-----------------+

特性

类似 Go 的工具

网络/HTTP

安装

目前 Prorsum 仅支持 SPM。

SPM

import PackageDescription

let package = Package(
    name: "MyApp",
    dependencies: [
        .Package(url: "https://github.com/noppoMan/Prorsum.git", majorVersion: 0, minor: 1)
    ]
)

Cocoapods

尚不支持

Carthage

尚不支持

用法

go

go 是 DispatchQueue().async { } 的别名

func asyncTask(){
    print(Thread.current)
}

go(asyncTask())

go {
    print(Thread.current)
}

gomain {
    print(Thread.current) // back to the main thread
}

WaitGroup

WaitGroup 等待一组 GCD 操作完成。主 GCD 操作调用 Add 来设置要等待的 GCD 操作的数量。然后每个 GCD 操作运行并在完成后调用 Done。同时,可以使用 Wait 阻塞,直到所有 GCD 操作完成。

let wg = WaitGroup()

wg.add(1)
go {
    sleep(1)
    print("wg: 1")
    wg.done()
}

wg.add(1)
go {
    sleep(1)
    print("wg: 2")
    wg.done()
}

wg.wait() // block unitle twice wg.done() is called.

print("wg done")

Channel<Element>

Channels 是连接并发操作的管道。你可以从一个 GCD 操作向通道发送值,并在另一个 GCD 操作中接收这些值。

let ch = Channel<String>.make(capacity: 1)

func asyncSend(){
    try! ch.send("Expecto patronum!")
}

go(asyncSend()) // => Expecto patronum!

go {
    try! ch.send("Accio!")
}

try! ch.receive() // => Accio!

ch.close()

select

select 语句允许 BlockOperation 等待多个通信操作。

let magicCh = Channel<String>.make(capacity: 1)

go {
  try! magicCh.send("Obliviate")
}

select {
    when(magicCh) {
        print($0)
    }

    otherwise {
        print("otherwise")
    }
}

forSelect

通常,你需要将 select 包装在 while 循环中。为了更轻松地使用这种模式,你可以使用 forSelect。forSelect 将循环直到调用 done()

let magicCh = Channel<String>.make(capacity: 1)
let doneCh = Channel<String>.make(capacity: 1)

go {
    try! magicCh.send("Crucio")
    try! magicCh.send("Imperio")
}

go {
    try! doneCh.send("Avada Kedavra!")
}

forSelect { done in
    when(magicCh) {
        print($0)
    }

    when(doneCh) {
        done() // break current loop
    }

    otherwise {
        print("otherwise")
    }
}

网络

HTTP 服务器

import Prorsum
import Foundation

let server = try! HTTPServer { (request, writer) in
    do {
        let response = Response(
            headers: ["Server": "Prorsum Micro HTTP Server"],
            body: .buffer("hello".data)
        )

        try writer.serialize(response)

        writer.close()
    } catch {
        fatalError("\(error)")
    }
}

try! server.bind(host: "0.0.0.0", port: 3000)
print("Server listening at 0.0.0.0:3000")
try! server.listen()

RunLoop.main.run() //start run loop

HTTP/HTTPS 客户端

import Prorsum

let url = URL(string: "https://google.com")
let client = try! HTTPClient(url: url!)
try! client.open()
let response = try! client.request()

print(response)
// HTTP/1.1 200 OK
// Set-Cookie: NID=91=CPfJo7FsoC_HXmq7kLrs-e0DhR0lAaHcYc8GFxhazE5OXdc3uPvs22oz_UP3Bcd2mZDczDgtW80OrjC6JigVCGIhyhXSD7e1RA7rkinF3zxUNsDnAtagvs5pbZSjXuZE; expires=Sun, 04-Jun-2017 16:21:39 GMT; path=/; domain=.google.co.jp; HttpOnly
// Transfer-Encoding: chunked
// Accept-Ranges: none
// Date: Sat, 03 Dec 2016 16:21:39 GMT
// Content-Type: text/html; charset=Shift_JIS
// Expires: -1
// Alt-Svc: quic=":443"; ma=2592000; v="36,35,34"
// Cache-Control: private, max-age=0
// Server: gws
// X-XSS-Protection: 1; mode=block
// Vary: Accept-Encoding
// X-Frame-Options: SAMEORIGIN
// P3P: CP="This is not a P3P policy! See https://www.google.com/support/accounts/answer/151657?hl=en for more info."

TCP

#if os(Linux)
    import Glibc
#else
    import Darwin.C
#endif

import Prorsum
import Foundation

let server = try! TCPServer { clientStream in
    while !clientStream.isClosed {
        let bytes = try! clientStream.read()
        try! clientStream.write(bytes)
        clientStream.close()
    }
}

// setup client
go {
    sleep(1)
    let client = try! TCPSocket()
    try! client.connect(host: "0.0.0.0", port: 3000)
    while !client.isClosed {
        try! client.write(Array("hello".utf8))
        let bytes = try! client.recv()
        if !bytes.isEmpty {
            print(String(bytes: bytes, encoding: .utf8))
        }
    }
    server.terminate() // terminate server
}

try! server.bind(host: "0.0.0.0", port: 3000)
try! server.listen() //start run loop

RunLoop.main.run() //start run loop

Websocket

这是一个 Websocket Echo 服务器示例。

#if os(Linux)
    import Glibc
#else
    import Darwin.C
#endif

import Foundation
import Prorsum

let server = try! HTTPServer { (request, writer) in
    do {
        let response: Response
        if request.isWebSocket {
            response = try request.upgradeToWebSocket { request, websocket in
                websocket.onText {
                    print("received: \($0)")
                    try! websocket.send($0)
                }
            }
        } else {
            response = Response(
                headers: ["Server": "Prorsum Micro HTTP Server"],
                body: .buffer("hello".data)
            )
        }

        try writer.serialize(response)

        try response.upgradeConnection?(request, writer.stream)

        writer.close()
    } catch {
        fatalError("\(error)")
    }
}

try! server.bind(host: "0.0.0.0", port: 8080)
print("Server listening at 0.0.0.0:8080")
try! server.listen()

RunLoop.main.run()

相关文章

许可证

Prorsum 在 MIT 许可证下发布。有关详细信息,请参阅 LICENSE。