nodejs開發grpc示例
Nodejs開發grpc有兩種方式(與其他語言開發方式不同)
- 靜態代碼生成:與傳統方式一樣,提前編譯生成好js源碼,開發時就可以應用生成js文件中源碼。
- 動態代碼生成:不需要提前由.proto文件(IDL文件)生成js代碼,而是通過提前指定好IDL文件的位置,運行時再生成對應的源碼文件。
哪個好,哪個不好?沒有明確規則,但是一個最佳實踐:要麼全部動態生成、要麼全部靜態生成,不然容易錯亂。
開發nodejs,工程路徑沒有嚴格要求,這裏在工程根目錄下:
- 創建app文件存放我們開發的js源碼
- 創建proto文件存放IDL文件
- 由於nodejs是異步框架,所以編寫nodejs代碼絕大多數都是通過回調、通知、事件的方式獲取對端的響應
一、動態代碼生成
可見,代碼中僅僅指定了IDL文件的路徑,通過對應的gprc的load方法加載這個文件,全程沒有用到grpc的編譯工具生成相關代碼。
工程結構:
這裏是一個grpc的nodejs客戶端與服務端實現,首先是服務端代碼
//定義一個常量指定proto文件路徑
var PROTO_FILE_PATH = 'E:\\01.study\\36.nodejs\\workspace\\grpc-demo\\proto\\Student.proto';
//引入GRPC庫
var grpc = require('grpc');
//找到我們在IDL文件中定義的service:StudentService
var grpcService = grpc.load(PROTO_FILE_PATH).com.mzj.netty.ssy._08_grpc;
//定義服務端
var server = new grpc.Server();
server.addService(grpcService.StudentService.service,{
//添加測試的rpc方法,服務名:服務對應調用方法
getRealNameByUsername: getRealNameByUsername1,
// 添加其他rpc方法
getStudentsByAge: getStudentsByAge1,
getStudentsWrapperByAges: getStudentsWrapperByAges1,
biTalk: biTalk1,
})
//綁定端口,並設置不是用ssl安全加密
server.bind('localhost:8899',grpc.ServerCredentials.createInsecure());
//啓動服務器
server.start();
//實現rpc服務調用處理函數:參數1call:請求對象,參數2callback:回調函數
function getRealNameByUsername1(call,callback) {
console.log("username : " + call.request.username);//打印請求對象
/**
* 定義回調函數
*/
callback(null,{realname: 'mazhongjia'});//參數1:錯誤對象,這裏不進行設置,參數2:返回給客戶端的結果對象,這裏的屬性名對應IDL中聲明的屬性名
}
function getStudentsByAge1(){
}
function getStudentsWrapperByAges1(){
}
function biTalk1(){
}
然後是客戶端代碼:
//--------------動態代碼生成的方式:--------------
//1、定義grpc的IDL文件位置
var PROTO_FILE_PATH = 'E:\\01.study\\36.nodejs\\workspace\\grpc-demo\\proto\\Student.proto';
//2、引入grpc庫,nodejs中,引入庫用require方法
var grpc = require('grpc');
//3、定義grpc服務
//找到我們在IDL文件中定義的service:StudentService
var grpcService = grpc.load(PROTO_FILE_PATH).com.mzj.netty.ssy._08_grpc;
//4、定義nodejs客戶端
var client = new grpcService.StudentService('localhost:8899',grpc.credentials.createInsecure());
//grpc.credentials.createInsecure():創建的是一個不安全的、不是用ssl證書加密的通道,與java如下代碼等價:
//.usePlaintext(true).
//調用rpc方法,其中方法首字母轉小寫
client.getRealNameByUsername({username:'lisi'},function (error,respData) {
console.log(respData);
})
grpc的IDL文件:
syntax = "proto3";
package com.mzj.netty.ssy._08_grpc;
option java_package = "com.mzj.netty.ssy._08_grpc";
option java_outer_classname = "StudentProto";
option java_multiple_files = true;
service StudentService{
//gRpc支持的四種調用形式示例:
rpc GetRealNameByUsername(MyRequest) returns (MyResponse){}//種類1:普通輸入參數與返回值
rpc GetStudentsByAge(StudentRequest) returns (stream StudentResponse){}//種類2:服務端rpc方法返回值是stream形式,參數是普通對象
rpc GetStudentsWrapperByAges(stream StudentRequest) returns (StudentResponseList){}//種類3:客戶端輸入參數是stream形式,返回是一個普通對象
rpc BiTalk(stream StreamRequest) returns (stream StreamResponse){}//種類4:雙向的流式的數據傳遞(客戶端發送請求/服務端返回結果都是流式)
//從IDL的定義上,四種調用形式區別體現在rpc定義時方法參數、返回值的message前面是否有stream關鍵字
//rpc方法的參數與返回值類型都是IDL中定義的message類型,而不能是string、int32等變量類型,這一點跟thrift不同,即使只有一個屬性,也得定義成message
}
message MyRequest{
string username = 1;
}
message MyResponse{
string realname = 2;
}
message StudentRequest{
int32 age = 1;
}
message StudentResponse{
string name = 1;
int32 age = 2;
string city = 3;
}
message StudentResponseList{
//protobuf中集合用repeated表示
repeated StudentResponse studentResponse = 1;//repeated表示集合類型,這裏表示服務器端向客戶端返回的是一個集合類型,集合中元素是StudentResponse
}
message StreamRequest{
string request_info = 1;
}
message StreamResponse{
string response_info = 1;
}
分別運行服務端、客戶端代碼。
二、靜態代碼生成
1、說明:通過grpc編譯器protoc預先生成js源碼,然後編碼過程中顯示調用。
2、具體操作方式:按照官網示例
https://github.com/grpc/grpc/tree/v1.4.x/examples/node/static_codegen
下面內容參考的是上面網址的README.md文件
步驟1:安裝grpc-tools,通過nodejs的npm包管理工具按照grpc-tools插件
npm install -g grpc-tools
步驟2:通過編譯器生成對應nodejs源碼:
下面是readme文件中原始命令,需要進行修改
grpc_tools_node_protoc --js_out=import_style=commonjs,binary:../node/static_codegen/ --grpc_out=../node/static_codegen --plugin=protoc-gen-grpc=`which grpc_tools_node_protoc_plugin` helloworld.proto
修改後實際執行的爲:
grpc_tools_node_protoc --js_out=import_style=commonjs,binary:static_codegen/ --grpc_out=static_codegen --plugin=protoc-gen-grpc=/c/Users/mzj/AppData/Roaming/npm/grpc_tools_node_protoc_plugin.cmd proto/Student.proto
其中兩個路徑分別是生成的消息源碼路徑和grpc通信源碼路徑,我們修改成生成到相同的路徑
其中/c/Users/mzj/AppData/Roaming/npm/grpc_tools_node_protoc_plugin是通過執行原始命令中which grpc_tools_node_protoc_plugin得到的,但是後面需要加上.cmd:
執行後出錯:
提示沒有這個目錄,手工創建後再執行,則OK
生成代碼如下:
其中這兩個文件:Student_pb.js是消息相關源碼,Student_grpc_pb.js是grpc相關通信代碼(與java生成源碼類似,也是消息+grpc通信兩部分)
編寫客戶端代碼:
//1、定義service,service位於Student_grpc_pb.js中
var service = require('../static_codegen/proto/Student_grpc_pb.js');
//2、定義消息
var message = require('../static_codegen/proto/Student_pb.js');
//3、引入grpc庫
var grpc = require('grpc');
//4、定義客戶端
var client = new service.StudentServiceClient('localhost:8899',grpc.credentials.createInsecure());
//5、定義請求message(與動態生成方式不同)
var request = new message.MyRequest();
request.setUsername('huna');
//6、調用rpc方法
client.getRealNameByUsername(request,function (error,respData) {
//靜態調用方式是以方法調用的方式獲取返回結果,因爲rpc的返回值在編譯期可見,而動態方式rpc返回值編輯期不可見、是通過屬性的方式獲取結果
console.log(respData.getRealname());//打印返回結果
})
測試:啓動動態代碼生成編寫的服務端、啓動靜態代碼生成的客戶端。
靜態代碼生成方式編寫的服務端:
總結動態與靜態代碼生成方式優缺點:
- 靜態與動態方式使用場景,到底使用哪種(視頻31_30分鐘)
- 動態方式好處:不需要預先生成源碼文件
- 動態方式缺陷:編寫代碼階段無法獲取具體有哪些屬性,只能自己保證編寫代碼屬性的正確性,無法在編譯期保證正確性,同時可讀性不好
- 靜態方式好處:每一個對象有什麼方法,編寫代碼階段都能看到,代碼可讀性好
- 靜態方式缺點:較動態方式麻煩
- 我推薦使用靜態代碼生成的方式:因爲代碼編寫過程中一些代碼編寫提示與可讀性更重要,而自動生成代碼可以通過編寫腳步實現自動化。