skynet framework 기반 개발된 서버는 몇가지 서비스로 구성된다. skynet 을 운영체제로 볼 수 있고, 서비스는 운영체제하의 프로세스로 볼 수 있다. 하지만 실제로는 한 개의 skynet 노드는 하나의 운영체제 프로세스만을 사용하며 서비스간 통신은 프로세스네에서 이루어지기 때문에 일반적인 운영체제 프로세스간 통신보다 훨씬 효율적이다. skynet framework 는 C 언어로 쓰여져 있고 그렇기 때문에 서비스 또한 C 언어로 개발한다. 하지만 framework 는 이미 snlua 라는 c언어로 개발된 서비스 모듈을 제공하여 Lua script 로 업무 로직을 구현할 수 있다. 즉 skynet 에서 다시 말해 skynet 에 임의의 snlua 서비스를 기동할 수 있고, 이것이 상속한 lua snlua 서비스라도 기동할수 있고, 이것이 로딩한 lua 스크립트가 다를 뿐이다. 이렇게 우리는 Lua 만 사용하여 개발 진행을 충분히 할 수 있다.

skynet 은 snlua 서비스에 탑재된 lua 스크립트에서 사용할 수 있는 skynet이라 불리는 lua 모듈을 제공한다. lua 확장자를 가지는 스크립트 문서를 작성하고, 문서명을 파라메터로 snlua 를 기동하면 된다, (서비스경로는 Config)

Snlua 서비스가 적재하는 Lua 스크립트 사용 skynet 은 제공 한다 한 개의 skynet 의 lua 모듈 snlua 서비스 로딩의 lua 스크립트 사용 통상, 스크립트의 첫번째항에는 아래처럼 시작한다

localskynet =require "skynet"

주: skynet 모듈은 skynet framework 외에서는 사용 불가능 하다, lua 표준 인터프리터에서 skynet 모듈의 코드를 구동하면 바로 오류가 발생한다. 이원인은 모든 skynet 서비스는 snlua 가 luaVM 으로 가져온 skynet_context 의 C 오브젝트에 의존하기 떄문이다

모든 skynet service의 제일 중요한 역할은 다른 서비스가 발송해온 메시지의 처리이다. 그리고 다른 서비스를 향하여 메시지를 발송하는 것이다 모든 skynet 메시지는 5개의 원소로 구성된다.

  1. session :대부분의 메시지는 요청-응답 패턴으로 동작한다. 즉, 한 서비스가 다른 서비스에 요청을 발송하고 요청을 받은 서비스는 처리를 완료 후 응답을 한다. Session은 요청을 발송하는 service에 의해 생성된다, 자신만의 유일한 메시지 표식이다. 상대방에 응답할 때, session을 붙여서 응답한다. 이렇게 하여 상대방은 어떤 메시지가 어떤 메시지에 대한 응답인지 구별한다. session은 unsigned int이고, 응답이 필요 없으면, 관례에 따라, 특수한 session 값인 0을 사용한다. Session 은 skynet framework가 생성 관리한다. 통상 사용자가 관심 가질 필요는 없다.

  2. source : 메시지기원. 모든 service는 모두 한 개의 32bit 정수표식을 가진다. 이 정수는 skynet 시스템에서 서비스되는 주소로 볼 수 있다. 서비스가 종료된 후에, 새로 기동된 서비스는 통상 이미 사용한 적이 있는 주소를 가지지 못한다 (한 바퀴를 돈 경우를 제외하고, 하지만 일반적으로 시간 간격이 매우 길다). 모든 받아온 메시지는 모두 source를 가지고 있다, 응답할 때 편리하게 해당 주소를 사용할 수 있다. 하지만 주소의 관리는 통상 framework가 완성한다. 사용자는 관심 가질 필요가 없다.

  3. type :메시지타입. 모든서비스는 256가지의 서로다른종류의 메시지를 받을수 있다. 각각의 종류는 서로다른 인코딩포맷을 가질수 있다.. 십수개타입은 프레임워크에 예약되어있다., 통상 유저가 새로운 메시지종류를 정의 하는것을 추천하지 않는다. 유저는 완전히 자신만의 타입을 가질수 있기 때문이고, 구체적인 메시지내용으로 구체적인 의미를 구분할수 있다. framework 는 이러한 type 을 문자열로 매핑하여 기억하기 편리하다. 제일 많이 사용하는 메시지 타입은 “lua” 로 lua 로 작성된 서비스들간의 통신에 광범위 하게 사용된다.

  4. message :메시지의 C 포인터, lua 층에서 봣을때 한 개의 lightuserdata 이다. Framwork 는 이세부내용을 숨길 수 있고, 최종 유저처리에는 디코딩된 lua 오브젝트이다. 아주 적은 경우에 lua층에서 직접 이 포인터를 조작할 필요가 있을것이다.

  5. Size: 메시지의 길이. 통산 message 와 함께 결합하여 사용한다. 그외, framework 를 만들때만 사용하는 일부 API 가 있고, 보통 서비스에서는 사용되지 않는다. 그러니 이러한 API 는 skynet.manager 모듈에 나눠 놓았다. 필요에의 해 사용시 먼저 아래와 같이 호출한다:

     require"skynet.manager" 
    

