一个类似 Go 的并发系统 + 适用于 Swift 的网络/http 库,可在 Linux 和 Mac 上运行。
我启动这个项目的原因是,我发现在之前创建的 Slimane 项目中使用 Swift 处理异步 I/O 非常困难。在 Swift 的异步范式中,我们经常需要为闭包妥善使用捕获列表,有时还需要保留对象(Connection 等)以避免被 ARC 释放。然后我认为 Go 的并发/并行和同步机制是 Swift 当前阶段的合适模型(如果你想在多核机器上编写服务器)。因为我们可以轻松地进行异步操作而无需回调链,可以使用简单的语法利用所有核心,并且可以通过 Channel 在线程之间轻松共享内存。
(Prorsum 不是 Goroutine。它没有协程,上下文切换在操作系统层面完成。它只是具有线程安全的共享内存机制(基于 GCD),其灵感来自 Go。)
Prorsum 的 HTTP 服务器架构是事件驱动主进程 + 多线程请求处理程序。在 DispatchQueue 中,你可以使用 go()
+ Channel<Element>
以同步语法编写异步 I/O。
轻松编写代码解决 C10K 问题,无需回调。
+-----------------+
|-- | Request Handler |
| +-----------------+
+--------+ | +-----------------+
----- TCP ---- | master |---Dispatch Queue---|-- | Request Handler |
+--------+ | +-----------------+
| +-----------------+
|-- | Request Handler |
+-----------------+
目前 Prorsum 仅支持 SPM。
import PackageDescription
let package = Package(
name: "MyApp",
dependencies: [
.Package(url: "https://github.com/noppoMan/Prorsum.git", majorVersion: 0, minor: 1)
]
)
尚不支持
尚不支持
go 是 DispatchQueue().async { }
的别名
func asyncTask(){
print(Thread.current)
}
go(asyncTask())
go {
print(Thread.current)
}
gomain {
print(Thread.current) // back to the main thread
}
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")
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 语句允许 BlockOperation
等待多个通信操作。
let magicCh = Channel<String>.make(capacity: 1)
go {
try! magicCh.send("Obliviate")
}
select {
when(magicCh) {
print($0)
}
otherwise {
print("otherwise")
}
}
通常,你需要将 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")
}
}
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
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."
#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 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。