<kbd id="afajh"><form id="afajh"></form></kbd>
<strong id="afajh"><dl id="afajh"></dl></strong>
    <del id="afajh"><form id="afajh"></form></del>
        1. <th id="afajh"><progress id="afajh"></progress></th>
          <b id="afajh"><abbr id="afajh"></abbr></b>
          <th id="afajh"><progress id="afajh"></progress></th>

          在校生的萬(wàn)字長(zhǎng)文:gRPC 實(shí)操指南(golang)

          共 11420字,需瀏覽 23分鐘

           ·

          2021-02-05 09:33

          1 RPC(Remote Procedure Call Protocol)

          1.1 什么是RPC

          RPC即遠(yuǎn)程調(diào)用協(xié)議,簡(jiǎn)單來(lái)說就是調(diào)用遠(yuǎn)程的函數(shù)。

          正常單機(jī)開發(fā)的情況下,我們通過函數(shù)的方式實(shí)現(xiàn)部分功能的解耦

          func sum(num1,num2 int) int {
          return num1 + num2
          }

          如上是一個(gè)最簡(jiǎn)單的求和函數(shù),我們只需要調(diào)用函數(shù)就可以實(shí)現(xiàn)求和的功能。

          但大部分時(shí)候函數(shù)不會(huì)這么簡(jiǎn)單,尤其對(duì)于非單機(jī)的分布式系統(tǒng),遠(yuǎn)程調(diào)用就尤為重要。

          1.2 RPC業(yè)務(wù)場(chǎng)景

          RPC的應(yīng)用場(chǎng)景很廣泛:

          ?所有的分布式機(jī)都需要進(jìn)行登陸的驗(yàn)證,對(duì)于所有的主機(jī)都實(shí)現(xiàn)相同的登陸驗(yàn)證邏輯維護(hù)極差,同時(shí)也失去部分分布式意義,所以從解耦的角度考慮,我們需要定義一個(gè)統(tǒng)一的登陸驗(yàn)證業(yè)務(wù)來(lái)做。?C/S架構(gòu)的傳輸業(yè)務(wù),如股票軟件,每天需要用戶登陸的時(shí)候去服務(wù)器拉取最新的數(shù)據(jù),或者較簡(jiǎn)單的文件傳輸業(yè)務(wù),登陸驗(yàn)證業(yè)務(wù),證書業(yè)務(wù)都可以使用rpc的方式?跨語(yǔ)言開發(fā)的項(xiàng)目,比如web業(yè)務(wù)使用golang進(jìn)行開發(fā),底層使用cpp或c,部分腳本使用py,跨語(yǔ)言通信可以通過RPC提供的不同語(yǔ)言的開發(fā)機(jī)制進(jìn)行實(shí)現(xiàn)。

          因而實(shí)際上,RPC就是一個(gè)遠(yuǎn)程的函數(shù),只不過RPC協(xié)議做的就是把整個(gè)過程透明化,以使得從開發(fā)角度來(lái)看,和本地函數(shù)調(diào)用沒有區(qū)別。

          1.3 主流RPC框架

          目前主流的RPC,有ali的Dubbo,還有g(shù)oogle的gRPC(本文主題)等

          一般RPC框架如下所示:

          ?客戶端:客戶端作為整個(gè)RPC業(yè)務(wù)的發(fā)起者,如上所說的股票軟件,需要客戶端主動(dòng)發(fā)起請(qǐng)求去拉取最新的股票數(shù)據(jù)。?服務(wù)端:服務(wù)端接受客戶端的請(qǐng)求,并做出相應(yīng)的回應(yīng)。簡(jiǎn)單來(lái)說,函數(shù)實(shí)體在服務(wù)端,數(shù)據(jù)處理在服務(wù)端。

          服務(wù)端和客戶端是每個(gè)RPC框架,開發(fā)者可見度最高的部分,實(shí)現(xiàn)RPC業(yè)務(wù)的重點(diǎn)就在于對(duì)C/S的設(shè)計(jì)和理解。首先,客戶端一定是率先發(fā)起請(qǐng)求的部分,服務(wù)端一定是具體處理請(qǐng)求的部分。比如之前我們說的求和函數(shù),函數(shù)主體一定是在服務(wù)端,客戶端有兩個(gè)數(shù)字num1,num2,向服務(wù)端發(fā)起RPC遠(yuǎn)程調(diào)用,并最后拿到求和結(jié)果。

          分清C/S很重要!!!!!

          ?客戶端stub,服務(wù)端stub,可以變相的理解為應(yīng)用層。主要是對(duì)客戶端的rpc調(diào)用和服務(wù)端的返回進(jìn)行序列化和反序列化,并進(jìn)行傳輸,即把rpc業(yè)務(wù)抽象成tcp socket的send和receive。(gRPC使用的就是tcp,http2.0協(xié)議,建立在傳輸層)

          2 gRPC

          2.1 什么是gRPC

          gRPC是google的開源RPC框架,引用官網(wǎng)的一句話

          A high-performance, open-source universal RPC framework

          如圖,展示了gRPC跨語(yǔ)言開發(fā)的結(jié)構(gòu)圖,本文將描述golang使用grpc的過程。

          嚴(yán)格來(lái)說,grpc通過tcp進(jìn)行通信,使用http2.0協(xié)議,同時(shí)使用protobuf定義接口,因而相對(duì)于傳統(tǒng)的restful api來(lái)說,速度更快,數(shù)據(jù)更小,接口要求更嚴(yán)謹(jǐn)。(protobuf此處不做詳細(xì)介紹,Google Protobuf[1]

          2.2 四種gRPC服務(wù)類型

          準(zhǔn)確來(lái)說不應(yīng)稱為四種,實(shí)際上是因?yàn)閞pc入?yún)⒑统鰠⒍伎蓪?shí)現(xiàn)流式或非流式,進(jìn)而排列組合形成四種常用的gRPC模式。

          ?簡(jiǎn)單RPC

          即客戶端發(fā)起一次請(qǐng)求,服務(wù)端進(jìn)行響應(yīng)(類似restful api)。這種模式下,rpc調(diào)用和本地函數(shù)基本相同,常常用于登陸驗(yàn)證,握手協(xié)議,簡(jiǎn)單業(yè)務(wù)等。

          ?????客戶端流RPC

          即客戶端流式發(fā)送請(qǐng)求,有序發(fā)送很多req包(如文件流上傳),server接收到所有的req包后會(huì)檢測(cè)到EOF,回發(fā)一個(gè)res并關(guān)閉連接。比如云計(jì)算應(yīng)用,客戶端傳輸眾多基礎(chǔ)數(shù)據(jù),等待服務(wù)端計(jì)算完成并返回結(jié)果。

          ?????服務(wù)端流RPC

          即客戶端發(fā)起一次請(qǐng)求,服務(wù)端會(huì)發(fā)很多res包(如文件流下載),server發(fā)送完成后關(guān)閉連接。常用于數(shù)據(jù)的拉取,如請(qǐng)求大量數(shù)據(jù),無(wú)法及時(shí)進(jìn)行反饋,進(jìn)而通過流式進(jìn)行反饋。

          ?????雙端流RPC

          即雙方對(duì)話,可以實(shí)現(xiàn)一問一答,一問多答,多問一答等,常用于聊天室等及時(shí)通訊業(yè)務(wù)。



          3 gRPC實(shí)操

          3.1 環(huán)境配置

          3.1.1 首先使用go get獲取grpc的官方軟件包

           go get google.golang.org/grpc

          3.1.2 下載protobuf編譯器

          protobuf代碼生成工具[2],通過proto文件生成對(duì)應(yīng)的代碼。

          (此處需要加入環(huán)境變量,各個(gè)系統(tǒng)操作不同,不贅述,protoc命令能夠正常使用即可)

          3.1.3 安裝golang編譯插件

          我們需要.proto最終生成可用的golang代碼,因而需要獨(dú)立安裝golang grpc的插件

          go get -u github.com/golang/protobuf/protoc-gen-go

          3.2 編寫proto文件

          protobuf的詳細(xì)語(yǔ)法見官方文檔,此處主要介紹rpc相關(guān)的內(nèi)容

          proto中rpc業(yè)務(wù)實(shí)際上就是一個(gè)函數(shù),由服務(wù)端重寫(overwrite)的函數(shù),一般網(wǎng)上的文章會(huì)把gRPC分為四種:簡(jiǎn)單RPC,服務(wù)端流RPC,客戶端流RPC,雙端流RPC。實(shí)際上區(qū)別就在于rpc函數(shù)的入?yún)⒑统鰠ⅲ酉聛?lái)詳細(xì)介紹一下四種情況,和一般的應(yīng)用場(chǎng)景。

          3.2.1 簡(jiǎn)單RPC

          //指定使用proto3(proto2,3有很多不同,不可混寫)
          syntax = "proto3";
          //指定生成的go_package,簡(jiǎn)單來(lái)說就是生成的go代碼使用什么包,即package proto
          option go_package = ".;proto";

          //定義rpc服務(wù)
          //此處rpc服務(wù)的定義,一定要從服務(wù)端的角度考慮,即接受請(qǐng)求,處理請(qǐng)求并返回響應(yīng)的一端
          //請(qǐng)求接受一個(gè)LoginReq(username+password)
          //響應(yīng)回發(fā)一條msg("true" or "false")
          service Login{
          rpc Login(LoginReq)returns(LoginRes){}
          }

          message LoginReq {
          string username = 1;
          string password = 2;
          }

          message LoginRes {
          string msg = 1;
          }

          以上就是一個(gè)簡(jiǎn)單的RPC業(yè)務(wù),功能是進(jìn)行登陸驗(yàn)證。

          但實(shí)際上業(yè)務(wù)不會(huì)這么簡(jiǎn)單,比如請(qǐng)求或者響應(yīng)體特別大,肯定不能封裝到一個(gè)protobuf包進(jìn)行傳輸,因而需要使用流式傳輸,如請(qǐng)求視頻資源,或者上傳文件等,此時(shí)就引出了兩種單向流類型,即客戶端流和服務(wù)端流。

          3.2.2 客戶端流RPC

          簡(jiǎn)單來(lái)說,就是客戶端請(qǐng)求是個(gè)流,其他和簡(jiǎn)單RPC類似。

          syntax = "proto3";
          option go_package = ".;proto";

          //下載服務(wù)
          //請(qǐng)求接受一個(gè)UploadReq(username+password)
          //響應(yīng)回發(fā)多條數(shù)據(jù)("true" or "false")
          service Upload{
          rpc Upload(stream UploadReq)returns(UploadRes){}
          }

          message UploadReq {
          string path = 1;
          int64 offset = 2;
          int64 size = 3;
          bytes data = 4;
          }

          message UploadRes {
          string msg = 1;
          }

          這里展示的應(yīng)用場(chǎng)景為上傳文件,即客戶端指定文件路徑,數(shù)據(jù)偏移量和大小,以及傳輸?shù)亩M(jìn)制數(shù)據(jù),打包通過protobuf發(fā)送給服務(wù)端,服務(wù)端不停接受req并寫文件,最終寫完之后給客戶端一個(gè)反饋res。

          RPC的流指的是客戶端流式發(fā)送數(shù)據(jù),本質(zhì)上是分塊寫的思想。即每個(gè)數(shù)據(jù)包指定路徑,偏移和寫入大小,同時(shí)包含數(shù)據(jù)內(nèi)容,每次寫一個(gè)固定大小的塊(如2M),流式指的是流式發(fā)送很多個(gè)塊,如1G為512個(gè)2M的塊。

          3.2.3 服務(wù)端流RPC

          同上~

          syntax = "proto3";
          option go_package = ".;proto";

          //下載服務(wù)
          //請(qǐng)求接受一個(gè)DownloadReq(username+password)
          //響應(yīng)回發(fā)多條數(shù)據(jù)("true" or "false")
          service Download{
          rpc Download(DownloadReq)returns(stream DownloadRes){}
          }

          message DownloadReq {
          string path = 1;
          int64 offset = 2;
          int64 size = 3;
          }

          message DownloadRes {
          int64 offset = 1;
          int64 size = 2;
          bytes data = 3;
          }

          理解了客戶端流,服務(wù)端流也一樣的道理,客戶端發(fā)送一個(gè)請(qǐng)求,服務(wù)端不停的發(fā)送響應(yīng),直到全部發(fā)送完成。

          上述代碼的場(chǎng)景即為下載文件,發(fā)送一次請(qǐng)求,請(qǐng)求讀取某個(gè)路徑下的文件,比如讀取6M大小,從2M的位置開始讀,響應(yīng)即分為三個(gè)塊,分別包含2-4,4-6,6-8的數(shù)據(jù)(塊大小可以定制,僅以2M舉例)。

          3.2.4 雙端流RPC

          雙端流RPC就是入?yún)ⅲ鰠⒔詾榱鳌R话愕膽?yīng)用場(chǎng)景,如聊天室,聊天室需要維持一個(gè)長(zhǎng)鏈接,連接過程中雙方進(jìn)行通信,都是流式的信息,類似應(yīng)用場(chǎng)景使用雙端流式的RPC。

          綜上,其實(shí)分類的四種RPC本質(zhì)上只是RPC函數(shù)在入?yún)⒑统鰠⑸嫌幸恍┎煌举|(zhì)上沒有太大區(qū)別。但go中具體每個(gè)rpc業(yè)務(wù)的復(fù)寫,針對(duì)流式和非流式處理不同,下面會(huì)詳細(xì)描述,golang中如何實(shí)現(xiàn)除雙端流之外的三種RPC(雙端流同理)。

          3.3 生成go rpc代碼

          編寫完proto文件就可以通過proto去生成對(duì)應(yīng)的go語(yǔ)言代碼了~

           protoc --go_out=plugins=grpc:. *.proto

          protoc為編譯器的命令,指定使用插件為grpc,輸出目錄為.(grpc:.)當(dāng)前目錄,待編譯文件為*.proto。此處可以指定某個(gè)文件編譯,也可以指定輸出目錄,這條命令會(huì)編譯當(dāng)前目錄下的所有proto文件并生成到當(dāng)前目錄。

          以login為例子,生成的pb.go,rpc的核心就在Client和Server的兩個(gè)interface中

          Client interface

          // LoginClient is the client API for Login service.
          //
          // For semantics around ctx use and closing/ending streaming RPCs, please refer to https://godoc.org/google.golang.org/grpc#ClientConn.NewStream.
          type LoginClient interface {
          Login(ctx context.Context, in *LoginReq, opts ...grpc.CallOption) (*LoginRes, error)
          }

          Server interface

          // LoginServer is the server API for Login service.
          type LoginServer interface {
          Login(context.Context, *LoginReq) (*LoginRes, error)
          }

          客戶端調(diào)用Client interface的方法,服務(wù)端重寫Server interface的方法

          一定要理解上述這句話!!!!!

          例如這個(gè)列出服務(wù)器目錄的rpc方法,客戶端只需要?jiǎng)?chuàng)建客戶端實(shí)例對(duì)象,然后調(diào)用這個(gè)方法就可以,傳入req,接受res。因而我們說,對(duì)于客戶端來(lái)說,此次調(diào)用和本地函數(shù)沒有區(qū)別,但實(shí)際上是gRPC實(shí)現(xiàn)的遠(yuǎn)程調(diào)用,對(duì)于客戶端開發(fā)是不可見的。

          再說服務(wù)端,服務(wù)端需要重寫Server中的方法,即服務(wù)端需要實(shí)現(xiàn)Server接口,對(duì)req進(jìn)行處理,并生成res,同時(shí)提供ctx上下文用作并發(fā)處理。

          綜上!!!!客戶端是這個(gè)函數(shù)的調(diào)用者,需要調(diào)用這個(gè)函數(shù),服務(wù)端是這個(gè)函數(shù)的定義者,需要重寫這個(gè)函數(shù)

          3.4 服務(wù)端

          下述代碼皆可從我的github庫(kù)中獲得源碼grpc-example[3]

          3.4.1 重寫Server interface

          3.4.1.1 簡(jiǎn)單RPC

          package main

          import (
          "context"
          "grpcExample/simple_rpc/proto"
          )

          type LoginServer struct {}

          //判斷用戶名,密碼是否為root,123456,驗(yàn)證正確即返回
          func (*LoginServer)Login(ctx context.Context, req *proto.LoginReq) (*proto.LoginRes, error) {
          //為降低復(fù)雜度,此處不對(duì)ctx進(jìn)行處理
          if req.Username == "root" && req.Password == "123456" {
          return &proto.LoginRes{Msg: "true"},nil
          } else {
          return &proto.LoginRes{Msg: "false"},nil
          }
          }

          此處的login函數(shù)即為server端重寫的server interface的login函數(shù),目的是處理req,生成res并返回。整個(gè)rpc業(yè)務(wù)的核心就在于服務(wù)端重寫的方法,此處驗(yàn)證用戶名和密碼并返回提示信息。(僅用于grpc演示,忽略網(wǎng)絡(luò)安全相關(guān)內(nèi)容)

          3.4.1.2 客戶端流RPC

          package main

          import (
          "grpcExample/client_stream_rpc/proto"
          "io"
          "log"
          )

          type UploadServer struct{}

          func (*UploadServer) Upload(uploadServer proto.Upload_UploadServer) error {
          for {
          //循環(huán)接受客戶端傳的流數(shù)據(jù)
          recv, err := uploadServer.Recv()
          //檢測(cè)到EOF(客戶端調(diào)用close)
          if err == io.EOF {
          //發(fā)送res
          err := uploadServer.SendAndClose(&proto.UploadRes{Msg: "finish"})
          if err != nil {
          return err
          }
          return nil
          } else if err != nil{
          return err
          }
          log.Printf("get a upload data package~ offset:%v, size:%v\n",recv.Offset,recv.Size)
          }
          }

          客戶端流式的rpc的入?yún)⑹且粋€(gè)server對(duì)象,可以通過這個(gè)server對(duì)象調(diào)用Recv函數(shù)獲取客戶端發(fā)送的每一個(gè)流。此處如果客戶端關(guān)閉連接,服務(wù)端會(huì)收到一個(gè)io.EOF的error,因而此處需要對(duì)err進(jìn)行判斷處理,如果客戶端方傳輸完成關(guān)閉連接等待響應(yīng),服務(wù)端檢測(cè)到EOF,應(yīng)調(diào)用SendAndClose發(fā)送res響應(yīng)信息并關(guān)閉連接,進(jìn)而完成客戶端流的傳輸。

          3.4.1.3 服務(wù)端流RPC

          package main

          import (
          "grpcExample/server_stream_rpc/proto"
          "log"
          )

          type DownloadServer struct{}

          func (*DownloadServer) Download(req *proto.DownloadReq, downloadServer proto.Download_DownloadServer) error {
          offset := req.Offset
          //循環(huán)發(fā)送數(shù)據(jù)
          for {
          err := downloadServer.Send(&proto.DownloadRes{
          Offset: offset,
          Size: 4 * 1024,
          Data: nil,
          })
          if err != nil {
          return err
          }
          offset += 4 * 1024
          if offset >= req.Offset + req.Size {
          break
          }
          }
          return nil
          }

          3.4.2 注冊(cè)服務(wù)

          func main() {
          lis, err := net.Listen("tcp", ":6012")
          if err != nil {
          log.Fatalf("failed to listen: %v", err)
          }

          //構(gòu)建一個(gè)新的服務(wù)端對(duì)象
          s := grpc.NewServer()
          //向這個(gè)服務(wù)端對(duì)象注冊(cè)服務(wù)
          proto.RegisterDownloadServer(s,&DownloadServer{})
          //注冊(cè)服務(wù)端反射服務(wù)
          reflection.Register(s)

          //啟動(dòng)服務(wù)
          s.Serve(lis)

          //可配合ctx實(shí)現(xiàn)服務(wù)端的動(dòng)態(tài)終止
          //s.Stop()
          }

          實(shí)際使用中,可以將這部分獨(dú)立為一個(gè)模塊,通過ctx控制server的啟動(dòng)和停止,進(jìn)而靈活的控制grpc服務(wù)。

          3.5 客戶端

          3.5.1 調(diào)用Client func

          3.5.1.1 簡(jiǎn)單RPC

          package main

          import (
          "context"
          "google.golang.org/grpc"
          "grpcExample/simple_rpc/proto"
          "log"
          "time"
          )

          func main() {
          //創(chuàng)立grpc連接
          grpcConn, err := grpc.Dial("127.0.0.1"+":6012", grpc.WithInsecure())
          if err != nil {
          log.Fatalln(err)
          }

          //通過grpc連接創(chuàng)建一個(gè)客戶端實(shí)例對(duì)象
          client := proto.NewLoginClient(grpcConn)

          //設(shè)置ctx超時(shí)(根據(jù)情況設(shè)定)
          ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second)
          defer cancel()

          //通過client客戶端對(duì)象,調(diào)用Login函數(shù)
          res, err := client.Login(ctx, &proto.LoginReq{
          Username: "root",
          Password: "123456",
          })
          if err != nil {
          log.Fatalln(err)
          }

          //輸出登陸結(jié)果
          log.Println("the login answer is", res.Msg)
          }

          所以,客戶端只需要維持一個(gè)實(shí)例化的client對(duì)象,通過client調(diào)用方法就可以使用RPC服務(wù),注意和服務(wù)端不同的是,每個(gè)服務(wù)都需要一個(gè)客戶端,即服務(wù)端是在一個(gè)對(duì)象上注冊(cè)很多個(gè)服務(wù),而客戶端調(diào)用每個(gè)RPC業(yè)務(wù)都需要一個(gè)對(duì)應(yīng)函數(shù)的Client對(duì)象。

          3.5.1.2 客戶端流RPC

          package main

          import (
          "context"
          "google.golang.org/grpc"
          "grpcExample/client_stream_rpc/proto"
          "log"
          "time"
          )

          func main(){
          //創(chuàng)立grpc連接
          grpcConn, err := grpc.Dial("127.0.0.1"+":6012", grpc.WithInsecure())
          if err != nil {
          log.Fatalln(err)
          }

          //通過grpc連接創(chuàng)建一個(gè)客戶端實(shí)例對(duì)象
          client := proto.NewUploadClient(grpcConn)

          //設(shè)置ctx超時(shí)(根據(jù)情況設(shè)定)
          ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second)
          defer cancel()

          //和簡(jiǎn)單rpc不同,此時(shí)獲得的不是res,而是一個(gè)client的對(duì)象,通過這個(gè)連接對(duì)象去發(fā)送數(shù)據(jù)
          uploadClient,err := client.Upload(ctx)
          if err != nil {
          log.Fatalln(err)
          }

          var offset int64
          var size int64
          size = 4 * 1024

          //循環(huán)處理數(shù)據(jù),當(dāng)大于64kb退出
          for {
          err := uploadClient.Send(&proto.UploadReq{
          Path: "../test.txt",
          Offset: offset,
          Size: size,
          Data: nil,
          })
          if err != nil {
          log.Fatalln(err)
          }
          offset += size
          //發(fā)送超過64KB,調(diào)用CloseAndRecv方法接收response
          if offset >= 64 * 1024 {
          res, err := uploadClient.CloseAndRecv()
          if err != nil {
          log.Fatalln(err)
          }
          log.Println("upload over~, response is ",res.Msg)
          break
          }
          }
          }

          客戶端流在調(diào)用函數(shù)的時(shí)候獲得的不是單純的res對(duì)象,而是一個(gè)client對(duì)象,通過這個(gè)對(duì)象控制流的發(fā)送,并且在發(fā)送完成后主動(dòng)調(diào)用CloseAndRecv去關(guān)閉連接并接受服務(wù)端的返回res。

          3.5.1.3 服務(wù)端流RPC

          package main

          import (
          "context"
          "google.golang.org/grpc"
          "grpcExample/server_stream_rpc/proto"
          "log"
          "time"
          )

          func main(){
          //創(chuàng)立grpc連接
          grpcConn, err := grpc.Dial("127.0.0.1"+":6012", grpc.WithInsecure())
          if err != nil {
          log.Fatalln(err)
          }

          //通過grpc連接創(chuàng)建一個(gè)客戶端實(shí)例對(duì)象
          client := proto.NewDownloadClient(grpcConn)

          //設(shè)置ctx超時(shí)(根據(jù)情況設(shè)定)
          ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second)
          defer cancel()

          //和簡(jiǎn)單rpc不同,此時(shí)獲得的不是res,而是一個(gè)client的對(duì)象,通過這個(gè)連接對(duì)象去讀取數(shù)據(jù)
          downloadClient,err := client.Download(ctx,&proto.DownloadReq{
          Path: "../test.txt",
          Offset: 0,
          Size: 64 * 1024,
          })
          if err != nil {
          log.Fatalln(err)
          }

          //循環(huán)處理數(shù)據(jù),當(dāng)監(jiān)測(cè)到讀取完成后退出
          for {
          res, err := downloadClient.Recv()
          if err != nil {
          log.Fatalln(err)
          }
          log.Printf("get a date package~ offset:%v, size:%v\n",res.Offset,res.Size)
          if res.Size + res.Offset >= 64 * 1024 {
          break
          }
          }

          log.Println("download over~")
          }

          此處獲取的也是一個(gè)讀取數(shù)據(jù)需要的對(duì)象,即客戶端發(fā)送請(qǐng)求后得到該對(duì)象,通過該對(duì)象調(diào)用Recv來(lái)讀取服務(wù)端流式發(fā)送的數(shù)據(jù)。

          4 寫在最后

          建議先理解grpc的C/S架構(gòu)

          建議閱讀:

          ?Go gRPC教程[4]?gRPC-go example[5]

          github(vx):cjq99419 歡迎提問和批評(píng)指正!

          References

          [1]?Google Protobuf:?https://developers.google.com/protocol-buffers
          [2]?protobuf代碼生成工具:?https://github.com/protocolbuffers/protobuf/releases
          [3]?grpc-example:?https://github.com/cjq99419/grpc-example
          [4]?Go gRPC教程:?https://studygolang.com/articles/28205
          [5]?gRPC-go example:?https://github.com/grpc/grpc-go/tree/master/examples



          推薦閱讀


          福利

          我為大家整理了一份從入門到進(jìn)階的Go學(xué)習(xí)資料禮包,包含學(xué)習(xí)建議:入門看什么,進(jìn)階看什么。關(guān)注公眾號(hào) 「polarisxu」,回復(fù)?ebook?獲取;還可以回復(fù)「進(jìn)群」,和數(shù)萬(wàn) Gopher 交流學(xué)習(xí)。

          瀏覽 55
          點(diǎn)贊
          評(píng)論
          收藏
          分享

          手機(jī)掃一掃分享

          分享
          舉報(bào)
          評(píng)論
          圖片
          表情
          推薦
          點(diǎn)贊
          評(píng)論
          收藏
          分享

          手機(jī)掃一掃分享

          分享
          舉報(bào)
          <kbd id="afajh"><form id="afajh"></form></kbd>
          <strong id="afajh"><dl id="afajh"></dl></strong>
            <del id="afajh"><form id="afajh"></form></del>
                1. <th id="afajh"><progress id="afajh"></progress></th>
                  <b id="afajh"><abbr id="afajh"></abbr></b>
                  <th id="afajh"><progress id="afajh"></progress></th>
                  夜夜嗨免费视频 | 国产成人免费视频在线 | 特黄aaaaaaaa真人毛片 | 日韩无码第三页 | 四虎8848精品成人免费网站 |