gRPC 예제 / (Python to nodeJS)
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 데이터)
from http://gentian.tistory.com/23 by ccl(S)
댓글
댓글 쓰기