Legacy 코드와 호환을 위하여, skynet.manager 는 skynet namespace 를 공유한다. 이 모듈을 require 후 이러한 별도의 API 를 skynet 아래에 보류했다. Require “skynet.manager” 의 응답값은 skynet namespace 이다.

이런 추가 API 리스트는 아래와같다. 구체적 설명은 계속해서 이어진다.

  • skynet.launch C 서비스를 기동한다
  • skynet.kill 강제로 서비스를 제거한다
  • skynet.abort skynet 프로세스를 종료한다
  • skynet.register 자신을 하나의 이름으로 등록한다.
  • skynet.name 서비스의 이름을 짓는다
  • skynet.forward_type 자신을 메시지전달기로 구현하여 한종류의 메시지를 재전달한다.
  • skynet.filter 메시지를 필터링하여 재처리한다 (주:filter type, msg, sz, session, source 다섯개의파라메터를 먼저처리후 새로운 다섯개의 파라메터를 리턴한다)
  • skynet.monitor 현재 skynet 프로세스에 한 개의 global한 서비스 모니터를 설정한다 서비스 주소

모든서비스는 32비트 숫자 주소가 있다., 이주소의 첫 8비트는 서비스가 위치한 노드를 나타낸다.

skynet.self() 서비스자신의 주소를 GET 한다.

skynet.harbor() 서비스가소속한 노드를 얻는다.

skynet.address(address) 숫자로 된 주소를 읽을 수 있는 문자열로 변환한다. 동시에. 주소에 하나의 이름을 지어 편하게 사용할 수 있다.

skynet.register(name) 자신을 별명으로 등록한다(별명은 16자리 문자열내여야한다)

skynet.name(name, address) 주소에 이름을 짓는다. skynet.name(name, skynet.self())skynet.register(name) 기능 동일

이 이름은 일단 등록하면, skynet 시스템에서 통용된다, 약속한 이름을 관리할 방법이 스스로 필요할 것이다.

”.” 으로 시작하는 이름은 동일한 skynet 노드에서 유효하다. 확장노드의 skynet 다른노드하의 서비스에서 “.” 으로 시작하는 이름은 보이지 않는다. 서로다른 skynet 노드에서 동일한 “.” 로시작하는 이름을 정의할 수 있다.

문자로 시작하는 이름은 모든 skynet 네트워크에서 모두 유효하고, 이러한 global한 이름으로 메시지를 다른 노드로 보낼수 있다. 원칙상, 글로벌이름의 남용은 장려하지 않는다. 글로벌이름은 어느정도 관리 비용이 있다. 사용할 만한곳은 비지니스층에서 서비스의 숫자주소를 교환하거나 서비스가 스스로 다른 서비스의 주소를 기억하여 메시지를 보내는 것이다.

