sproto 분석 및 skynet 소스 코드 분석의 구성, 지루한

Skynet은 클라이언트와의 통신을위한 일련의 프로토콜 sproto를 제공합니다. 디자인은 간단하고 lua 사용에 도움이됩니다 . 공식 wiki https://github.com/cloudwu/skynet/wiki/Sproto를 참조하십시오 . 이 기사에서는 어셈블리 ".sproto"파일과 sproto 구성 프로세스를 소개합니다. 나중에 sproto 사용법에 대한 또 다른 기사를 쓸 것입니다.

1. .sproto 파일 프로세스 어셈블

다음과 같은 간단한 test.sproto 파일은 .sproto 파일 어셈블리 프로세스를 소개하는 예제로 사용됩니다.

-- test.sproto
.Person {
    name 0 : string
    id 1 : integer
    email 2 : string

    .PhoneNumber {
        number 0 : string
        type 1 : integer
    }

    phone 3 : *PhoneNumber
}

.AddresBook {
    person 0 : *Person
}

proto1 1001 {
    request {
        p 0 : integer
    }
    response {
        ret 0 : *Person
    }
}

sparser.parse api를 통해 .sproto 파일을 어셈블합니다. 매개 변수 텍스트는 test.sproto 파일의 내용입니다.

 -- lualib/sprotoparser.lua
 function sparser.parse(text, name)
     local r = parser(text, name or "=text")
     dump(r)
     local data = encodeall(r)
     sparser.dump(data)
     return data
 end

