grpc 簡(jiǎn)單使用

參考:golang grpc 快速開(kāi)始
以 windows 平臺(tái)為例:

go get google.golang.org/protobuf/cmd/protoc-gen-go 
 go get  google.golang.org/grpc/cmd/protoc-gen-go-grpc
因?yàn)槲耶?dāng)前 的 gopath 就是f:/go 所以 直接把get 包安裝到了 bin 目錄

gopath/bin 要加入到環(huán)境變量

然后就可以試一下 grpc 了

  • 直接用 官方的例子
git clone -b v1.35.0 https://github.com/grpc/grpc-go
cd grpc-go/examples/helloworld
go run greeter_server/main.go
go run greeter_client/main.go
# greeter_client控制臺(tái)輸出:
> Greeting: Hello world

  • 修改 rpc 服務(wù)的函數(shù)
    在 helloworld/helloworld.proto 添加SayHelloAgain()具有相同請(qǐng)
    求和響應(yīng)類(lèi)型的新方法音同,改成如下文件
// The greeting service definition.
service Greeter {
  // Sends a greeting
  rpc SayHello (HelloRequest) returns (HelloReply) {}
  // Sends another greeting
  rpc SayHelloAgain (HelloRequest) returns (HelloReply) {}
}

// The request message containing the user's name.
message HelloRequest {
  string name = 1;
}

// The response message containing the greetings
message HelloReply {
  string message = 1;
}

  • 重新編譯更新的 .proto 文件
protoc --go_out=. --go_opt=paths=source_relative \
    --go-grpc_out=. --go-grpc_opt=paths=source_relative \
    helloworld/helloworld.proto
image.png

重新生成 了 helloworld/helloworld.pb.go和helloworld/helloworld_grpc.pb.go文件
我們 上面新添加了 一個(gè) rpc 函數(shù) : SayHelloAgain

  • 修改 greeter_server/main.go
    添加下面的代碼
func (s *server) SayHelloAgain(ctx context.Context, in 
*pb.HelloRequest) (*pb.HelloReply, error) {
        return &pb.HelloReply{Message: "Hello again " + in.GetName()}, nil
}
  • 修改 greeter_client/main.go

main 函數(shù)末尾添加 :

r, err = c.SayHelloAgain(ctx, &pb.HelloRequest{Name: name})
if err != nil {
        log.Fatalf("could not greet: %v", err)
}
log.Printf("Greeting: %s", r.GetMessage())
  • 分別運(yùn)行 greeter_server/main.go 和 greeter_client/main.go

更全面的官方教程在此, 包含普通砚蓬,客戶(hù)端流,服務(wù)端流,雙向流

簡(jiǎn)單介紹一下四種模式下的基本用法(既然看了萄金,還是要記錄一下的)

服務(wù)端:

  • 普通的調(diào)用
func (s *routeGuideServer) GetFeature(ctx context.Context, point 
*pb.Point) (*pb.Feature, error) {
    for _, feature := range s.savedFeatures {
        if proto.Equal(feature.Location, point) {
            return feature, nil
        }
    }
    // No feature was found, return an unnamed feature
    return &pb.Feature{Location: point}, nil
}
  • 服務(wù)端流 ( stream.Send 不斷發(fā)響應(yīng))
// 服務(wù)端流 
func (s *routeGuideServer) ListFeatures(rect *pb.Rectangle, 
stream pb.RouteGuide_ListFeaturesServer) error {
    for _, feature := range s.savedFeatures {
        if inRange(feature.Location, rect) {
            if err := stream.Send(feature); err != nil {
                return err
            }
        }
    }
    return nil
}
  • 客戶(hù)端流 ( stream.Recv() 不斷取值, 遇到 io.EOF 的錯(cuò)誤,就調(diào)用 stream.SendAndClose 返回值鞭光,并通知客戶(hù)端函數(shù)執(zhí)行完畢)