skynet.localname(name) 검색에사용한다. .로 시작하는 이름에 해당하는 주소를 찾는다. 비블록 API 이다. Cross 노드의 글로벌이름을 검색할 수 없다.

아래의 API 설명중, 특별한 언급이 없으면 모든 주소를 받아들이는 파라메터는 모두 이 주소의 문자열별명을 사용할수 있다.

메시지분배기와 응답

skynet.dispatch(type, function(session, source, …) … end) 특정 종류 메시지 처리함수의 등록. 대부분 프로그램은 lua 형식의 메시지 처리함수를 등록한다, 사용관례는 아래와같다.

localCMD ={}
skynet.dispatch("lua", function(session, source, cmd, ...)
  localf =assert(CMD[cmd])
  f(...)
end)

이 코드조각은 lua 형식의 메시지 분배함수의 등록이다. 통상 lua형식 메시지의 첫번째 원소는 한 개의 문자열이다. 구체적인 메시지에 해당하는 조작을 의미한다. 스크립트에 CMD라는 표를 생성하여 대응하는 조작함수정의를 표에 정의한다. 모든 lua 메시지 도착 후 CMD 표에서 처리함수를 찾아 나머지 파라메터를 가져온다. Response 메시지를 source 에 능동적으로 보내 응답하는 것을 제외하고 source 와 response 는 처리함수에 전달하지 않는다. 왜냐하면 훨씬 간단한 방법이 있기 때문이다. 프레임워크는 이 두개의 값을 기억한다. 이것은 하나의 관용법일 뿐이다. 다른방식으로 메시지를 처리하여도 된다. skynet 은 어떻게 할지 규정하지 않는다.

추천하지는 않지만, 새로운 메시지 종류를 추가했다면. 사용메서드는 skynet.register_protocol 이다. 예를 들어 문자열방식 인코딩 메시지의 메시지종류를 등록한다. 통상 C로 작성한 서비스가 문자열 메시지를 쉽게 디코딩한다. skynet 은 이미 이런 종류의 메시지종류를 skynet.PTYPE_TEXT 라 정의했다. 단 기본적으로 lua 에서 사용하도록 등록하지 않았다.

skynet.register_protocol{
  name ="text",
  id =skynet.PTYPE_TEXT,
  pack =function(m) returntostring(m) end,
  unpack =skynet.tostring,
}

새로운 종류는 메시지 인코딩,디코딩을 위한 pack 과 unpack 함수를 반드시 제공해야한다, Pack 함수 결과값은 문자열이거나 userdata 와 size 이다. Lua 스크립트에서 string 을 사용하길 추천한다. 두번째형식을 사용하기 위해선 skynet 의 framework 에 대한 충분한 이해가 필요하다 (이를 사용하는 것은 거의 성능을 고려하기 때문, 데이터의 복사를 줄일 수 있다)

Unpack 함수의 파라메터는 lightuserdata 와 정수이다. 위에서 말한 message 와 size 이다. Lua 는 C 포인터를 직접처리할 방법이 없다, C라이브러이에서 가져온 함수를 사용하여 디코딩해야한다. skynet.tostring 이 이런 함수 중 하나이다. 이것은 C포인터와 길이를 lua 의 string 으로 번역한다.

이제 skynet.dispatch 를 사용하여 text 형식의 처리 메서드를 등록한다. 물론 직접 skynet.register_protocol 에서 직접 dispatch 함수를 넣는 것도 가능하다

