go grpc流式和非流式的例子


參考grpc官方:  https://grpc.io/docs/quickstart/go.html

或官方中文翻譯: http://doc.oschina.net/grpc?t=60133

 

安裝proto buf 3、protoc編譯器

1-安裝grpc(需要紅杏出牆): 

# 因為本朝網絡的安全原因,需要提前export http_proxy https_proxy 來紅杏出牆到golang.org。 比如執行linux命令 export http{,s}_proxy=紅杏出牆IP:紅杏出牆端口號
執行:go get -u google.golang.org/grpc

2-安裝

  下載protoc:

           從 https://github.com/google/protobuf/releases下載預編譯的“protoc編譯器”,用於生成gRPC服務代碼。(文件名為:protoc-<版本>-<平台>.zip)

          下載后,解壓zip文件,並將protoc二進制文件所在bin目錄添加到PATH環境變量中

   安裝protoc的go插件:

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

  go get -u github.com/golang/protobuf  //解決go build報錯:” undefined: proto.ProtoPackageIsVersion3 “ 。高版本protoc搭配低版本protobuf就會出現這樣的問題

        將$GOPATH/bin加入到PATH環境變量中

 

非流式RPC: 

1-使用proto buf 3的IDL(接口定義語言)語法編寫 .proto 文件.  關於.proto文件的語法規則參考本博protocol buffer的整理

我的.proto文件路徑是:$GOPATH/src/nonstream/non_stream.proto  

 1 syntax = "proto3"; //必須指定,否則就認為是proto2版本
 2 
 3 package nonstream;  //包名,生成go代碼的時候,protoc會用到這個包名,生成的.pb.go文件中有package nonstream,后面的服務端、客戶端go源文件時需要導入該包名
 4 
 5 //定義服務 ( 風格:服務名和rpc方法名是大駝峰風格 )
 6 service EchoIface {
 7      //自己的GO源碼的類需要實現接口Echo函數 
 8      rpc Echo (Ping) returns (Pong) {} 
 9 }
