【GoLang 那點事】實踐 gRPC 之 GoLang 入門 HelloWord(三)

a_wei發表於2019-08-26
  • 這篇文章裡我們要實現一個基於GoLang程式語言的gRPC的客戶端與服務端通訊的HelloWorld案例

編寫hello_world.proto檔案,如下程式碼:

syntax proto3

package proto

//介面請求入參
message HelloRequest{
    string request = 1;
}
//介面返回出參
message HelloResponse{
    string response = 1;
}
//定義介面
service HelloService{
    //一個簡單的rpc
    rpc HelloWorld(HelloRequest) returns (HelloResponse){}
    //一個伺服器端流式rpc
    rpc HelloWorldServerStream(HelloRequest) returns (stream HelloResponse){}
    //一個客戶端流式rpc
    rpc HelloWorldClientStream(stream HelloRequest) returns (HelloResponse){}
    //一個客戶端和伺服器端雙向流式rpc
    rpc HelloWorldClientAndServerStream(stream HelloRequest) 
                       returns (stream HelloResponse){}
}
  • 如上程式碼,通過protobuffer的service定義一個介面HelloService,介面中有四個方法都以HelloWorld開頭,入參是HelloRequest,出參是HelloResponse,通過最前面的rpc關鍵字標識為這是一個rpc介面

編譯hello_world.proto檔案生成對應的go檔案

go_common
    grpc
        helloworld_new
            client //存放客戶端程式碼
            proto 存放proto檔案和生成的go檔案
            server存放server檔案
  • cd go_common/grpc/helloworld_new/proto,進入proto資料夾下
  • 執行命令:protoc --go_out=plugins=grpc:. hello_world.proto
  • 最後生成hello_world.pb.go檔案,主要有以下幾部分組成:
    1. 方法出入參結構體以及序列化和反序列方法
    2. 註冊出入參結構體的init方法
    3. 客戶端存根結構體和介面以及實現
    4. 服務端結構體和介面以及一個空實現
    5. stream的send和recv結構體和介面以及實現
    6. 服務的一些描述
  • 程式碼太多,這裡粘一些核心程式碼,並且簡化了一些出入參,完成程式碼參考github上的原始碼
//包名
package proto
//介面請求入參
type HelloRequest struct {
    Request    string 
}
//介面返回出參
type HelloResponse struct {
    Response     string   
}
//生成的helloServce的客戶端介面,就跟java的api是的,讓別人引用
type HelloServiceClient interface {
    //一個簡單的
    rpc HelloWorld(in *HelloRequest) (*HelloResponse, error)
    //一個伺服器端流式
    rpc HelloWorldServerStream(in *HelloRequest) (ServerStream,error)
    //一個客戶端流式
    rpc HelloWorldClientStream() (ClientStream, error)
    //一個客戶端和伺服器端雙向流式
    rpc HelloWorldClientAndServerStream() (ServerClientStream error)
}
//helloServiceClient結構體
type helloServiceClient struct {
    cc *grpc.ClientConn
}
//獲取客戶端存根,入參是一個client連線
func NewHelloServiceClient(cc *grpc.ClientConn) HelloServiceClient {        
    return &helloServiceClient{cc}
}

//這是伺服器端的介面,我們的服務端需要實現這些介面
type HelloServiceServer interface {
    //一個簡單的
    rpc HelloWorld(in *HelloRequest) (*HelloResponse, error)
    //一個伺服器端流式
    rpc HelloWorldServerStream(*HelloRequest, StreamServer) error
    //一個客戶端流式
    rpc HelloWorldClientStream(ClientStream) error
    //一個客戶端和伺服器端雙向流式
    rpc HelloWorldClientAndServerStream (ClientServerStream) error
}

編寫服務端程式碼

  • 在server資料夾下新建hello_world_server.go檔案,按如下步驟進行
    1. 建立HelloWorldServer結構體
    2. 實現pb.go檔案中的HelloServiceServer介面(實現所有方法)
    3. StartServer(開啟服務)
  • 詳細程式碼依然看github