각종류에 대응하는 메시지를 받을때마다 dispatch 함수는 callback 된다. 메시지는 먼저 unpack 함수를 거치고, 결과값은 dispatch 전달된다. 각메시지의 처리는 모두 한 개의 독립적인 coroutine에서 처리된다. 보기에 multi thread 방식으로 동작 하는 것으로 보인다. 하지만 기억하자. 한 개의 luaVM(동일한 Lua서비스)에서 영원히 multi-thread 병렬처리 상황은 발생할 수 없다. Lua script 는 thread 안전성을 고려할 필요가 없다. 하지만 매번 sync API 호출될 때, 스크립트는 모두 재진입이 발생할 가능성이 있다. 이것에 대해 반드시 조심해야한다. CriticalSection 모듈은 이러한 병렬처리가 가져오는 복잡성을 줄이는데 도움이 될 것 이다.

메시지응답에 skynet.ret(message, size) 를 사용할 수 있다. message, size 에 대응하는 메시지에 현재 메시지의 session 을 덛붙이고, skynet.PTYPE_RESPONSE 의 종류이며, 현재 메시지의 발송지인 source 로 메시지를 발송한다.

역사적이유로 인해(초기버전의 skynet의 기본메시지형식은 text였다, 또한 특별한 인코딩도 거치지 않았다), 이 API는 전통적인 C 포인터와 길이를 전달하기위해 설계되었다, 또한 현재 메시지의 pack 함수의 인코딩을 거치지도 않는다. 아니면 size 도 무시하고 한개의 문자열을 전달 할 수도 있다.

skynet에서 제일 많이 사용되는 메시지 형식은 lua 이기 때문에, 이러한 메시지는 skynet.pack 을 통해 인코딩된다, 그러니 관용법은 skynet.ret(skynet.pack(…)) 이다. 그런데 skynet.pack(…) 결과값은 하나의 lightuserdata 와 하나의 길이 이다. skynet.ret 의 파라메터 요구에 부합한다, 그에대응하는것은 skynet.unpack(message, size) 이다 이것은 하나의 C포인터 더하기 길이의 메시지를 한조의 lua 오브젝트로 디코딩한다

skynet.ret 은 하나의 메시지처리 coroutine 에서 오직 한번만 호출할수 있다, 여러 번 호출할 경우 예외가 발생한다. 어쩔때는 한 개의 요청을 대기시킬 필요가 있다. 장래에 시기가 만족했을 때 다시 응답한다. 그리고 응답할때 이미 다른 coroutine내에 있다. 이러한 경우에 skynet.response(skynet.pack) 을 호출하여 closure를 얻어서 이후 이 closure 를 호출하여 즉시 응답 메시지를 보낸다. 여기의 skynet.pack 파라메터는 옵션이다. 여기에 다른 인코딩 함수를 사용할 수 있다 기본은 skynet.pack 이다.

skynet.response 리턴한 closure는 응답지연에 사용될수 있다. 호출했을때, 첫 번째 파라메터는 통상 true 이고 정상적인 응답을 의미한다. 그후의 파라메터는 응답의 파라메터에 필요하다. 만약에 false 라면 요청자에게 exception 을 던진다. 그것의 응답값은 응답의 주소가 유효한지를 의미한다. 만약 응답주소가 유효한지 알고싶을 뿐이라면 첫번째 파라메터로 TEST를 넣어 검사할수 있다.

주: skynet.ret 와 skynet.response 는 모두 비블럭API 이다

외부요청에 응답(ret이나 response)하지 않는다면, session 이 0이 아닐경우, skynet 은 당신에게 문제가 있을 수 있음을 알리는 한줄의 로그를 남긴다. skynet.igonreret() 함수를 호출하여 framework 에게 이런 session 을 무시하게 할 수 있다. 이는 통상 session을 다른 데이터(즉 skynet.call 을 사용하지 않음) 전송에 사용하고 싶을 때 사용한다. 예를 들어 클라이언트의 socket fd 를 session 으로 간주하여서, 외부메시지를 직접적으로 내부서비스에 보내 처리한다.

메시지 데이터 포인터에 관하여