func (s *routeGuideServer) RecordRoute(stream pb.RouteGuide_RecordRouteServer) error {
    var pointCount, featureCount, distance int32
    var lastPoint *pb.Point
    startTime := time.Now()
    for {
        point, err := stream.Recv()
        if err == io.EOF {
            endTime := time.Now()
            return stream.SendAndClose(&pb.RouteSummary{
                PointCount:   pointCount,
                FeatureCount: featureCount,
                Distance:     distance,
                ElapsedTime:  int32(endTime.Sub(startTime).Seconds()),
            })
        }
        if err != nil {
            return err
        }
        pointCount++
        for _, feature := range s.savedFeatures {
            if proto.Equal(feature.Location, point) {
                featureCount++
            }
        }
        if lastPoint != nil {
            distance += calcDistance(lastPoint, point) // 計(jì)算兩個(gè) point之間的距離
        }
        lastPoint = point
    }
}

  • 雙向流 ( stream 入?yún)⒓瓤梢訰ecv 也可以 Send ,每一個(gè) recv 都會(huì)循環(huán)發(fā)送 send ,這樣就模擬了雙向的效果)
// 為了模擬流發(fā)送泞遗,他在每一個(gè)請(qǐng)求收到后惰许,循環(huán)發(fā)送所有的對(duì)應(yīng)
// 的值, 并且枷鎖史辙,防止并發(fā)共享一個(gè)屬性導(dǎo)致出現(xiàn)問(wèn)題
func (s *routeGuideServer) RouteChat(stream pb.RouteGuide_RouteChatServer) error {
    for {
        in, err := stream.Recv()
        if err == io.EOF {
            return nil
        }
        if err != nil {
            return err
        }
        key := serialize(in.Location)

        s.mu.Lock()
        s.routeNotes[key] = append(s.routeNotes[key], in)
        // Note: this copy prevents blocking other clients while serving this one.
        // We don't need to do a deep copy, because elements in the slice are
        // insert-only and never modified.
        rn := make([]*pb.RouteNote, len(s.routeNotes[key]))
        copy(rn, s.routeNotes[key])
        s.mu.Unlock()

        for _, note := range rn {
            if err := stream.Send(note); err != nil {
                return err
            }
        }
    }
}

客戶(hù)端
其實(shí)和服務(wù)端基本邏輯一致

  • 調(diào)用服務(wù)流:
func printFeatures(client pb.RouteGuideClient, rect *pb.Rectangle) {
    log.Printf("Looking for features within %v", rect)
    ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second)
    defer cancel()
    stream, err := client.ListFeatures(ctx, rect)
    if err != nil {
        log.Fatalf("%v.ListFeatures(_) = _, %v", client, err)
    }
    for {
        feature, err := stream.Recv()
        if err == io.EOF {
            break
        }
        if err != nil {
            log.Fatalf("%v.ListFeatures(_) = _, %v", client, err)
        }
        log.Printf("Feature: name: %q, point:(%v, %v)", feature.GetName(),
            feature.GetLocation().GetLatitude(), feature.GetLocation().GetLongitude())
    }
}

就是一個(gè)簡(jiǎn)單的 for {stream.Recv()}

下面看一個(gè)簡(jiǎn)單的汹买,grpc 里究竟怎么實(shí)現(xiàn) rpc 協(xié)議的:

func (c *routeGuideClient) GetFeature(ctx context.Context, in *Point, opts ...grpc.CallOption) (*Feature, error) {
    out := new(Feature)
    err := c.cc.Invoke(ctx, "/routeguide.RouteGuide/GetFeature", in, out, opts...)
    if err != nil {
        return nil, err
    }
    return out, nil
}

Invoke 所在接口:

type ClientConnInterface interface {
    // Invoke performs a unary RPC and returns after the response is received
    // into reply.
    Invoke(ctx context.Context, method string, args interface{}, reply interface{}, opts ...CallOption) error
    // NewStream begins a streaming RPC.
    NewStream(ctx context.Context, desc *StreamDesc, method string, opts ...CallOption) (ClientStream, error)
}

