- 프로토콜 버퍼 정의 파일을 작성한다.
//person.proto파일
syntax = "proto3";
//메시지 타입 정의
message Person {
string name = 1;
int32 id = 2;
string email = 3;
}
//기본형식
(키워드) (이름) {
타입 변수명 = 값
}
- 프로토콜 파일을 로드
import protobuf from 'protobufjs'
protobuf.load("person.proto").then(root=>{
//'person.proto' 파일을 로드한다.
const Person = root.lookupType("Person")
//'Person' 메시지 타입을 'root' 객체에서 찾는다. 이는 person.proto파일에서 정의한 메시지 타입임
const message = Person.create({name:"Hong Doe", id:123, <email:"hong1212@example.com>"});
/* 'Person'메시지 타입을 사용하여 새로운 메시지 객체를 생성하는 부분 ,
여기서 name,id,email 필드를 설정한다.*/
const buffer = Person.encode(massage).finish();
/* 생성된 메시지 객체를 바이너리 형식으로 인코딩하는 부분 , 'finish'메서드는 최종 인코딩된
버퍼를 반환한다.*/
const decodedMessage = Person.decode(buffer);
//인코딩된 버퍼를 다시 메시지 객체로 디코딩하는 부분
console.log("Original message:",message);
//원래 생성된 메시지 객체를 콘솔로 출력
console.log("Encoded buffer:",buffer);
//인코딩된 바이너리 버퍼를 콘솔에 출력
console.log("Decoded message:",decodedMessage);
//디코딩된 메시지 객체를 콘솔에 출력
})
패킷 구조 설계
바이트 배열의 구조
프로젝트에서 공통적으로 사용할 패킷의 구조 정의
필드 명 타입 설명 크기
totalLength | int | 메시지의 전체 길이 | 4Byte |
packetType | Int | 패킷 타입 | 1Byte |
protobuf | protobuf | 프로토콜 버퍼 구조체 | 가변 |
프로토 버프 구조
- common 아래의 구조로 클라이언트 요청 → “payload”에 각 핸들러에 맞는 데이터가 들어가게 된다.
필드 명 타입 설명
handlerId | uint32 | 핸들러 ID (4바이트) |
userId | string | 유저 ID (UUID ,16바이트) |
clientVersion | string | 클라이언트 버전(문자열) |
sequence | uint32 | 유저의 호출 수 (42억) |
payload | bytes | 실제 데이터 |
syntax = "proto3";
package common;
//공통 패킷 구조
message Packet{
uint32 handlerId = 1; //핸들러 ID (4byte)
string userId = 2; //유저 ID (UUID)
string clientVersion = 3; //클라이언트 버전(문자열)
uint32 sequence = 4; // 유저의 호출 수 (42억)
bytes payload = 5; // 실제 데이터
}
- Response 클라이언트 요청에 대해서는 아래의 구조로 반환해주게 된다.
필드 명 타입 설명
handlerId | uint32 | 핸들러 ID |
responseCode | uint32 | 응답 코드(성공:0 , 실패:에러 코드) |
timestamp | int64 | 메시지 생성 타임스탬프(Unix타임스탬프) |
data | bytes | 실제 응답 데이터 (선택적 필드) |
sequence | uint32 | 시퀀스 값 |
syntax = "proto3";
package response;
//공통 응답 메시지 구조
message Response{
uint32 handlerId = 1; //핸들러ID
uint32 responseCode = 2; //응답 코드 (성공:0 , 실패:에러 코드)
int64 timestamp = 3; //메시지 생성 타임스탬프(Unix타임스탬프)
bytes data = 4; //실제 응답 데이터 (선택적 필드)
uint32 sequence = 5; //시퀀스 값
}
- 프로토버프 로드
import fs from "fs";
import path from "path";
import { fileURLToPath } from "url";
import protobuf from "protobufjs";
import { packetNames } from "../protobuf/packetNames.js";
const __filename = fileURLToPath(import.meta.url);
const __dirname = path.dirname(__filename);
//최상위 경로
const protoDir = path.join(__dirname, "../protobuf");
//이 폴더 내에 있는 모든 proto로 끝나는 확장자를 읽는 함수
const getAllProtoFiles = (dir, fileList = []) => {
const files = fs.readdirSync(dir);
files.forEach((file) => {
const filePath = path.join(dir, file);
if (fs.statSync(filePath).isDirectory()) {
getAllProtoFiles(filePath, fileList);
} else if (path.extname(file) === ".proto") {
fileList.push(filePath);
}
});
return fileList;
};
const protoFiles = getAllProtoFiles(protoDir);
const protoMessages = {}; //원본
export const loadProtos = async () => {
try {
const root = new protobuf.Root(); //protobuf에 내장된 메서드 Root는 새로운 인스턴스를 생성해준다.
await Promise.all(protoFiles.map((file) => root.load(file)));
for (const [packageName, types] of Object.entries(packetNames)) {
console.log(`~ file: loadProtos.js:40 ~ loadProtos ~ packageName:`, packageName);
protoMessages[packageName] = {};
for (const [type, typeName] of Object.entries(types)) {
protoMessages[packageName][type] = root.lookupType(typeName);
}
}
// console.log(protoMessages);
console.log(`protobuf 파일 로드됨`);
} catch (err) {
console.error(`Protobuf 파일 로드 중 에러발생함`, err);
}
};
//데이터가 변조될 가능성을 최대한 줄이기위해 얕은복사로 복사한 데이터를 가져다 쓸꺼임
export const getProtoMessages = () => {
console.log(`~ file: loadProtos.js:55 ~ getProtoMessages ~ protoMessages:`, protoMessages);
return { ...protoMessages }; //원본을 복사한 객체
};
'부트캠프' 카테고리의 다른 글
멀티플레이 게임 파일 구조 이해 (0) | 2024.11.05 |
---|---|
프로토콜 , 로드밸런싱 , 헬스체크 (1) | 2024.10.31 |
리눅스 기본 명령어 / PM2명령어 (1) | 2024.10.24 |
일단박조KPT (2) | 2024.10.16 |
IOCP 의 개념 (0) | 2024.10.14 |