skynet 서비스간 pass 되는 메시지는 기반층에서 C포인터/lightuserdata + 길이를나타내는하나의 숫자로 표현된다. 한 메시지가 skynet 서비스에 진입할 시, 메시지의 종류에 따라 대응하는 처리과정으로 전달된다. (skynet.register_protocol). 이 메시지 데이터 포인터는 메시지를 발송한측 생성한것이다. 통상 skynet_malloc 이 분배한 메모리조각이다. 기본상황에서, framework 는 skynet_free 를 호출하여 이 포인터를 해제한다.

만약 framework 가 skynet_free 를 사용하는 것을 방지하고 싶다면 skynet.start 호출대신skynet.forward_type 을 사용한다. skynet.start 와 다른 것은 skynet.forward_type 은 하나의 테이블을 넘겨야 한다. 이 테이블은 framework 가skynet_free 를 호출할 필요가 없는 메시지타입들을 나타낸다. 예를 들어

skynet.forward_type( { [skynet.PTYPE_LUA] =skynet.PTYPE_USER}, start_func )

위 코드 의미는 PTYPE_LUA 형의 메시지처리 완료후, skynet_free를 호출 하지 않도록 하여 데이터 포인터를 해제하지 않도록 한다. 통상 메시지 전달에 사용된다. 여기 framework가 PTYPE_LUA의 처리과정을 기본으로 정의했기때문에 skynet.register_protocol 은 이과정을 재정의 할 수 없다. 그래서 우리는 메시지형식을 PTYPE_USER 로 변경했다. 그리고 다른종류상황에서도 skynet.forward_type 사용으로 메시지 데이터 포인터의 메모리 해제를 방지하는것이 필요하다: 만약 어떤 특별한 메시지에 대해, 복잡한 오브젝트를 넘겼을 때(skynet_malloc 으로 분배한 완전한 메모리조각이 아니다) 그렇다면 framework 에게 데이터 포인터를 무시하도록 할 수 있다. 또한 스스로 이 오브젝트의 해제함수를 사용하여 이 포인터를 해제해야 할 것이다.

메시지 직렬화

위에서 모든 메시지는 각종류별로 인코딩과 디코딩 함수를 정의 해야 하는 것을 이야기했다

메시지가 오직 동일한 프로세스내에서 유통될 때에는 C 오브젝트를 직접 포인터로 인코딩할 수 있다. 프로세스가 동일하기 때문에, C 포인터의 전달은 유효하다. 하지만, skynet 기본적으로 다수의 노드를 제공하는 모드이다. 메시지는 다른 머신에있는 다른 노드에게 전송될 수도 있다. 이런 상황에서 모든 메시지는 필수적으로 하나의 연속된 메모리 조각이다, 메시지에대해 직렬화 작업을 진행해야 한다.

skynet 은 기본으로 lua데이터구조의 직렬화 솔루션 한세트를 제공한다. 즉 위에서 제시한 skynet.pack 그리고 skynet.unpack 함수이다. skynet.pack 은 lua 객체를 malloc으로 분배된 C 포인터 + 숫자로된 길이를 더한것으로 직렬화 한다. C 포인터로 인용된 데이터 조각을 언제 해제 할 것인지에 대한 문제를 고려해야한다. 물론 skynet.pack 을 메시지처리 framework 에서 사용될때 framework 는 이런 문제를 관리 해결해준다. skynet 은 c 포인터를 다른 서비스에 발송하고, 받는쪽에서는 사용완료후 이 포인터를 해제한다. (역: 그럼언제가 문제란거야?)

만약 직렬화 모듈을 다른곳에 사용하고 싶으면, 다른API 인 skynet.packstring 을 사용할것을 추천한다. skynet.pack 과는 다르게, 이것은 lua_string 을 반환한다. 더군다나 skynet.unpack 은 C포인터를 처리하고 lua_string 도 처리할 수 있다.

이 직렬화는 string, boolean, number, lightuserdata, table 이러한 형식을 지원한다, 하지만 lua table 의 metatable 에 대한 지원은 매우 제한적이니, 가능한한 metatable 을 가진 lua 오브젝트를 직렬화하지 않도록 한다.