這里就類(lèi)似 rpc 的風(fēng)格了。(呀復(fù)習(xí)一下聊倔, go rpc 的基本寫(xiě)法了)

type HelloService struct {}

func (p *HelloService) Hello(request string, reply *string) error {
    *reply = "hello:" + request
    return nil
}
其中Hello方法必須滿(mǎn)足Go語(yǔ)言的RPC規(guī)則:方法只能有兩個(gè)可序
列化的參數(shù)晦毙,其中第二個(gè)參數(shù)是指針類(lèi)型,并且返回一個(gè)error類(lèi)
型耙蔑,同時(shí)必須是公開(kāi)的方法见妒。

grpc 官方文檔: 生成代碼結(jié)構(gòu)的講解

具體講講生成的 代碼(有一些名稱(chēng)是固定的格式)

服務(wù)端:

對(duì)于 service Bar{ rpc Foo( client_data) returns (server_data) ; } 為例子 ( 流的話加上關(guān)鍵字 stream 修飾參數(shù)即可 )
注冊(cè)函數(shù) RegisterBarServer 格式為 Register<service name>Server (當(dāng)然你也可以自己寫(xiě)注冊(cè)的函數(shù))

func RegisterBarServer(s *grpc.Server, srv BarServer)

普通一元函數(shù) ( 以 Foo 為例子)

Foo(context.Context, *MsgA) (*MsgB, error)
// MsgA 接受的消息, MsgB 發(fā)送的消息

服務(wù)端流:

Foo(*MsgA, <ServiceName>_FooServer) error

// MsgA   接收到的消息
//  <ServiceName>_FooServer  流接口類(lèi)型   
// <Services name>_<rpc_func_name>Server

<ServiceName>_FooServer 接口定義如下 ( 所以可以使用 Send 方法了):

type <ServiceName>_FooServer interface {
    Send(*MsgB) error
    grpc.ServerStream
}

客戶(hù)端流

Foo(<ServiceName>_FooServer) error

type <ServiceName>_FooServer interface {
    SendAndClose(*MsgA) error
    Recv() (*MsgB, error)
    grpc.ServerStream
}

// 一直 Recv 甸陌,最后結(jié)束的時(shí)候發(fā)送一次消息 調(diào)用 SendAndClose  結(jié)束本次響應(yīng)

// 流消息結(jié)束须揣, Recv返回(nil, io.EOF)  

雙流

Foo(<ServiceName>_FooServer) error

type <ServiceName>_FooServer interface {
    Send(*MsgA) error
    Recv() (*MsgB, error)
    grpc.ServerStream
}


//同時(shí)首發(fā)數(shù)據(jù)  Send  和 Recv

客戶(hù)端接口

  • 一元方法:
(ctx context.Context, in *MsgA, opts ...grpc.CallOption) (*MsgB, error)

  • 服務(wù)流
Foo(ctx context.Context, in *MsgA, opts ...grpc.CallOption) (<ServiceName>_FooClient, error)
// <ServiceName>_FooClient  代表 服務(wù)端的 流對(duì)象 

type <ServiceName>_FooClient interface {
    Recv() (*MsgB, error)
    grpc.ClientStream
}
  • 客戶(hù)端流
Foo(ctx context.Context, opts ...grpc.CallOption) (<ServiceName>_FooClient, error)

// <ServiceName>_FooClient代表客戶(hù)機(jī)到服務(wù)器stream的MsgA
type <ServiceName>_FooClient interface {
    Send(*MsgA) error
    CloseAndRecv() (*MsgB, error)
    grpc.ClientStream
}
  • 雙向流
type <ServiceName>_FooClient interface {
    Send(*MsgA) error
    Recv() (*MsgB, error)
    grpc.ClientStream
}

暫時(shí)完畢。钱豁。

