Linux 및 Mac에서 작동하는 Swift용 동시 시스템 + 네트워킹/http 라이브러리와 같은 Go입니다.
이 프로젝트를 시작한 이유는 이전에 만들었던 Slimane이라는 프로젝트에서 Swift로 비동기 IO를 처리하는 것이 매우 어렵다고 느꼈기 때문입니다. Swift의 비동기 패러다임에서는 클로저를 위해 캡처 목록을 잘 사용해야 하는 경우가 많으며 때로는 ARC에 의해 해제되는 것을 피하기 위해 객체(Connection 등)를 유지해야 합니다. 그러다가 Go의 동시/병렬 및 동기 메커니즘이 현재 Swift 단계(MultiCore Machine에서 Server를 작성하려는 경우)에 적합한 모델이라고 생각했습니다. 콜백 체인 없이도 비동기 작업을 쉽게 할 수 있기 때문에 간단한 구문으로 전체 코어를 사용할 수 있고 스레드와 스레드 간 채널을 통해 메모리를 쉽게 공유할 수 있습니다.
(Prorsum은 고루틴이 아닙니다. 코로틴이 없고 컨텍스트 전환이 OS 측에서 수행됩니다. Go에서 크게 영감을 받은 스레드 안전 공유 메모리 메커니즘(GCD에서 작동)만 있습니다.)
Prorsum의 HTTP 서버 아키텍처는 이벤트 기반 마스터 + 다중 스레딩 요청 처리기입니다. DispatchQueue에서는 go()
+ Channel<Element>
사용하여 동기 구문으로 비동기 I/O를 작성할 수 있습니다.
콜백 없이 C10K를 해결하는 코드를 쉽게 만들 수 있습니다.
+-----------------+
|-- | Request Handler |
| +-----------------+
+--------+ | +-----------------+
----- TCP ---- | master |---Dispatch Queue---|-- | Request Handler |
+--------+ | +-----------------+
| +-----------------+
|-- | Request Handler |
+-----------------+
Currenty Prorsum은 SPM만 지원합니다.
import PackageDescription
let package = Package (
name : " MyApp " ,
dependencies : [
. Package ( url : " https://github.com/noppoMan/Prorsum.git " , majorVersion : 0 , minor : 1 )
]
)
아직 지원되지 않음
아직 지원되지 않음
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 작업은 기다릴 GCD 작업 수를 설정하기 위해 Add를 호출합니다. 그런 다음 각 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>
채널은 동시 작업을 연결하는 파이프입니다. 하나의 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
일반적으로 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 라이선스로 배포됩니다. 자세한 내용은 라이센스를 참조하세요.