메시지푸쉬와 원격호출

다른서비스에서 발송되어온 요청의 처리능력이 있으려면, 반드시 다른 서비스에 메시지를 보내거나 요청해야하는 능력이 있어야한다.

skynet.send(address, typename, …) 이 api 는 typename 이라는 한종류의 메시지를 address 로 전송한다. 이것은 먼저 등록된 pack 함수로 pack 된 내용이다.

skynet.send 는 비블럭 API 이다, 메시지를 발송완료후, coroutine이 계속해서 다음으로 진행한다. 이기간에 서비스는 다시 진입하지 못한다.

skynet.call(address, typename, …) 이 API 는 다르다, 이것은 내부에서 하나의 유일한 session 을 생성한다. Address 로 요청을 발송하고 동시에 해당session 의 응답을 기다린다(해당 address응답 할 필요가 없다). 메시지가 응답해온후, 이전에 등록한 unpack 함수를 통해 디코딩 된다. 표면상 보기에, 하나의 RPC 를 발송한것같다. 동시에 블록하고 응답을 기달린다. call 은 timeout 을 지원하지 않는다. Timeout 기능이 필요하다면 여기를 보라 TimeOutCall 아니면 여기 blog.

특히 주의 해야 할 것은, skynet.call 은 그저 현재 coroutine 만 블록 될 뿐 전체 서비스는 블록 되지 않는다. 응답을 기다리는 동안, 서비스는 여전히 다른 요청을 처리할 수 있다. 주의해야 할 것은, skynet.call 전에 얻은 서비스내의 상태가 응답이후 매우 높은 확률로 변했을 수 있다.

그리고 3개의 API가 관련이 있다, 하지만 개발에 거의 필요하지 않는다.

skynet.redirect(address, source, typenmame, session, …) skynet.send 와 비슷한 기능을 가진다, 하지만 좀더 상세하다. 발송 주소(메시지 발송지를 다른 서비스로 위장할 수 있다)를 지정할 수 있고 발송session 을 지정할 수 있다. 발송메시지의 세션을 지정한다. 주: address 와 source 모두 숫자주소여야 한다. 별명 사용할 수 없다. skynet.redirect 는 pack 을 호출하지 않는다. 파라메터의 “…” 는 encoding된 문자열이거나 userdata + 길이여야 한다

skynet.genid() 유일한 session id 를 생성한다.

skynet.rawcall(address, typename, message, size) skynet.call 기능이 비슷하다(또한 블럭API). 하지만발송시 pack을통한 package과정을 거치지 않는다. 응답을 받은후에도 unpack 과정을 거치지 않는다.

서비스의 기동과 종료

모든 skynet 서비스는 필수로 1개의 기동함수가 있다. 이점은 보통 lua 스크립트와 다르다. 전통의 lua 스크립트는 전용의 메인 함수가 존재하지 않는다. 스크립트 자신이 메인 함수 이다. 하지만 skynet 서비스는, 능동적으로 skynet.start(function() … end). 통해 호출 해야 한다.

skynet.start 는 서비스의 기동함수로 함수를 등록한다. 물론 스크립트내에 마음대로 lua 코드를 작성할 수 있다. 그것들은 start 함수보다 먼저 실행된다. 하지만 외부에서 skynet 의 블록 API 호출하지 말아야한다. 왜냐하면 프레임워크가 그것들을 다시 깨울 수 없기 때문이다.

만약 skynet.start 함수등록전에 뭔가를 하고 싶다면, skynet.init(function()…end) 를 사용할 수 있다. 이것은 통상 lua 라이브러리 작성에 사용된다. 작성해야하는 서비스가 라이브러리를 호출 해야할때 skynet의 블록 API 를 호출이 필요하다면 start 전에 skynet.init 을 사용하여 그러한 작업들을 등록한다

