gRPC(google+remote process call) 詳解
grpc 簡介
grpc是一個(gè)高性能、開源和通用的 RPC 框架琅锻,面向移動(dòng)和 HTTP/2 設(shè)計(jì)卦停。所謂RPC(remote procedure call 遠(yuǎn)程過程調(diào)用)框架實(shí)際是提供了一套機(jī)制,使得應(yīng)用程序之間可以進(jìn)行通信恼蓬,而且也遵從server/client模型惊完。使用的時(shí)候客戶端調(diào)用server端提供的接口就像是調(diào)用本地的函數(shù)一樣。如下圖所示就是一個(gè)典型的RPC結(jié)構(gòu)圖滚秩。
[圖片上傳失敗...(image-861e7e-1649389684397)]
- 客戶端使用RPC調(diào)用遠(yuǎn)程方法专执,客戶端的存根發(fā)起請求,對請求的數(shù)據(jù)使用protobuf對象序列化和壓縮郁油。
- 服務(wù)器接收到請求后本股,解碼請求數(shù)據(jù),使用protobuf反序列化為內(nèi)存對象桐腌,處理業(yè)務(wù)邏輯并返回響應(yīng)結(jié)果拄显。
- 客戶端收到服務(wù)端響應(yīng),解碼響應(yīng)數(shù)據(jù)案站,喚醒阻塞客戶端躬审。
grpc 使用
工欲善其事必先利其器,開發(fā)一個(gè)grpc示例之前先安裝好需要的工具和插件
下載protoc工具 https://github.com/protocolbuffers/protobuf/releases/ 注意系統(tǒng)和版本, 下載完成后,執(zhí)行路徑添加到環(huán)境變量
插件安裝
go get -u github.com/golang/protobuf/protoc-gen-go蟆盐, GOBIN($GOPATH/bin)添加到環(huán)境變量即可1
grpc 示例
- 創(chuàng)建proto文件
syntax = "proto3";
package pb;
option go_package = "./;pb";
message Message {
string body = 1;
}
message ReqMsgNum{
}
message RespMsgNum{
int32 num = 1;
}
message ReqMatchWord{
string word = 1;
}
message RespMatchWord{
bool isMatch = 1;
}
service ChatService {
rpc SayHello(Message) returns (Message) {}
rpc GetMsgNum(ReqMsgNum) returns (stream RespMsgNum){}
rpc MatchWord(stream ReqMatchWord) returns (RespMatchWord){}
rpc Chat(stream Message) returns (stream Message){}
}
這里定義了四個(gè)方法
sayHello 普通的rpc調(diào)用
GetMsgNum 服務(wù)端流式調(diào)用承边,客戶端普通的rpc調(diào)用
MatchWord 客戶端流式調(diào)用,服務(wù)端普通rcp調(diào)用
Chat 雙向流式調(diào)用
- 生成對應(yīng)的go文件
protoc --go_out=plugins=grpc:. chat.proto
- 服務(wù)端代碼
package main
import (
"google.golang.org/grpc"
"log"
"mytest/grpc/pb"
"mytest/grpc/server/chat"
"net"
)
func main() {
listen, err := net.Listen("tcp", ":1234")
if err != nil {
log.Fatal(err)
}
s := chat.ChatServer{}
rpcServer := grpc.NewServer()
pb.RegisterChatServiceServer(rpcServer, &s)
log.Println("start server")
if err := rpcServer.Serve(listen); err != nil{
log.Fatal(err)
}
}
package chat
import (
"context"
"log"
"mytest/grpc/pb"
"strings"
"time"
)
type ChatServer struct {
}
// 普通請求
func (s *ChatServer) SayHello(ctx context.Context, in *pb.Message) (*pb.Message, error) {
log.Println("Receive message => ", in.Body)
return &pb.Message{Body: "Hello From the Server!"}, nil
}
// 服務(wù)端流推送消息數(shù)量
func (s *ChatServer) GetMsgTotal(in *pb.ReqMsgNum, stream pb.ChatService_GetMsgNumServer) error {
var msgAmount int32 = 0
for {
msgAmount++
err := stream.Send(&pb.RespMsgNum{Num: msgAmount})
if err != nil {
log.Println(err)
break
}
time.Sleep(time.Second)
}
return nil
}
// 服務(wù)端接收客戶端流
func (s *ChatServer) MatchWord(stream pb.ChatService_MatchWordServer) error {
var matchAmount int32
for {
resp, err := stream.Recv()
if err != nil {
log.Println(err)
break
}
log.Println("recv word => ", resp.Word)
// TODO 業(yè)務(wù)處理 是否匹配單詞
if strings.Contains(resp.Word, "good") {
matchAmount++
}
}
err := stream.SendAndClose(&pb.RespMatchWord{IsMatch: matchAmount>0})
if err != nil{
log.Println(err)
return err
}
return nil
}
// 雙向流通信
func (s *ChatServer) Chat(stream pb.ChatService_ChatServer) error {
var msg string
for {
resp, err := stream.Recv()
if err != nil{
log.Println(err)
break
}
log.Println("recv msg => ", resp.Body)
msg = "server " + resp.Body
err = stream.Send(&pb.Message{Body: msg})
if err != nil{
log.Println(err)
break
}
log.Println("send msg => ", msg)
}
return nil
}
- 客戶端代碼
package main
import (
"context"
"fmt"
"google.golang.org/grpc"
"log"
"mytest/grpc/pb"
"time"
)
var ctx = context.Background()
func main() {
conn, err := grpc.Dial(":1234", grpc.WithInsecure())
if err != nil{
log.Fatal(err)
}
defer conn.Close()
log.Println("start client")
cli := pb.NewChatServiceClient(conn)
sayHello(cli)
//getMsgTotal(cli)
//sendWord(cli)
//chat(cli)
}
func sayHello(cli pb.ChatServiceClient) {
resp, err := cli.SayHello(ctx, &pb.Message{Body: "i am tom"})
if err != nil{
log.Fatal(err)
}
log.Println(resp.Body)
}
// 服務(wù)端流的方式推送消息
func getMsgNum(cli pb.ChatServiceClient) {
stream, err := cli.GetMsgNum(ctx, &pb.ReqMsgNum{})
if err != nil{
log.Fatal(err)
}
for {
resp, err := stream.Recv()
if err != nil{
log.Println(err)
break
}
log.Println("msg total => ", resp.Num)
}
}
// 客戶端流的方式發(fā)送消息
func sendWord(cli pb.ChatServiceClient){
stream, err := cli.MatchWord(ctx)
if err != nil{
log.Fatal(err)
}
var sendStr string = "abc"
for i := 0; i < 5; i++{
log.Println("send str => ", sendStr)
err := stream.Send(&pb.ReqMatchWord{Word: sendStr})
if err != nil{
log.Fatal(err)
return
}
sendStr += "good"
time.Sleep(time.Second)
}
resp, err := stream.CloseAndRecv()
if err != nil{
log.Println(err)
return
}
log.Println("the word match => ", resp.IsMatch)
}
// 雙向流收發(fā)消息
func chat(cli pb.ChatServiceClient) {
stream, err := cli.Chat(ctx)
if err != nil{
log.Fatal(err)
}
var i int
var msg string
for i < 100 {
msg = fmt.Sprintf("chat msg %d", i)
fmt.Println("send msg => ", msg)
err := stream.Send(&pb.Message{Body: msg})
if err != nil{
fmt.Println(err)
break
}
resp, err := stream.Recv()
if err != nil{
fmt.Println(err)
break
}
log.Println("recv msg => ", resp.Body)
time.Sleep(time.Second * 2)
i++
}
}
遇到問題:
1. 使用protoc生成go文件提示 protoc-gen-go: unable to determine Go import path石挂,
該問題出現(xiàn)原因是沒有正確設(shè)置option go_package博助。在proto文件中添加option go_package = "./;pb";其中pb是包名,可以自定義痹愚, ./表示當(dāng)前目錄富岳。該選項(xiàng)主要是用于配置包依賴路徑,例如a.proto imports b.proto,則生成的pd.go文件也有依賴關(guān)系拯腮,因此要設(shè)置該路徑窖式。
2. 運(yùn)行時(shí)提示
undefined: grpc.SupportPackageIsVersion7
undefined: grpc.ClientConnInterface
undefined: grpc.ClientConnInterface
undefined: grpc.ServiceRegistrar
出現(xiàn)這個(gè)是因?yàn)?grpc和proto-gen-go 的版本問題導(dǎo)致的,兩種解決方案动壤。
- 升級grpc版本1.27或以上萝喘,筆者這里升級到1.29.1
replace google.golang.org/grpc => google.golang.org/grpc v1.29.1
- 降級protoc-gen-go的版本
go get -u github.com/golang/protobuf/protoc-gen-go是安裝最新版的protoc-gen-go
降低protoc-gen-go的具體辦法,在終端運(yùn)行如下命令,這里降低到版本 v1.2.0
GIT_TAG="v1.2.0"
go get -d -u github.com/golang/protobuf/protoc-gen-go
git -C "$(go env GOPATH)"/src/github.com/golang/protobuf checkout $GIT_TAG
go install github.com/golang/protobuf/protoc-gen-go
3. proto 請求參數(shù)或者響應(yīng)參數(shù)為空
通過引入 empty.proto,具體看示例
syntax = "proto3";
import "google/protobuf/empty.proto";
package proto;
message ReqHello {
string message = 1;
}
message RespHello {
string message = 1;
}
service Greeter {
// 沒有返回值 情況
rpc Hello1(ReqHello) returns (google.protobuf.Empty) {}
// 沒有參數(shù) 情況
rpc Hello2 (google.protobuf.Empty) returns (RespHello) {}
// 沒有參數(shù)阁簸,沒有返回值 情況
rpc Hello3 (google.protobuf.Empty) returns (google.protobuf.Empty) {}
}