10 
11 // 定義消息 (風格: 消息名用大駝峰風格)
12 message Ping {
13   string request = 1;   //(風格: 字段名用小寫下划線風格,如果是枚舉字段名則是大寫下划線風格)
14 }
15 
16 message Pong {
17   string reply = 1;
18

2- 使用protoc生成go代碼:

protoc命令的語法: protoc -I /PATH/TO/.proto_DIR/   /PATH/TO/TARGET.proto --go_out=plugins=grpc:/PATH/TO/OUTPUT_DIR  #不指定-I表示從當前工作目錄尋找.proto文件。-I一般是指向項目根目錄。
#本例編譯.proto文件使用下面的命令(需要cd到.proto文件所在目錄,再執行)
protoc non_stream.proto --go_out=plugins=grpc:. #執行完這條命令后,就可以在當前目錄下看到non_stream.pb.go源碼文件了。 命令中:后面的.表示當前目錄,加入要生成代碼到/tmp目錄,則使用選項--go-out=plugins=grpc:/tmp
# 題外話: --go-out 選項中有無plugins=grpc的區別:
1
protoc --go_out=. ./helloworld.proto // -–go_out=后直接跟路徑(點號表示當前路徑)。只生成序列化/反序列化代碼文件,不需要rpc通訊。 2 protoc --go_out=plugins=grpc:. ./helloworld.proto // --go_out=后跟了grpc插件和目錄。生成序列化反序列化代碼 和 客戶端/服務端通訊代碼。

 

3-編寫非流式服務端go代碼:

 1 package main
 2 
 3 import (
 4     "context"
 5     "log"
 6     "net"
 7     "strings"
 8 
 9     pb "nonstream"  //剛才.proto文件定義的包名
10     "google.golang.org/grpc"
11 )
12 
13 type server struct{}
14 
15 /*server結構體需要實現EchoIface接口的Echo方法, 注意這里的參數多了context,返回值多了error。這可以查看protoc生成的.pb.go文件就發現有下面的定義:
16 type EchoIfaceServer interface {
17     Echo(context.Context, *Ping) (*Pong, error)
18 }
19 */
20 func (s *server) Echo(ctx context.Context, in *pb.Ping) (*pb.Pong, error) {
21     log.Printf("Received from client: %s", in.Request)
22     u := strings.ToUpper(in.Request)  //注意.proto文件中字段是小寫下划線,這里變成了大駝峰了
23     return &pb.Pong{Reply: u + "..." + in.Request + "..."}, nil
24 }
25 
26 func main() {
27     lis, err := net.Listen("tcp", ":5555") //1.指定監聽地址:端口號
28     if err != nil {
29         log.Fatalf("failed to listen: %v", err)
30     }
31 
32     s := grpc.NewServer() //2.新建gRPC實例
33     pb.RegisterEchoIfaceServer(s, &server{})  //3.在gRPC服務器注冊我們的服務實現。參數2是接口(滿足服務定義的方法)。在.pb.go文件中搜索Register關鍵字即可找到這個函數簽名
34     if err := s.Serve(lis); err != nil {   //4.Serve()阻塞等待
35         log.Fatalf("failed to serve: %v", err)
36     }
37 }

 

4-編寫非流式客戶端go代碼:

 

 1 package main
 2 
 3 import (
 4     "context"
 5     "log"
 6     "time"
 7     "fmt"
 8 
 9     pb "nonstream"
10     "google.golang.org/grpc"
11 )
12 
13 
14 func main() {
15     //1.建立連接
16     conn, err := grpc.Dial("127.0.0.1:5555", grpc.WithInsecure())  //如果需要授權認證或tls加密,則可以使用DialOptions來設置grpc.Dial
17     if err != nil {
18         log.Fatalf("grpc.Dial: %v", err)
19     }
20     defer conn.Close()
21 
22     c := pb.NewEchoIfaceClient(conn) //2.新建一個客戶端stub來執行rpc方法
23 
24     ctx, cancel := context.WithTimeout(context.Background(), time.Second)  //超時1秒執行cancel
25     defer cancel()
26 
27     r, err := c.Echo(ctx, &pb.Ping{Request: "yahoo"})  //3.調用rpc方法
28     if err != nil {
29         log.Fatalf("c.Echo: %v", err)
30     }
31 
32     fmt.Printf("echo from server: %s\n", r.Reply)
33 }

 

 

---------------------------------- 分割線 ------------------------------

單向流式RPC -- 服務流式響應

流式的好處之一是可以實現異步

1- 編寫.proto文件:

 1 // 這個文件的路徑: $GOPATH/src/stream/stream.proto 。包名是stream,后面的服務端/客戶端代碼會導入這個包
 2 syntax = "proto3";
 3 
 4 package stream;
 5 
 6 service EchoIface {
 7       rpc Echo(Ping) returns (stream Pong) {}  //server-side-stream
 8 }
 9 
10 message Ping {
11       string request = 1;
12 }
13 
14 message Pong {
15       string reply = 1;
16 }

2-使用protoc生成go代碼(此處省略,具體參考上面protoc的例子)

3-服務端代碼:

package main

import (
    "log"
    "net"
    "strconv"

    "google.golang.org/grpc"
    pb "stream"
)

type server struct{}

/* 從.pb.go文件中,我們發現了下面的定義:
      type EchoIfaceServer interface {
           Echo(*Ping, EchoIface_EchoServer) error
      }
   而參數EchoIface_EchoServer的定義為(有Send函數):
      type EchoIface_EchoServer interface {
          Send(*Pong) error
          grpc.ServerStream
   }
*/
func (s *server) Echo(in *pb.Ping, es pb.EchoIface_EchoServer) error {
    n := in.Request
    for i := 0; i < 10; i++ {  //發10次Hello
        es.Send(&pb.Pong{Reply: "Hello " + n + ":" + strconv.Itoa(i)})
    }
    return nil
}

func main() {
    conn, err := net.Listen("tcp", ":6666")
    if err != nil {
        log.Fatalf("net.Listen: %v", err)
    }
    svr := grpc.NewServer()

    pb.RegisterEchoIfaceServer(svr, &server{})
    if err := svr.Serve(conn); err != nil {
        log.Fatalf("s.Serve(): %v", err)
    }
}

4-客戶端代碼:

 1 package main
 2 
 3 import (
 4     "context"
 5     "io"
 6     "log"
 7 
 8     "google.golang.org/grpc"
 9     pb "stream"
10 )
11 
12 func main() {
13     conn, err := grpc.Dial("127.0.0.1:6666", grpc.WithInsecure())
14     if err != nil {
15         log.Fatalf("grpc.Dial: %v", err)
16     }
17     defer conn.Close()
18 
19     c := pb.NewEchoIfaceClient(conn)
20 
21     /* 從protoc生成的.pb.go文件中,我們發現如下定義:
22                type EchoIfaceClient interface {
23                       Echo(ctx context.Context, in *Ping, opts ...grpc.CallOption) (EchoIface_EchoClient, error)
24                }
25                而上面接口函數的第一個返回值在.pb.go文件中的的定義是:
26                        type EchoIface_EchoClient interface {
27                          Recv() (*Pong, error)
28                         grpc.ClientStream
29                    }
30     */
31     stream, err := c.Echo(context.Background(), &pb.Ping{Request: "google"})
32     if err != nil {
33         log.Fatalf("c.Echo: %v", err)
34     }
35 
36     for {
37         pong, err := stream.Recv()
38         if err == io.EOF {
39             break
40         }
41 
42         if err != nil {
43             log.Printf("stream.Recv: %v", err)
44         }
45 
46         log.Printf("echo from server: %s", pong.Reply)
47     }
48 
49 }

 

雙向流式RPC,客戶端流式RPC

無非就是:

    1-編寫.proto文件的服務定義時,在需要變成流式的參數或返回值前加stream修飾。

    2-對.pb.go文件執行正則搜索,找到函數/方法的定義。再根據定義寫go代碼

 但是要注意一些差異(細節代碼參考):

     1-客戶端流式RPC (客戶端一次或多次發數據到服務端,服務端響應一次):

               客戶端: 使用 流 的Send()方法 發送請求,發送完后使用 流 的CloseAndRecv方法讓gRPC服務端知道客戶端已經完成請求並且期望獲得一個響應。如果CloseAndRecv()返回的err不為nil,那么返回的第一個值就是一個有效的服務端響應。

              服務端:  使用 流 的Recv() 方法接收客戶端消息,並且用 流 的SendAndClose() 方法返回它的單個響應。

     2-雙向流式RPC中(客戶端:等待接收,並同時發送一次或多次數據到服務端,最后一次發送結束標識--CloseSend()方法。 服務端:來一個,處理一個,響應一個,不用等待客戶端發送完成才處理。):

             客戶端:一個goroutine用來執行 流的 Recv()方法 等待服務端發來的流式響應(阻塞等待狀態)。另一個goroutine用來將一次或多次請求通過流的Send()方法發送到服務端,發完后,使用CloseSend()結束發送。

             服務端: 服務端用 流 的Recv()方法接收客戶端的請求流,接收一個、處理一個、發送一個響應,直到出錯,或因為客戶端CloseSend()導致的服務端接收完請求。

更多細節,結合源代碼https://github.com/grpc/grpc-go/blob/master/examples/route_guide和官方文檔中譯版看

 

不需要入參結構體或返回結構體的情況:

方法1:  可以在.proto文件中定義空的消息(也就是空結構體,這樣做的好處是以后可以給結構體添加字段, 接口函數簽名不變).          

message MyRespone {
}

務必注意,在源代碼編寫的時候,不能直接直接返回空指針, 否則會報"rpc error: code = Internal desc = grpc: error while marshaling: proto: Marshal called with nil".  意思大概就是不能序列化nil指針

if err != nil {
       return nil, err  //錯誤! 正確寫法 return &pb.MyRespone{},err
}

方法2: 使用protobuf的內置空類型.

.proto文件需要類似這樣定義(以上面非流式的例子做修改成不需要返回結構體為例子)

 syntax = "proto3"; 
 import "google/protobuf/empty.proto";  //這個就是protobuf內置的空類型
 package nonstream; 
 
 service EchoIface {
      rpc Echo (Ping) returns (google.protobuf.Empty) {} 
 }
 
 message Ping {
   string request = 1;
 }

源碼文件的編寫需要這樣(以上面非流式的例子做修改):

import "github.com/golang/protobuf/ptypes/empty"
 ...
 func (s *server) Echo(ctx context.Context, in *pb.Ping) (*empty.Empty, error)

 

 

 

           

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM