회사에서 주로 mfc를 다루다가 최근 웹프론트엔드 파트 업무를 맡게 되면서 한꺼번에 html, css, javascript, vue를 접하게 되었다...혼돈...😱
아무튼 과장님이 백엔드에서 프로토버퍼로 데이터를 보낼거니까 어떻게 받아서 쓰는지 알아두라고 하셔서 프로토콜 버퍼에 대해 알아보았다!
아래와 같은 순서로 프로토콜버퍼에 대해 알아보자✨
구조화된 데이터를 직렬화하는 방식이다.
직렬화(Serialization)
는 데이터를 파일로 저장하거나 네트워크 통신에 사용하기 위한 형식, 즉 바이트 스트림 형태로 변환하는 것이다.
주로 server-client간 데이터를 교환할 때 JSON형태를 많이 사용하는데 JSON 대신 프로토콜 버퍼를 이용하는 이유는 무엇일까? JSON 방식과 비교하여 프로토콜버퍼의 원리를 알아보자.
예를들어 {name : "jinny", age : 27}라는 객체가 있다고하자.
🔨JSON 포맷으로 직렬화
let person = { name : "jinny" age : 27 };
//JS객체 -> JSON 문자열로 변경
let serializedJson = JSON.stringify(person);
//serializedJson = {"name":"jinny","age":27} 데이터 크기는 총 25byte
🔨프로토콜 버퍼 포맷으로 직렬화
* 프로토 파일 : 프로토콜 버퍼 방식을 사용하기위해 필요한 파일
message Person{
string name = 1; //필드 넘버(필드 태그..?)
int32 age = 2;
}
프로토콜 버퍼를 사용한 경우에는 아래 그림과 같이 9바이트를 사용하게 된다. name, age와 같은 속성값을 프로토파일에서 작성한 필드 넘버로 대체하는 것이 핵심이다.
최초 1바이트 중 5bit는 필드 넘버
를, 3bit는 필드 타입
을 나타낸다. type을 비롯한 더욱 자세한 설명은 링크에서 확인할 수 있다.
결과적으로 프로토콜 버퍼를 사용하면 직렬화, 역질렬화 속도가 빠르고 직렬화된 파일의 크기를 월등히 줄일 수 있어, 대용량 데이터를 처리할 때 성능이 더 좋다고 한다..!
JSON과 달리 사람이 읽기 어려우며, proto파일이 없으면 데이터 해석이 불가하다는 단점도 있지만 스키마가 존재하여 이에 따라 쉽게 직렬화, 역질렬화가 가능하기 때문에 내부서비스에서 사용하면 효과적이라고 한다..!
🌞 백엔드 환경 세팅
1) npm installl -g express-generator
//backend폴더가 생성되면 cd backend 해당 폴더로 이동
2) express --veiw=pug backend(원하는 폴더명)
3) npm install
//node.js에서 protobuf를 사용하기위한 라이브러리 설치
4) npm inatall protobufjs --save
//서버 실행
5) npm start
🌞 .proto 파일 작성(test.proto)
package test;
syntax = "proto3"; //ver3 사용을 위해 명시
message Person{
string name = 1;
int32 age = 2;
}
🌞 proto.js파일 생성(routes/proto.js)
서버쪽으로 get 요청이 오는 경우 protobuf를 통해 데이터를 인코딩하여 클라이언트쪽으로 보낼 것이다.
const express = require("express");
const router = express.Router();
///import protobuf
const protobuf = require("protobufjs");
router.get("/", function (req, res, next) {
//위에서 작성한 프로토파일을 로드한다.
protobuf.load("test.proto", function (err, root) {
if (err) throw err;
//메시지 타입을 얻어온다.
let personMessage = root.lookupType("./test.Person"); //"프로토파일 패키지명.메시지이름"
//예시 데이터(해당 데이터를 전송할 예정)
let payload = { name: "jinny", age: 27 };
//필요하다면 payload가 형식에 알맞는지 유효성검사를 진행한다.
let errMsg = personMessage.verify(payload);
//payload 유효하다면 null을 반환하고,
//유효하지 않으면(age:"27"이라고 작성한다면 "age:integer expected"오류를 반환)오류를 반환한다.
if (errMsg) throw Error(errMsg);
//객체 직렬화(message => uint8Array)
var buffer = personMessage.encode(payload).finish();
console.log(buffer);
//<Buffer 0a 05 6a 69 6e 6e 79 10 1b>
//클라이언트에 직렬화한 데이터를 전송한다.
res.send(buffer);
});
});
module.exports = router;
🌞 app.js 수정
//아래 코드를 app.js파일에 추가한다.
//프론트쪽에서 api/proto로 요청이 오는 경우 routes/proto.js 파일로 라우팅
const protoRouter = require("./routes/proto");
pp.use("/api/proto", protoRouter);
🌞 프론트엔드 환경 세팅
//예제에서는 vue2를 이용했다.
1) vue create frontend(폴더명)
//폴더 이동
2) cd frontend
//protobuf 사용을 위한 라이브러리 설치
3) npm i protobufjs
//서버와 http통신을 위해 axios 설치
4) npm i axios
//실행
5) npm run serve
🌞 main.js 파일 수정 (axios import)
//모든 컴포넌트에서 this.$http.get 과 같은 방식으로 http통신가능하도록 main.js파일에서 아래와 같이 추가하였다.
import Vue from "vue";
import App from "./App.vue";
import axios from "axios";
Vue.config.productionTip = false;
Vue.prototype.$http = axios;
new Vue({
render: (h) => h(App),
}).$mount("#app");
🌞 .proto파일 작성
백엔드와 동일한 프로토 파일을 작성하면된다.
예제에서는 src/proto/test.proto 해당 경로에 작성하였다.
🌞 js파일 생성
프론트엔드 프로젝트에서 node와 같이 .proto파일을 바로 import할 수 없고, .proto파일을 .js파일로 변환하여 import해야한다.
package.json 파일에서 아래 부분을 추가해준 후
npm run proto 명령어를 실행하면
src/proto/proto.js 파일이 생성된다.
"scripts": {
"serve": "vue-cli-service serve",
"build": "vue-cli-service build",
"lint": "vue-cli-service lint",
//proto 파일이 변경된다면 npm run proto 명령어를 통해 js 파일을 갱신할 수 있다. (경로 확인 필요)
"proto": "pbjs -t json-module -w commonjs -o src/proto/proto.js src/proto/*.proto"
},
🌞 vue.config.js 파일 생성(frontend/vue.config.js)
//파일 추가 후 npm run build를 해주면 프론트에서 /api/~~~ api를 붙여서 요청할 경우 백엔드로 연결된다.
module.exports = {
devServer: {
proxy: {
"/api": {
//위에서 생성한 express 서버의 포트이다.(3000)
target: "http://localhost:3000/api",
changeOrigin: true, //
pathRewrite: {
"^/api": "",
},
},
},
},
};
🌞 App.vue 파일 작성
vue cli로 vue 프로젝트를 생성한 경우 자동으로 생성되는 컴포넌트는 삭제하였다.
<template>
<div id="app">
<button type="button" @click="getData">test</button>
{{ person }}
</div>
</template>
<script>
//위에서 생성한 .js파일을 import해준다!
import ProtoRoot from "../src/proto/proto";
export default {
name: "App",
components: {},
data() {
return {
person: {},
};
},
methods: {
getData() {
this.$http.get("/api/proto",{
//responseType 지정
responseType: "arraybuffer"
}).then((res) => {
//메시지 타입을 얻어온다.
let personMessage = ProtoRoot.lookupType("test.Person");
//서버에 데이터를 요청하면 아까 서버에서 보낸 프로토버퍼가 올 것이다.
//decdoe : uint8Array -> message
let data = personMessage.decode(Buffer.from(res.data));
console.log(data);
this.person = data;
});
},
},
};
</script>
<style></style>
위에서 작성한 코드를 실행시켜보자!
vs code termianl
//express 서버 실행 해두고,
backend> npm start
//프론트도 실행
frontend>npm run build
frontend>npm run serve
🌞 localhost:8080 접속
실행 후 vue-cli에서 기본으로 설정된 8080 포트로 접속해보자! 아래와 같은 화면이 보이고 data : person은 비어있는 상태이다.
🌞 test 버튼 클릭 & decode 결과 확인
서버 터미널에서는 아래와 같이 요청이 온 것을 확인할 수 있다.
서버측에서 프로토버퍼로 인코딩하여 보낸 데이터를 수신하여 decode하여 아래와 같이 person 데이터에 값이 들어간 것을 확인할 수 있다.
이상 간단한 예제를 통해 프로토콜 버퍼를 사용하는 법을 알아보았다. axios로 연습했는데 대용량 데이터를 계속 받아야해서 웹소켓으로 구현하게 되었지만🙄
틀린 부분이 있다면 알려주시면 감사하겠습니다(❁´◡`❁)
공부하면서 참고한 사이트들
예시까지 이해가 정말 잘되었어요. 감사합니다. :)