플랫버퍼 찍먹
2022-05-18 22:23:52

개요

  • 플랫버퍼도 전에 본 프로토콜버퍼처럼 구글에서 만든 교차 플랫폼 직렬화 라이브러리
  • 구글 벤치마크로만 보면 세계관 최강자인듯.. 👀
  • 별도의 파싱없이 위치값?으로만 접근하는듯
  • 사용자가 실수하면 답이 없을듯
  • 깃헙에 다른 언어 예시 코드가 잇는데 ts는 현재 기준 업엇음.. 😤

설치

brew(m1)로 설치하였음

1
2
xcode-select --install
arch -arm64 brew install flatbuffers --HEAD

IDL 작성

다는 안써봣고 몇가지 주로 써볼만한 거만 써봄

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
namespace FlatBuffer.Users; // 네임스페이스를 지정하고 컴파일 하면 그 폴더에 생김

enum Gender:byte { male = 0, female } // 흔한 enum

struct Phone { // only scalar(숫자)만 가능하다 주의!
first: int;
second: int;
third: int;
}

table ReqUser { // 플랫버퍼에서 객체를 정의하는 주요 방법
name: string;
gender: Gender;
phone: Phone;
}

table ResUser {
id: int;
create_at: long;
name: string;
gender: Gender;
phone: Phone;
}

table ResUserList {
users: [ResUser]; // 배열로 보내고 싶을때
}

컴파일

여러 종류의 언어로 컴파일 가능한데 난 ts로 컴파일함

1
flatc --ts -o ./ ./fbs/User.fbs // flatc --사용할 언어 o는 output 지정 그 뒤 경로는 input

경로

그럼 ts파일 생김

테스트

플랫버퍼 쓰기 위해서 해당 프로젝트에 모듈 깔아야함

1
npm i flatbuffers -S

클라,서버 모두 컴파일된 ts 파일이랑 flatbuffers 모듈 임포트 해야함 아래는 생략

클라이언트

데이터를 플랫버퍼를 통해 바이너리로 작성해서 서버에 전달

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
import * as flatbuffers from "flatbuffers"; // 이런식으로 불러올거 선언햇다고 가정

let builder = new flatbuffers.Builder(); // 플랫버퍼 선언
/*
스트링의 경우 아래처럼 빌더를 통해서 만들어야 한다
주의점은 아래처럼 ReqUser 테이블 데이터를 넣기 전에 미리 생성해야 함
안 하면 순서 오류 난다
*/
let myName = builder.createString(crypto.randomUUID()); // 크립토는 무시 걍 아무 문자열 넣을려고 한거
ReqUser.startReqUser(builder); // 테이블 시작한다고 호출
ReqUser.addName(builder, myName); // 뭔가 데이터를 삽입할땐 항상 builder, 데이터
ReqUser.addGender(
builder,
Math.round(Math.random()) === 0 ? Gender.female : Gender.male
);
ReqUser.addPhone(
builder,
Phone.createPhone(builder, 8210, getRandom(3), getRandom(4)) // 갯랜덤은 걍 아무숫자 가지고 오는 함수이므로 무시
);
let end = ReqUser.endReqUser(builder); // 테이블 다 썻으면 이렇게 닫고 그 위치?를 받아야함
builder.finish(end); // 위치값 저장

let ab = builder.asUint8Array(); // 빌더에서 arrayBuffer 만드러줌
let buf = Buffer.from(ab); // 버퍼로 변환

아래는 전송 코드 예시 axios 썻음

1
2
3
4
5
6
7
const { data } = await axios({
method: "post",
url: "http://localhost:3000/save",
data: buf,
headers: { "Content-Type": "application/octet-stream" },
responseType: "arraybuffer", // 나중에 데이터 받을때도 바이너리이므로 arraybuffer로
});

서버

통신으로 바이너리를 받아서 플랫버퍼로 읽어보기

전에는 바이너리 받는 거 따로 작성했었는대 그럴 필요가 업음

바디파서에서 아래처럼 쓰면 바이너리 받을 수 있음

1
app.use(bodyParser.raw());

예로 Express에서 데이터를 받으면 아래처럼 플랫버퍼에 넣기만 하면 끝임

1
2
3
4
5
6
7
let byte = new Uint8Array(req.body);
let buf = new flatbuffers.ByteBuffer(byte);
let params = ReqUser.getRootAsReqUser(buf);

console.log(params.name());
console.log(params.gender());
console.log(params.phone().?first);

배열로 쓰는거 작성 코드 예시

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
let builder = new flatbuffers.Builder(); // 빌더 생성
let userEndList: number[] = [];
users.forEach((user) => {
let name = builder.createString(user.name); // 스트링 먼저 생성 필요
ResUser.startResUser(builder); // 유저 객체 시작
ResUser.addId(builder, user.id); // 이하 값 삽입
ResUser.addCreateAt(builder, BigInt(user.create_at)); // long의 경우 BigInt로 감싸줘야 됨 안 그러면 안됨...
ResUser.addGender(
builder,
user.gender === 0 ? Gender.male : Gender.female
);
ResUser.addName(builder, name);
ResUser.addPhone(
builder,
Phone.createPhone(
builder,
user.first_phone_number,
user.second_phone_number,
user.third_phone_number
)
);
let resUserEnd = ResUser.endResUser(builder); // 유저 객체 끝
userEndList.push(resUserEnd); // 끝난 위치 값 배열로 저장
});
/*
배열 결과를 보낼 테이블(=ResUserList) 시작 전에 createUsersVector
먼저 생성 해줘야 순서 오류 안남
*/
let userVectorEnd = ResUserList.createUsersVector(builder, userEndList);
ResUserList.startResUserList(builder); // 테이블 시작
ResUserList.addUsers(builder, userVectorEnd); // 위치 삽입
let end = ResUserList.endResUserList(builder); // 닫음

builder.finish(end);
let result = builder.asUint8Array(); // 변환

res.end(Buffer.from(result)); // 응답

참조