NodeJS C++ addon 바이너리 데이터 넘기기 삽질기

C++ | 2014. 7. 8. 15:33
Posted by 클라리넷

목표 : NodeJS C++ addon을 붙여서 네트워크 통신은 NodeJS가 담당하고 게임 로직은 C++이 담당하는 것 
통신 방식 : ProtoBuf + socket.io


삽질기 

0. 자동화 작업 
.proto 파일을 통한 Auto generated 코드를 만들어서 통신하는 게 프로토버프의 기본. 
근데 이 .proto 파일 하나를 C++/NodeJS/브라우저 세 곳에서 써야되기에, 
파일을 한 군데서 작성하고, 나머지 위치에 복사해주는 방식을 택했다. 

모든 빌드의 중심은 Visual Studio. 
Visual Studio에서 prebuild, postbuild event로 스크립트를 돌리고, 
거기서 protobuf C++ 코드 자동 생성도 하고, node 확장자 생성도 하고, .proto 파일 복사도 하고, 다 한다. 

1. 클라 -> 서버 
ProtoBuf.js를 이용해서 NodeJS/브라우저에서 .proto 파일을 파싱하고, 
그 파싱한 정보로 decode/encode를 한다. 
여기서 가장 효율이 좋은 것은 ArrayBuffer 통한 바이너리 형태의 데이터. 
근데 문제는 socket.io는 바이너리 데이터의 전송을 지원하지 않는다. 
문자열 온리...... 

찾아보면 socket.io로 바이너리 데이터를 보내고 받는 플러그인도 있으나, 
귀찮아서(..) 그냥 hex 데이터를 보내고 받기로 했다. 
ProtoBuf.js에서 base64, hex, AB(이게 뭐지), binary 네 가지 형태의 암호화 방식을 지원하고 있어서 가능한 방법이다. 

어쨌든 브라우저는 패킷을 encodeHex로 보내고, 
NodeJS는 그걸 받아서 decodeHex를 한 후, 
그걸 다시 binary로 encode해서 C++ addon에 넘긴다. 
C++ protobuf에서는 binary 데이터가 아니면 읽을 수 없기에..... 
비효율적이긴 하지만, 나중에 브라우저 - NodeJS 패킷 통신을 바이너리로 직접 하게 바꾸면 해결되는 문제라 그냥 냅뒀다. 

C++에 넘겨줄 때 패킷의 enum 값을 같이 넘겨주고, 그 enum 값에 따라 적절한 protobuf message 클래스를 이용해서 
직렬화하면 완성! 나머지는 로직의 영역이다. 

2. 서버 -> 클라 
간단할줄 알았으나 내 코딩 시간을 며칠이나 잡아먹은 원흉 되시겠다. 
일단 며칠 간의 삽질을 단 5분 만에 끝내도록 도와주신 최모군(28세, 백수)님에게 감사의 말을 올립니다. 

C++ addon에서 NodeJS로 바이너리와 패킷 enum 값을 보내면 
NodeJS에서는 그걸 encodeHex 해서 브라우저로 보내는 방식이다. 
브라우저는 decodeHex 하면 되고. 

문제가 두 가지가 있었다. 
첫 번째는 NodeJS에서 C++로 넘겨준 자바스크립트 콜백 함수가 계속 증발하는 문제, 
두 번째는 바이너리 데이터를 C++에서 NodeJS로 넘기는 문제였다. 

첫 번째 문제가 며칠을 잡아먹은 문제다. 
일단 C++ addon에서 SetSendFunction을 통해 콜백을 받고, 
그 콜백을 멤버 변수로 저정해둔다. 
그리고 나중에 Send를 할 때 그 콜백에 인자를 적절하게 넘기고 호출한다. 

여기서 발생한 현상이, SetSendFunction으로 콜백을 저장하면 
SetSendFunction 내부에서는 callback->IsFunction() 이 true로 나오지만, 
나중에 Send 할 때 확인해보면 false가 나온다. 

이게 도대체 왜 이러는지 알 수가 없어서, 구글링도 해보고, 
gc가 알지 못하고 걍 죽여버리는 게 원인인가 싶어서(결국 이게 맞긴 했지만..) 
NodeJS 쪽에서 로컬 변수로 함수를 만들고, 그 변수를 대입하는 방식도 써봤고, 
C++ nodeJS addon을 만들어본 친구가 하필 일본에 가서 좌절도 해봤고, 
진짜 온갖 꼼수 및 디버깅을 해봤지만 해결을 할 수 없었다. 

열심히 삽질을 하고 있는 와중에, 잉챗에서 열심히 불평을 하고 있으려니까 
최모군(28세, 백수)님이 보우하사, Local 대신 Persistent를 써보라고 말해주셨고, 
Local<T> ->Persistent<T>로 못 바꾸고 고생하고 있자 친히 구글링을 하셔셔 stackoverflow 문서도 찾아주셨다. 
그리고 해결이 되었다. 

검색을 해본 결과, Persistent로 사용하면 v8 gc 관리 대상에 들어가고, 
Local로 하면 안 들어간다고 한다. 
그래서 Local로 저장해두면 나중에 gc가 회수해버려서 사라져버렸던 것이다. 
어쨌든 Persistent로 콜백 저장 문제는 해결했다. 

두 번째 문제는, 자바스크립트에서 binary data를 받을 때는 typed array를 쓰는데, 
이걸 어떻게 C++에서 생성해야 하나... 라는 고민이었는데, 
다행히도 NodeJS C++ 코드에서 node::Buffer라는 클래스를 제공해준다. 
이걸 쓰면 raw data를 NodeJS 단으로 쉽게 넘길 수 있다. 
해결 완료. 

3. 결론 
hex 값 부분만 일단 떼놓고 보면, C++/NodeJS/브라우저 간 protobuf를 이용한 통신을 구축 완료하였다. 
적절한 자동화와 prebuild/postscript를 거치면 당신도 이제 C++과 Javascrip를 이용한 서버/클라이언트 환경을 구축할 수 있다. 
도전해보세요! 

사실 다른 거 다 필요없고 자바스크립트 변수(특히 콜백 함수)를 C++ 측에 저장할 때는 
Local이 아니라 Persistent를 써야한다는 것만 기억하면 된다... 제길.

'C++' 카테고리의 다른 글

람다  (0) 2013.06.13
 

블로그 이미지

클라리넷

카테고리

Vie (12)
(6)
C++ (2)
게임 개발 (1)
게임하기 (0)
엔진 (0)
전자기기 (3)
Deep Learning (0)