skynet.exit() 는 현재서비스를 종료하는데 사용된다. skynet.exit 이후 코드는 모두 실행되지 않는다. 게다가 현재서비스에 블럭된 coroutine 까지 즉시 종료된다. 이것은 통상 일부RPC가 응답을 받지 못하게 된다. skynet.exit() 를 호출할때 반드시 조심하기 바란다

skynet.kill(address) 은 강제로 다른 서비스를 닫는데 사용할 수 있다. 단 강력히 이렇게 사용하도록 추천하지 않는다. 왜냐하면 오브젝트는 임의의 메시지 처리 완료후 그대로 종료되기 때문이다. 그러니 추천하는 사용법은, 메시지를 발송하고, 상대방이 뒷처리 진행 후 skynet.exit를 호출하는 것이다. 후자가 훨씬 안전하다.

skynet.newservice(name, …) 새로운 lua 서비스를 기동한다. name 은 스크립트의 이름이다 (확장자.lua 쓸필요없다). 오직 기동 되는 스크립트의 start 함수 리턴 후, 이 API 는 기동된 서비스의 주소를 반환한다, 이것은 block API 이다. 만약 기동 된 스크립트가 초기화 부분에서 예외가 발생하거나, 초기화이후 skynet.exit 를 사용하여 종료하면, skynet.newservice 도 예외를 던진다. 만약 기동 된 스크립트의 start 함수가 무한루프라면 newservicce 또한 영원히 block 될 것이다.

**주의: 기동 parameter 는 실제론 문자열 결합방식으로 전달된다. 그러니 파라메터에 복잡한 Lua 오브젝트를 전달하지 말자. 받아들여온 파라메터는 모두 문자열이다. 게다가 문자열내 공백이 있을 수 없다. (그렇지 않으면 여러 개의 파라메터로 분할되게된다). 이런류의 파라메터 전달방식은 역사적으로 내려온것이다. 잠재적인 문제가 많이 있다. 현재 추천하는 규칙으로, 서비스가 하나의 기동 메시지에 대응할때 newservice 후, 즉시 skynet.call 로 기동 요청을 발송하는것이다. **

skynet.launch(servicename, …) C모듈의 서비스를 기동하는데 사용된다. skynet 은 주로 lua 로 작성한 서비스를 사용하기 떄문에 이것의 사용은 많지 않다. 주의: 하나의 lua 스크립트가 여러 개의 lua 서비스를 기동할 수 있다, 하나의 C모듈또한 여러 개의 C 서비스를 기동 할수 있다. 서비스의 주소는 운행시 서로 다른 서비스의 유일한 표식이다. 만약 시스템에 오직 하나만 존재하는 서비스를 작성하고 싶으면 UniqueService 참고하라

만약 skynet 의 원리에 매우 익숙하다면, 단순 서비스의 성능에 대한 극단적인 요구가 있을때 TinyService 참고하라

클럭과 스레드

skynet 내부 클럭정밀도는 1/100 초이다.

