1) gRPC 관련 지식
A. gRPC
구글에서 처음 개발하여 공개한 원격 프로시저 호출(RPC) 시스템으로 HTTP/2 를 사용하고 인터페이스 설명 언어로 프로토콜 버퍼를 사용함.
인터페이스 설명 언어가 하나이기 때문에 다양한 언어에서 데이터를 주고 받을 수 있는 장점이 있음
B. HTTP/2
HTTP/1.1의 알려진 성능 제한이 개선된 프로토콜로 헤더 필드 압축과 동일한 연결에서 다중 동시 교환 허용 등을 통한 리소스 비용 절감 및 성능 개선이 진행됨
C. 프로토콜 버퍼
구글에서 공개한 직렬화 데이터 구조로 다양한 언어를 지원한다는 장점이 있다. 최대 64M 까지 데이터를 전달할 수 있다고 하며, JSON 과 유연한 데이터 전환이 가능하다.
관련 출처 : http://bcho.tistory.com/1182
2) gRPC 사용 (python to nodejs)
I. 사용한 소프트웨어 (ubuntu 16.04 환경에서 설치함)
l nodejs (v6.14.4)
n nodejs grpc module (v1.11.0)
l mongoDB(v2.6.10)
l Python (v2.7.12)
n grpcio, grpcio-tool (v1.16.0)
II. protobuf 인터페이스 선언
gRPC 를 사용하려면 받는 쪽과 주는 쪽 모두 인터페이스 모델을 정의해주어야 한다. message 는 각 언어에서 Object 에 해당하는 값이며 Service 는 Function 에 해당하는 값이다.
syntax = "proto3";
package json;
service Json {
rpc grpcSend (Request) returns (Response) {}
}
message Request {
string jsonStr = 1;
}
message Response {
string jsonStr = 1;
}
III. 서버 및 클라이언트 설치
l nodejs 설치
cd ~
curl -sL https://deb.nodesource.com/setup_6.x -o
nodesource_setup.sh
bash nodesource_setup.sh
apt-get install nodejs
apt-get install build-essential
l npm 모듈 설치 (아래 package.json 파일 작성 후 npm install 실행)
{
"name":
"myapp",
"version":
"0.1.0",
"description":
"",
"main":
"app_grpc.js",
"scripts": {
"test":
"echo \"Error: no test specified\" && exit 1"
},
"author":
"nanum",
"license":
"ISC",
"bugs": {},
"homepage":
"",
"dependencies":
{
"body-parser":
"^1.18.3",
"ejs":
"^2.3.3",
"express":
"^4.16.3",
"grpc-web":
"^1.0.0",
"method-override": "^3.0.0",
"mongoose":
"^5.3.0",
"requests":
"^0.2.2",
"websocket":
"^1.0.28",
"@grpc/proto-loader": "^0.1.0",
"async":
"^1.5.2",
"google-protobuf": "^3.0.0",
"grpc":
"^1.11.0",
"lodash":
"^4.6.1",
"minimist":
"^1.2.0"
}
}
l mongoDB 설치
vi
/usr/sbin/policy-rc.d
#
exit 101 대신 exit 0 으로 변경.
#실행까지 같이
apt-get
install mongodb-clients mongodb-server
apt-get
update
l python 관련 grpc 라이브러리 설치
apt-get
install python-pip
pip
install --upgrade pip
python
-m pip install grpcio #--ignore-installed
python
-m pip install --user grpcio-tools
IV. 클라이언트 코드
grpc_sender.py (공통 코드)
from __future__ import print_function
import json
import time
import grpc
import json_pb2
import json_pb2_grpc
def make_request(_jsonStr):
return json_pb2.Request(jsonStr=_jsonStr)
def grpcSend(_jsonStr):
with grpc.insecure_channel('10.0.0.133:3002') as channel:
request = make_request(_jsonStr)
stub = json_pb2_grpc.JsonStub(channel)
response = stub.grpcSend(request)
print('Sent : grpc')
grpc_inference.py (main)
import sum
import base64
import time
import grpc_sender
#ws_sender.sendWs()
def toJson(message):
jsonString =
json.dumps(message, ensure_ascii=False).encode('utf8')
return jsonString
def get_data(num):
json_path =
'/root/client_send/json/api/inference/current_tracking_'+str(num)+'.json'
img_path =
'/root/client_send/images/cam/cam ('+str(num)+').jpg'
#json_path = '/home/star/generator/images/screenshots/conference.jpg'
with open(json_path,
'rb') as single_json:
json_text =
single_json.read()
with open(img_path,
'rb') as single_img:
img_b64 =
base64.b64encode(single_img.read())
json_text =
"{\"api-type\": \"inference\",
\"current_tracking\":" + json_text + ",
\"img\" : \"" + img_b64 + "\"}"
print(json_text)
return json_text
def main():
type_num = 1
while type_num <=
21:
tmpString =
get_data(type_num)
print(tmpString)
grpc_sender.grpcSend(tmpString)
type_num =
type_num+1
time.sleep(2)
if __name__ == '__main__':
print('start')
main()
print('end')
current_tracking_1.json (샘플 json 데이터)
{
"time":
"2018-10-19T14:45:04.080163+09:00",
"state_name": "daily_ready",
"status":
null,
"state_id": 140001861004480,
"intent":
"unknown",
"slots":
null,
"state_group": "ready",
"domain":
"Daily",
"state_tag": "Daily.ready.daily_ready.Ready",
"stance":
"neutral",
"speech":
"안녕",
"subject":
null
}
cam (1).jpg (샘플 이미지 데이터)
V. 서버 코드
l app_grpc.js (주요 핵심 코드)
/* proto파일에서 service descriptors 로드 */ var PROTO_PATH = __dirname + '/protobuf/json.proto'; var grpc = require('grpc'); var protoLoader = require('@grpc/proto-loader'); // Suggested options for similarity to existing grpc.load
behavior var packageDefinition = protoLoader.loadSync( PROTO_PATH, {keepCase: true, longs: String, enums: String, defaults: true, oneofs: true }); var protoDescriptor =
grpc.loadPackageDefinition(packageDefinition); // The protoDescriptor object has the full package hierarchy var json = protoDescriptor.json; /* proto파일에서 service descriptors 로드 end */ … function grpcSend(call, callback) {
//sendWS(call.request.message);
console.log(call.request.jsonStr);
broadcast(call.request.jsonStr); callback(null,
call.request); } function grpc_start() { var server =
new grpc.Server();
server.addService(json.Json.service, {grpcSend: grpcSend});
server.bind('0.0.0.0:4996',
grpc.ServerCredentials.createInsecure());
server.start(); } grpc_start();
VI. 통합 테스트
Receive서버 실행
node app_grpc.js
Send클라이언트 실행
python grpc_inference.py
grpc 도 처음이지만, protobuf 라는 것을 처음 써보았다.
사용해본 바로는 Java의 SOAP 와 JNI 를 합친 느낌이었다.
JNI 인터페이스 처럼 protobuf 인터페이스를 지정해서 언어에 맞는 전처리 작업을 해주고,
SOAP 처럼 gRPC도 원격으로 서버의 함수를 실행시키는 듯한 느낌을 주었다.
웹 소켓으로 되어 있던 모듈을 grpc로 바꾸는 작업이었던 지라,
웹 소켓과의 속도 비교도 할 수 있었는데, 약 1.2배정도 빠른 것을 확인할 수 있었다. (63 kb 데이터)