3-4 행에서 .sproto 파일은 lpeg 라이브러리를 통해 분석되고 lua 테이블로 변환됩니다. 결과의 일부는 프로토콜과 유형의 두 범주를 포함하여 다음과 같습니다. 프로토콜에는 모든 프로토콜이 포함되며 각 프로토콜에는 요청, 응답 및 태그의 세 가지 키가 있습니다. 유형에는 모든 유형이 포함되며 각 유형에는 하나 이상의 필드가 있습니다. 각 필드에는 이름, 태그, 유형 이름 및 기타 정보가 포함됩니다.

  "protocol" = {
           "proto1" = {
               "request"  = "proto1.request"
               "response" = "proto1.response"
               "tag"      = 1001
           }
       }
  "type" = {
           "AddresBook" = {
              1 = {
                  "array"    = true
                  "name"     = "person"
                  "tag"      = 0
                  "typename" = "Person"
              }
          }
          "Person" = {
              1 = {
                  "name"     = "name"
                  "tag"      = 0
                  "typename" = "string"
              }
 ...

5-6 행, lua 테이블이 특정 형식의 이진 데이터로 어셈블 된 후 결과는 다음과 같습니다 (행당 16 바이트).

 

  02 00 00 00 00 00 65 01 - 00 00 32 00 00 00 02 00 
  00 00 00 00 0A 00 00 00 - 41 64 64 72 65 73 42 6F 
  6F 6B 1A 00 00 00 16 00 - 00 00 05 00 00 00 01 00 
  04 00 02 00 04 00 06 00 - 00 00 70 65 72 73 6F 6E 
  6E 00 00 00 02 00 00 00 - 00 00 06 00 00 00 50 65 
  72 73 6F 6E 5A 00 00 00 - 12 00 00 00 04 00 00 00 
  06 00 01 00 02 00 04 00 - 00 00 6E 61 6D 65 10 00 
  00 00 04 00 00 00 02 00 - 01 00 04 00 02 00 00 00 
  69 64 13 00 00 00 04 00 - 00 00 06 00 01 00 06 00 
  05 00 00 00 65 6D 61 69 - 6C 15 00 00 00 05 00 00 
  00 01 00 06 00 08 00 04 - 00 05 00 00 00 70 68 6F 
  6E 65 4E 00 00 00 02 00 - 00 00 00 00 12 00 00 00 
  50 65 72 73 6F 6E 2E 50 - 68 6F 6E 65 4E 75 6D 62 
  65 72 2E 00 00 00 14 00 - 00 00 04 00 00 00 06 00 
  01 00 02 00 06 00 00 00 - 6E 75 6D 62 65 72 12 00 
  00 00 04 00 00 00 02 00 - 01 00 04 00 04 00 00 00 
  74 79 70 65 2F 00 00 00 - 02 00 00 00 00 00 0E 00 
  00 00 70 72 6F 74 6F 31 - 2E 72 65 71 75 65 73 74 
  13 00 00 00 0F 00 00 00 - 04 00 00 00 02 00 01 00 
  02 00 01 00 00 00 70 34 - 00 00 00 02 00 00 00 00 
  00 0F 00 00 00 70 72 6F - 74 6F 31 2E 72 65 73 70 
  6F 6E 73 65 17 00 00 00 - 13 00 00 00 05 00 00 00 
  01 00 04 00 02 00 04 00 - 03 00 00 00 72 65 74 18 
  00 00 00 14 00 00 00 04 - 00 00 00 D4 07 08 00 0A 
  00 06 00 00 00 70 72 6F - 74 6F 31 

이 결과를 통해 sproto 조립 프로세스를 반대로 수행합니다 (아래 결과 참조).

2-4 행에서 문자열은 "<s4"형식에 따라 압축됩니다. 즉, 문자열 길이가 4 바이트를 차지하고 리틀 엔디안 형식에 따라 헤더에 압축되고 문자열 내용이 추가됩니다.

14-22 행에서 결과의 처음 6 바이트는 "\ 2 \ 0 \ 0 \ 0 \ 0 \ 0"이고 다음은 유형 (tt)의 어셈블리 결과와 프로토콜 (tp)의 어셈블리 결과입니다. 결과 7-10 바이트는 65 01 00 00이며, 이는 유형 357 (6 * 2 ^ 4 + 5 + 1 * 2 ^ 16)의 조합 된 길이입니다. 결과는 368 바이트부터 시작하는 프로토콜의 어셈블리 결과이며 368-371 바이트는 18 00 00 00이며 이는 프로토콜의 어셈블리 결과가 24 바이트 (1 * 2 ^ 4 + 8)임을 의미합니다. 마지막 24 단어 섹션.

  -- lualib/sprotoparser.lua
  function packbytes(str)
      return string.pack("<s4",str)
  end
  
   local function encodeall(r)
       return packgroup(r.type, r.protocol)
   end
   
  local function packgroup(t,p)
      ...
      tt = packbytes(table.concat(tt))
      tp = packbytes(table.concat(tp))
      result = {
          "\2\0", -- 2fields
          "\0\0", -- type array   (id = 0, ref = 0)
          "\0\0", -- protocol array (id = 1, ref =1)
  
          tt,
          tp,
      }
      return table.concat(result)
  end

형식 어셈블리 결과에는 총 357 바이트 (11-367)가 있습니다. 모든 형식을 오름차순 사전 순서로 탐색 한 다음 어셈블리를 위해 packtype을 호출합니다.

첫 번째 유형은 "AddresBook"입니다. result11-14 바이트는 32 00 00 00입니다. 즉, "AddresBook"의 어셈블리 결과는 1 개의 Field가 있으므로 50 바이트 (3 * 2 ^ 4 + 2) (21 행)입니다. , result15-20 바이트는 02 00 00 00 00 00 (13-15 행), 16 행에 packbytes ( "AddresBook"), 길이는 10, 결과는 0A 00 00 00 41 (A) 64 (d)입니다. 64 (d) 72 (r) 65 (e) 73 (s) 42 (B) 6F (o) 6F (o) 6B (k), 결과의 21-34 바이트입니다. 35에서 38까지의 다음 4 바이트 인 1A 00 00 00은 "AddresBook", 26 (1 * 2 ^ 4 + 10)의 모든 필드를 조합 한 길이입니다.

  -- lualib/sprotoparser.lua
   local function packtype(name, t, alltypes) -- 组装每一个type
       ...
       local data
       if #fields == 0 then
           data = {
               "\1\0", -- 1 fields
               "\0\0", -- name (id = 0, ref = 0)
               packbytes(name),
          }
      else
          data = {
              "\2\0", -- 2 fields
              "\0\0", -- name (tag = 0, ref = 0)
              "\0\0", -- field[]      (tag = 1, ref = 1)
              packbytes(name),
              packbytes(table.concat(fields)),
          }
      end
  
      return packbytes(table.concat(data))
  end 

 39 번째 -42 번째 결과의 4 바이트 16 00 00 00 필드 어셈블리 프로세스는 첫 번째 필드 22 (1 * 2 ^ 4 + 6)의 어셈블리 결과 길이, 즉 43-64 바이트입니다. 결과. AddresBook의 데이터에 따르면 조립 프로세스는 다음과 같습니다.

8 호선 05 00

13 호선 00 00

23 호선, 01 00 

24 행, 04 00, f.type = 1

25 행, 02 00, f.tag = 0

28 호선 04 00

33 행, 06 00 00 00 70 (p) 65 (e) 72 (r) 73 (s) 6F (o) 6E (n), name = "person". 결과의 43-64 바이트와 정확히 일치합니다.

"AddresBook" = {
     1 = {
         "array"    = true
         "name"     = "person"
         "tag"      = 0
         "typename" = "Person"
     }
 }

 

 -- lualib/sprotoparser.lua
  local function packfield(f) -- 组装每一个field
      local strtbl = {}
      if f.array then
          if f.key then
              table.insert(strtbl, "\6\0")  -- 6 fields
          else
              table.insert(strtbl, "\5\0")  -- 5 fields
          end
     else
         table.insert(strtbl, "\4\0")    -- 4 fields
     end
     table.insert(strtbl, "\0\0")    -- name (tag = 0, ref an object)
     if f.buildin then
         table.insert(strtbl, packvalue(f.buildin))      -- buildin (tag = 1)
         if f.extra then
             table.insert(strtbl, packvalue(f.extra))        -- f.buildin can be integer 
   or string
         else
             table.insert(strtbl, "\1\0")    -- skip (tag = 2)
         end
         table.insert(strtbl, packvalue(f.tag))          -- tag (tag = 3)
     else
         table.insert(strtbl, "\1\0")    -- skip (tag = 1)
         table.insert(strtbl, packvalue(f.type))         -- type (tag = 2)
         table.insert(strtbl, packvalue(f.tag))          -- tag (tag = 3)
     end
     if f.array then
         table.insert(strtbl, packvalue(1))      -- array = true (tag = 4)
     end
     if f.key then
         table.insert(strtbl, packvalue(f.key)) -- key tag (tag = 5)
     end
     table.insert(strtbl, packbytes(f.name)) -- external object (name)
     return packbytes(table.concat(strtbl))
 end

 다음으로 다른 유형을 순서대로 어셈블하고 유형을 어셈블 한 다음 packproto를 호출하여 각 프로토를 어셈블합니다. result372-375의 4 바이트는 14 00 00 00이며, 이는 "proto1"의 조합 된 길이가 20 (1 * 2 ^ 4 + 4)임을 의미합니다.

"proto1" = {
 "request" = "proto1.request"
 "response" = "proto1.response"
 "tag" = 1001
}

조립 과정은 다음과 같습니다.

라인 10-12, 04 00 00 00 D4 07

18 행, 08 00 (alltypes [p.request] .id = 3)

24 행, 0A 00 (alltypes [p.response] .id = 4)

35 번째 줄 name = "proto1", 길이는 6, 포장 후 06 00 00 00 70 (p) 72 (r) 6F (o) 74 (t) 6F (0) 31 (1)입니다. 결과의 마지막 20 바이트에 해당합니다.

   -- lualib/sprotoparser.lua
  local function packproto(name, p, alltypes) -- 组装每一个proto
      if p.request then
          local request = alltypes[p.request]
          if request == nil then
              error(string.format("Protocol %s request type %s not found", name, p.request))
          end
          request = request.id
      end
      local tmp = {
         "\4\0", -- 4 fields
         "\0\0", -- name (id=0, ref=0)
         packvalue(p.tag),       -- tag (tag=1)
     }
     if p.request == nil and p.response == nil and p.confirm == nil then
         tmp[1] = "\2\0" -- only two fields
     else
         if p.request then
             table.insert(tmp, packvalue(alltypes[p.request].id)) -- request typename (tag=2)
         else
             table.insert(tmp, "\1\0")-- skip this field (request)

         end
         if p.response then
             table.insert(tmp, packvalue(alltypes[p.response].id)) -- request typename (tag=3)
         elseif p.confirm then
             tmp[1] = "\5\0" -- add confirm field
             table.insert(tmp, "\1\0")
             -- skip this field (response)
             table.insert(tmp, packvalue(1)) -- confirm = true
         else
             tmp[1] = "\3\0" -- only three fields
         end
     end
 
     table.insert(tmp, packbytes(name))
 
     return packbytes(table.concat(tmp))
 end

 요약 : .sproto 파일을 어셈블하는 프로세스는 다음과 같습니다.

(1) lpeg 라이브러리를 사용하여 .sproto 파일의 내용을 구문 분석하고 정보를 lua 테이블에 저장합니다.

(2) 모든 유형을 순서대로 조합하고 먼저 각 유형의 이름을 조합 한 다음 필드를 조합합니다.

(3) 모든 프로토 타입을 차례로 조립합니다.

최종적으로 어셈블 된 바이너리 블록은 N 개의 유형과 N 개의 프로토로 구성됩니다. 각 유형에는 이름과 N 개의 필드가 포함됩니다. 각 필드에는 이름, 빌드, 유형, 태그 및 배열과 같은 정보가 포함됩니다. 각 프로토에는 이름, 태그, 요청, 응답 및 기타 정보. 필드, 유형 또는 proto에 관계없이 다음 바이트의 정보를 나타 내기 위해 일부 바이트 접두사가 추가됩니다. 형식은 다음과 같습니다.

2. sproto 건설 과정

 .sproto 파일이 바이너리 블록으로 어셈블 될 때 sproto 구성은 바이너리 블록을 구문 분석하는 것입니다. 조립 과정을 이해 한 후 분석 과정은 조립 과정을 반대로하고 최종적으로 분석 결과를 c 구조에 저장하는 것입니다. lua 레이어 newproto를 통해 api create_from_bundle은 결국 세 가지 매개 변수를 사용하여 sproto를 빌드하기 위해 호출됩니다. s 생성 후의 sproto는이 구조에 저장되고, 이진 데이터 블록은 스트림에 의해 조립되고 sz 길이입니다.

19 행, struct_field api가 접두사를 계산하고 접두사가 다른 다음 데이터는 다른 의미를 갖습니다.

23 행, count_array api는 총 유형 수 계산, 총 proto 수 계산 및 각 유형의 총 필드 수와 같은 수를 계산합니다.

26-29 행, 총 유형 수 s-> type_n 저장

31-34 행, 총 프로토스 수 s-> protocol_n 저장

38-43 행, import_type api를 통해 각 유형의 데이터를 구성하고 s-> type 배열에 저장합니다.

44-49 행, import_protocol api를 통해 각 proto 데이터를 구성하고 s-> proto 배열에 저장합니다.

  // lualib/sproto/sproto.c
  struct sproto *
  sproto_create(const void * proto, size_t sz) {
      ...
      if (create_from_bundle(s, proto, sz) == NULL) {
          pool_release(&s->memory);
          return NULL;
      }
      return s;
 }
 
 static struct sproto *
 create_from_bundle(struct sproto *s, const uint8_t* stream, size_t sz) {
     ...
     int fn = struct_field(stream, sz);
     int i;
     ...
     for (i=0;i<fn;i++) {
         int value = toword(stream + i*SIZEOF_FIELD);
         int n;
         if (value != 0)
             return NULL;
         n = count_array(content);
         if (n<0)
            return NULL;
         if (i == 0) {
             typedata = content+SIZEOF_LENGTH;
             s->type_n = n;
             s->type = pool_alloc(&s->memory, n * sizeof(*s->type));
         } else {
             protocoldata = content+SIZEOF_LENGTH;
             s->protocol_n = n;
             s->proto = pool_alloc(&s->memory, n * sizeof(*s->proto));
         }
         content += todword(content) + SIZEOF_LENGTH;
     }
 
     for (i=0;i<s->type_n;i++) {
         typedata = import_type(s, &s->type[i], typedata);
         if (typedata == NULL) {
             return NULL;
         }
     }
     for (i=0;i<s->protocol_n;i++) {
         protocoldata = import_protocol(s, &s->proto[i], protocoldata);
         if (protocoldata == NULL) {
             return NULL;
         }
     }
 
     return s;
 }

sproto 데이터 구조는 다음과 같습니다.

// lualib/sproto/sproto.c
struct sproto { // 整个sproto结构
    struct pool memory;
    int type_n; // types总数
    int protocol_n; // proto总数
    struct sproto_type * type; // N个type信息
    struct protocol * proto; // N个proto信息
};

struct sproto_type { // 单个type结构
    const char * name; // 名称
    int n; // fields实际个数
    int base; //如果tag是连续的,为最小的tag,否则是-1
    int maxn; //fields实际个数+最小的tag+不连续的tag个数,比如tag依次是1,3,5,则maxn=3+1+2=6
    struct field *f; // N个field信息
};

struct field { // 单个field结构
    int tag; //唯一的tag
    int type; // 类型,可以是内置的integer,string,boolean,也可以是自定义的type,也可以是数组
    const char * name; // 名称
    struct sproto_type * st; //如果是自定义的类型,st指向这个类型
    int key;
    int extra;
};

struct protocol { //单个proto结构
    const char *name; //名称
    int tag; //唯一的tag
    int confirm;    // confirm == 1 where response nil
    struct sproto_type * p[2]; //request,response的类型
};

sproto_dump api를 통해 생성 된 sproto의 정보를 다음과 같이 출력합니다.

=== 5 types ===
AddresBook 1 0 1
    person (0) *Person
Person 4 0 4
    name (0) string
    id (1) integer
    email (2) string
    phone (3) *Person.PhoneNumber
Person.PhoneNumber 2 0 2
    number (0) string
    type (1) integer
proto1.request 1 0 1
    p (0) integer
proto1.response 1 0 1
    ret (0) *Person
=== 1 protocol ===
    proto1 (1001) request:proto1.request response:proto1.response

 빌드가 성공하면 saveproto를 호출하여 사용할 모든 lua VM로드 (loadproto)에 대해 전역 어레이 G_sproto에 sproto를 저장합니다.

이것이 sproto의 분석 및 구축 과정입니다. 다음으로 sproto 사용법에 대한 기사를 쓸 것입니다.


2021 년 1 월 13 일 14 일, 2 주 후인 4 시간짜리 스카이 넷 트레이닝 캠프를 열 예정입니다. 등록은 현재 진행 중입니다. 게임 개발에 관심이있는 분은 구독 할 수 있습니다.

훈련 캠프 내용은 대략 다음과 같습니다.

1. 멀티 코어 동시 프로그래밍
2. 메시지 큐, 스레드 풀
3. 액터 메시지 스케줄링
4. 네트워크 모듈 구현
5. 타임 휠 타이머 구현
6. Lua / c 인터페이스 프로그래밍
7. 스카이 넷 프로그래밍 필수 사항
8. 데모는 액터 프로그래밍 사고를 보여줍니다.

게임 개발을위한 기술 축제를 만들기 위해 함께 일하는 모든 사람을 기대합니다.

등록 스크린 샷을 통해 그룹 973961276에 입장하여 마지막 스카이 넷 훈련 캠프의 녹화 및 방송과이 기간 동안의 미리보기 자료를받을 수 있습니다!

추천

출처blog.csdn.net/linuxguitu/article/details/111991876