skynet.now() 은 skynet 노드 프로세스내부의 시간을 return 한다. 이 return값은 진짜 시간 값이 아니고 그 자체로 별의미가 없다. 서로 다른 노드에서 동시에 획득한 값 또한 서로 같지 않다. 오로지 두번 호출했을 때 그값의 차이가 의미가 있다. 사용처는 흘러간 시간을 측정하는데 사용된다 단위는 1/100초이다 (주의: 여기의 시간조각은 skynet내부 클럭 주기의 시간보다 짧다, 예로, 비교적 시간을 많이 잡아먹는 조작과 엄청 긴 시간을 소모하는 순환, 아니면 외부의 블록 호출, os.execute(‘sleep 1’)과 같은, 중간에 skynet 의 블록 호출 API 호출이 없을 지라도, 두번의 호출의 반환 값은 여전히 다를 수 있다.

skynet.hpc() 만약 당신이 성능 분석이 필요하다면, skynet.hpc 를 사용할수 있다, 반환값은 나노세크이다 (10000000000/1초) 의 64비트의 정수이다.

skynet.starttime() 은 skynet 노드 프로세스기동의 utc 시간을 초단위로 return 한다.

skynet.time() 返回以秒为单位(精度为小数点后两位)的 UTC 时间。它时间上等价于:skynet.now()/100 + skynet.starttime() skynet.time() 은 초단위 (정밀도는 소수점 두자리이다) 의 UTC 시간을 return 한다. 시간상의 등가는: skynet.now()/100 + skynet.starttime()

역 * 100 이 맞지않나???

skynet.sleep(ti) 현재 coroutine 이 사간단위 ti 만큼 정지된다. 단위는 1/100초이다. 이것은 timer를 framework에 등록하여 구현된다. framework는 ti 시간이후 타이머에 메시지를 보내 이 coroutine 을 깨운다. 이것은 블록 API 이다. 응답값은 시간이 도달했음을 알려준다. 아니면 skynet.wakeup 에 의해 깨어 날 수도 있다. (응답값은 “BREAK”).

skynet.yield()skynet.sleep(0)과 같다. 현재 CPU 의 제어권을 양보한다. 통상 어떤 대량의 작업을 실행하고자 할때 블럭API 를 호출할 기회가 없을때 yield 를 호출하여 시스템 실행이 매끄럽게 한다.

skynet.timeout(ti, func) framework 가 ti 시간이후 func 함수를 호출한다. 이것은 블록 API 가 아니고 현재 coroutine 은 계속해서 진행된다, 또한 func 은 새로운 coroutine에서 진행된다.

skynet 의 타이머구현은 매우 고효율이다, 그러니 일반적으로 성능문제를 걱정할 필요 없다, 그럼에도 불구하고 당신의 서비스가 타이머를 대량으로 사용하고 싶다면, 하나의 매우 좋은 방법이 있다: 즉 하나의 서비스내에서 될수 있는데로 단 한 개의 skynet.timeout 을 사용하여 이를 자신의 시간이벤트모듈로 사용한다. 이리하면 framework 에서 서비스로 발송되는 메시지의 수량을 대량 절약 할 수 있다.

Timout 은 취소 인터페이스가 없다, 간단히 감싸서 취소하는 능력을 얻을 수 있다.

function cancelable_timeout(ti, func)
  local function cb()
    if func then
      func()
    end
  end
  local function cancel()
    func = nil
  endf
  skynet.timeout(ti, cb)
  return cancel
end

local cancel = cancelable_timeout(ti, dosomething)

cancel()  -- cancel dosomething
  • skynet.fork(func, …) 从功能上,它等价于 skynet.timeout(0, function() func(…) end) 但是比 timeout 高效一点。因为它并不需要向框架注册一个定时器。 skynet.fork(func, …) 기능상, skynet.timeout(0, function() func(…) end) 와 동일하다. 하지만 timeout 보다 효율이 좋다. 왜냐하면 framework 에 타이머를 설정할 필요가 없기 때문이다.

skynet.wait(token) 把当前 coroutine 挂起,之后由 skynet.wakeup 唤醒。token 必须是唯一的,默认为 coroutine.running() 。 skynet.wait(token) 현재 coroutine 을 suspend 한다. 그런후 skynet.wakeup 으로 꺠어난다. Token 은 유일한값이어야한다. 기본값은 coroutine.running() 이다

skynet.wakeup(token) skynet.sleep 이나 skynet.wait 으로 suspend 한 coroutine 을 꺠운다. 1.0버전에서 wakeup 은 순서를 보장하지 않았지만 현재버전에서는 보장한다.

로그 추적

skynet.trace() 메시지처리과정중, 만약 이 API를 호출하면, 추적로그메시지를 기동한다. 매번 호출할때마다 유일한 TAG 를 생성한다. 모든 현재실행중인 스트림과, 호출된 다른 서비스, 모두 로그에 기록된다. 구체적인 이해는 여기를 참고한다. https://blog.codingnow.com/2018/05/skynet_trace.html

출처: https://github.com/cloudwu/skynet/wiki/LuaAPI