type HelloServiceServer struct {}

//簡單rpc實現
func (*HelloServiceServer) HelloWorld() {
    log.Printf("%v",req.Request)
    return &pb.HelloResponse{Response:"hello my is gRpcServer"}, nil
}
//服務端流式rpc實現
func (*HelloServiceServer) HelloWorldServerStream() error {
    log.Printf("%v",req.Request)
    srv.Send(&pb.HelloResponse{Response:"hello my is gRpcServer stream"})
    return nil
}
//客戶端流式rpc實現
func (*HelloServiceServer) HelloWorldClientStream() error {
    for{
       //不斷接受客戶端傳送的訊息
        req,err := srv.Recv()
        //直到傳送結束終止for迴圈
        if err != nil && err.Error() == "EOF"{
            break
        }
        if err != nil{
            log.Fatalf("%v",err)
            break
        }else{
            log.Printf("%v",req.Request)
        }
    }
    //返回給客戶端處理結果
    srv.SendAndClose(&pb.HelloResponse{Response:"hello my is gRpcServer"})
    return nil
}
//雙向流式rpc
func (*HelloServiceServer) HelloWorldClientAndServerStream(){
    for{
        //接受客戶端訊息
        req,err := srv.Recv()
        if err != nil && err.Error() == "EOF"{
            break
        }
        if err != nil{
            log.Fatalf("%v",err)
            break
        }else{
            log.Printf("%v",req.Request)
           //返回客戶端結果 
            srv.Send(&pb.HelloResponse{Response:"hello my is gRpcServer stream"})
        }
    }
    return nil
}
func StartServer(){
    //開啟服務端監聽
    lis, err := net.Listen("tcp", "127.0.0.1:8090")
    if err != nil {
        log.Fatalf("failed to listen: %v", err)
    }
    //建立grpcServer
    gRpcServer := grpc.NewServer()
    //註冊服務實現
    pb.RegisterHelloServiceServer(gRpcServer, &HelloServiceServer{})
    //啟動服務
    gRpcServer.Serve(lis)
}

編寫客戶端

  • 在client目錄下建立hello_world_client.go檔案
func StartClient(){
    //連線grpc的服務端
    conn, err := grpc.Dial("127.0.0.1:8090",grpc.WithInsecure())
    //建立客戶端存根物件
    c := pb.NewHelloServiceClient(conn)
    // Contact the server and print out its response.
    //設定超時時間
    ctx, cancel := context.WithTimeout(context.Background(), time.Second)
    defer cancel()
    //呼叫服務端的方法
    r, err := c.HelloWorldClientAndServerStream(ctx,grpc.EmptyCallOption{})
    if err != nil{
        log.Fatalf("%v", err)
        return
    }
    //客戶端往服務端傳送10次訊息
    for i:=0;i<10;i++{
        r.Send(&pb.HelloRequest{Request:"my is golang gRpc client"})
    }
    //傳送完畢
    r.CloseSend()
    //迴圈接受服務端返回的結果,直到返回EOF
    for{
        res,err := r.Recv()
        if err != nil && err.Error() == "EOF"{
            break
        }
        if err != nil {
            log.Fatalf("%v", err)
            break
        }
        log.Printf("result:%v",res.Response)
    }
    //關閉連線
    defer conn.Close()
}

github程式碼執行說明

  • github程式碼截圖
  • 先啟動 hello_world_server_test.go
  • 再啟動hello_world_client_test.go
  • 如遇到啟動的任何問題可留言

【GoLang那點事】實踐gRPC之GoLang入門HelloWord

歡迎大家關注微信公眾號:“golang那點事”,更多精彩期待你的到來

【GoLang那點事】實踐gRPC之GoLang入門HelloWord

那小子阿偉

相關文章