只有 客戶(hù)端流耻卡,才有 CloseAndRecv (客戶(hù)端) 和 SendAndClose ( 服務(wù)端只有這個(gè),沒(méi)有 send)函數(shù)寥院,其他的沒(méi)有這兩個(gè)函數(shù)

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末劲赠,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子秸谢,更是在濱河造成了極大的恐慌凛澎,老刑警劉巖,帶你破解...
    沈念sama閱讀 206,311評(píng)論 6 481
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件估蹄,死亡現(xiàn)場(chǎng)離奇詭異塑煎,居然都是意外死亡,警方通過(guò)查閱死者的電腦和手機(jī)臭蚁,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,339評(píng)論 2 382
  • 文/潘曉璐 我一進(jìn)店門(mén)最铁,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái)讯赏,“玉大人,你說(shuō)我怎么就攤上這事冷尉∈妫” “怎么了?”我有些...
    開(kāi)封第一講書(shū)人閱讀 152,671評(píng)論 0 342
  • 文/不壞的土叔 我叫張陵雀哨,是天一觀的道長(zhǎng)磕谅。 經(jīng)常有香客問(wèn)我,道長(zhǎng)雾棺,這世上最難降的妖魔是什么膊夹? 我笑而不...
    開(kāi)封第一講書(shū)人閱讀 55,252評(píng)論 1 279
  • 正文 為了忘掉前任,我火速辦了婚禮捌浩,結(jié)果婚禮上放刨,老公的妹妹穿的比我還像新娘。我一直安慰自己尸饺,他們只是感情好进统,可當(dāng)我...
    茶點(diǎn)故事閱讀 64,253評(píng)論 5 371
  • 文/花漫 我一把揭開(kāi)白布。 她就那樣靜靜地躺著浪听,像睡著了一般麻昼。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上馋辈,一...
    開(kāi)封第一講書(shū)人閱讀 49,031評(píng)論 1 285
  • 那天抚芦,我揣著相機(jī)與錄音,去河邊找鬼迈螟。 笑死叉抡,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的答毫。 我是一名探鬼主播褥民,決...
    沈念sama閱讀 38,340評(píng)論 3 399
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼洗搂!你這毒婦竟也來(lái)了消返?” 一聲冷哼從身側(cè)響起,我...
    開(kāi)封第一講書(shū)人閱讀 36,973評(píng)論 0 259
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤耘拇,失蹤者是張志新(化名)和其女友劉穎撵颊,沒(méi)想到半個(gè)月后,有當(dāng)?shù)厝嗽跇?shù)林里發(fā)現(xiàn)了一具尸體惫叛,經(jīng)...
    沈念sama閱讀 43,466評(píng)論 1 300
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡倡勇,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 35,937評(píng)論 2 323
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了嘉涌。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片妻熊。...
    茶點(diǎn)故事閱讀 38,039評(píng)論 1 333
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡夸浅,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出扔役,到底是詐尸還是另有隱情帆喇,我是刑警寧澤,帶...
    沈念sama閱讀 33,701評(píng)論 4 323
  • 正文 年R本政府宣布亿胸,位于F島的核電站番枚,受9級(jí)特大地震影響,放射性物質(zhì)發(fā)生泄漏损敷。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,254評(píng)論 3 307
  • 文/蒙蒙 一深啤、第九天 我趴在偏房一處隱蔽的房頂上張望拗馒。 院中可真熱鬧,春花似錦溯街、人聲如沸留晚。這莊子的主人今日做“春日...
    開(kāi)封第一講書(shū)人閱讀 30,259評(píng)論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)。三九已至花履,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間服赎,已是汗流浹背媒殉。 一陣腳步聲響...
    開(kāi)封第一講書(shū)人閱讀 31,485評(píng)論 1 262
  • 我被黑心中介騙來(lái)泰國(guó)打工, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留郭宝,地道東北人辞槐。 一個(gè)月前我還...
    沈念sama閱讀 45,497評(píng)論 2 354
  • 正文 我出身青樓,卻偏偏與公主長(zhǎng)得像粘室,于是被迫代替她去往敵國(guó)和親榄檬。 傳聞我的和親對(duì)象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 42,786評(píng)論 2 345

推薦閱讀更多精彩內(nèi)容