Golang gRPC 示例


典型的配合使用場景是,寫好 .proto 描述文件定義 RPC 的接口,然后用 protoc(帶 gRPC 插件)基於 .proto 模板自動生成客戶端和服務端的接口代碼。

ProtoBuf

需要工具主要包括:

  • 編譯器:protoc,以及一些官方沒有帶的語言插件;
  • 運行環境:各種語言的 protobuf 庫,不同語言有不同的安裝來源;

語法類似 C++ 語言,可以參考 語言規范

比較核心的,message 是代表數據結構(里面可以包括不同類型的成員變量,包括字符串、數字、數組、字典……),service代表 RPC 接口。變量后面的數字是代表進行二進制編碼時候的提示信息,1~15 表示熱變量,會用較少的字節來編碼。另外,支持導入。

默認所有變量都是可選的(optional),repeated 則表示數組。主要 service rpc 接口只能接受單個 message 參數,返回單個 message;

syntax = "proto3"; package hello; message HelloRequest { string greeting = 1; } message HelloResponse { string reply = 1; repeated int32 number=4; } service HelloService { rpc SayHello(HelloRequest) returns (HelloResponse){} } 

編譯最關鍵參數是指定輸出語言格式,例如,python 為 --python_out=OUT_DIR

一些還沒有官方支持的語言,可以通過安裝 protoc 對應的 plugin 來支持。例如,對於 Go 語言,可以安裝

$ go get -u github.com/golang/protobuf/{protoc-gen-go,proto} // 前者是 plugin;后者是 go 的依賴庫 

之后,正常使用 protoc --go_out=./ hello.proto 來生成 hello.pb.go,會自動調用 protoc-gen-go 插件。

ProtoBuf 提供了 Marshal/Unmarshal 方法來將數據結構進行序列化操作。所生成的二進制文件在存儲效率上比 XML 高 3~10 倍,並且處理性能高 1~2 個數量級。

gRPC

工具主要包括:

  • 運行時庫:各種不同語言有不同的 安裝方法,主流語言的包管理器都已支持。
  • protoc,以及 grpc 插件和其它插件:采用 ProtoBuf 作為 IDL 時,對 .proto 文件進行編譯處理。

官方文檔 寫的挺全面了。

類似其它 RPC 框架,gRPC 的庫在服務端提供一個 gRPC Server,客戶端的庫是 gRPC Stub。典型的場景是客戶端發送請求,同步或異步調用服務端的接口。客戶端和服務端之間的通信協議是基於 HTTP2 的 gRPC 協議,支持雙工的流式保序消息,性能比較好,同時也很輕。

采用 ProtoBuf 作為 IDL,則需要定義 service 類型。生成客戶端和服務端代碼。用戶自行實現服務端代碼中的調用接口,並且利用客戶端代碼來發起請求到服務端。一個完整的例子可以參考 這里

以上面 proto 文件為例,需要執行時添加 grpc 的 plugin:

$ protoc --go_out=plugins=grpc:. hello.proto

 

 

 

 

1、安裝gRPC runtime

 

go get google.golang.org/grpc

  


  

為了自動生成Golang的gRPC代碼,需要安裝protocal buffers compiler以及對應的GoLang插件

 

2、protocal buffer安裝

從https://github.com/google/protobuf/releases下載安裝包,例如:protobuf-cpp-3.0.0-beta-3.zip,解壓后

configure
make && make install

  


  

再添加環境變量:export LD_LIBRARY_PATH=/usr/local/lib,之后protoc命令即可運行

 

3、安裝GoLang protoc 插件

 

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

  


  

4、定義service, 參考為github上的源碼example:https://github.com/grpc/grpc-go/tree/master/examples/helloworld

一個RPC service就是一個能夠通過參數和返回值進行遠程調用的method,我們可以簡單地將它理解成一個函數。因為gRPC是通過將數據編碼成protocal buffer來實現傳輸的。因此,我們通過protocal buffers interface definitioin language(IDL)來定義service method,同時將參數和返回值也定義成protocal buffer message類型。具體實現如下所示,包含下面代碼的文件叫helloworld.proto

 

// Copyright 2015, Google Inc.
// All rights reserved.
//
// Redistribution and use in source and binary forms, with or without
// modification, are permitted provided that the following conditions are
// met:
//
//     * Redistributions of source code must retain the above copyright
// notice, this list of conditions and the following disclaimer.
//     * Redistributions in binary form must reproduce the above
// copyright notice, this list of conditions and the following disclaimer
// in the documentation and/or other materials provided with the
// distribution.
//     * Neither the name of Google Inc. nor the names of its
// contributors may be used to endorse or promote products derived from
// this software without specific prior written permission.
//
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.

syntax = "proto3";

option java_multiple_files = true;
option java_package = "io.grpc.examples.helloworld";
option java_outer_classname = "HelloWorldProto";

package helloworld;

// The greeting service definition.
service Greeter {
  // Sends a greeting
  rpc SayHello (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;
}

  

 

 

 

5、接着,根據上述定義的service,我們可以利用protocal buffer compiler ,即protoc生成相應的服務器端和客戶端的GoLang代碼。生成的代碼中包含了客戶端能夠進行RPC的方法以及服務器端需要進行實現的接口。

假設現在所在的目錄是$GOPATH/src/helloworld/helloworld,我們將通過如下命令生成gRPC對應的GoLang代碼:

 如果不想生成go語言形式的,可以把go_out換成別的

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

  


  

此時,將在目錄下生成helloworld.pb.go文件。

該文件應該為:

// Code generated by protoc-gen-go.
// source: helloworld.proto
// DO NOT EDIT!

/*
Package helloworld is a generated protocol buffer package.
It is generated from these files:
	helloworld.proto
It has these top-level messages:
	HelloRequest
	HelloReply
*/
package helloworld

import proto "github.com/golang/protobuf/proto"
import fmt "fmt"
import math "math"

import (
	context "golang.org/x/net/context"
	grpc "google.golang.org/grpc"
)

// Reference imports to suppress errors if they are not otherwise used.
var _ = proto.Marshal
var _ = fmt.Errorf
var _ = math.Inf

// This is a compile-time assertion to ensure that this generated file
// is compatible with the proto package it is being compiled against.
// A compilation error at this line likely means your copy of the
// proto package needs to be updated.
const _ = proto.ProtoPackageIsVersion2 // please upgrade the proto package

// The request message containing the user's name.
type HelloRequest struct {
	Name string `protobuf:"bytes,1,opt,name=name" json:"name,omitempty"`
}

func (m *HelloRequest) Reset()                    { *m = HelloRequest{} }
func (m *HelloRequest) String() string            { return proto.CompactTextString(m) }
func (*HelloRequest) ProtoMessage()               {}
func (*HelloRequest) Descriptor() ([]byte, []int) { return fileDescriptor0, []int{0} }

// The response message containing the greetings
type HelloReply struct {
	Message string `protobuf:"bytes,1,opt,name=message" json:"message,omitempty"`
}

func (m *HelloReply) Reset()                    { *m = HelloReply{} }
func (m *HelloReply) String() string            { return proto.CompactTextString(m) }
func (*HelloReply) ProtoMessage()               {}
func (*HelloReply) Descriptor() ([]byte, []int) { return fileDescriptor0, []int{1} }

func init() {
	proto.RegisterType((*HelloRequest)(nil), "helloworld.HelloRequest")
	proto.RegisterType((*HelloReply)(nil), "helloworld.HelloReply")
}

// Reference imports to suppress errors if they are not otherwise used.
var _ context.Context
var _ grpc.ClientConn

// This is a compile-time assertion to ensure that this generated file
// is compatible with the grpc package it is being compiled against.
const _ = grpc.SupportPackageIsVersion4

// Client API for Greeter service

type GreeterClient interface {
	// Sends a greeting
	SayHello(ctx context.Context, in *HelloRequest, opts ...grpc.CallOption) (*HelloReply, error)
}

type greeterClient struct {
	cc *grpc.ClientConn
}

func NewGreeterClient(cc *grpc.ClientConn) GreeterClient {
	return &greeterClient{cc}
}

func (c *greeterClient) SayHello(ctx context.Context, in *HelloRequest, opts ...grpc.CallOption) (*HelloReply, error) {
	out := new(HelloReply)
	err := grpc.Invoke(ctx, "/helloworld.Greeter/SayHello", in, out, c.cc, opts...)
	if err != nil {
		return nil, err
	}
	return out, nil
}

// Server API for Greeter service

type GreeterServer interface {
	// Sends a greeting
	SayHello(context.Context, *HelloRequest) (*HelloReply, error)
}

func RegisterGreeterServer(s *grpc.Server, srv GreeterServer) {
	s.RegisterService(&_Greeter_serviceDesc, srv)
}

func _Greeter_SayHello_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {
	in := new(HelloRequest)
	if err := dec(in); err != nil {
		return nil, err
	}
	if interceptor == nil {
		return srv.(GreeterServer).SayHello(ctx, in)
	}
	info := &grpc.UnaryServerInfo{
		Server:     srv,
		FullMethod: "/helloworld.Greeter/SayHello",
	}
	handler := func(ctx context.Context, req interface{}) (interface{}, error) {
		return srv.(GreeterServer).SayHello(ctx, req.(*HelloRequest))
	}
	return interceptor(ctx, in, info, handler)
}

var _Greeter_serviceDesc = grpc.ServiceDesc{
	ServiceName: "helloworld.Greeter",
	HandlerType: (*GreeterServer)(nil),
	Methods: []grpc.MethodDesc{
		{
			MethodName: "SayHello",
			Handler:    _Greeter_SayHello_Handler,
		},
	},
	Streams:  []grpc.StreamDesc{},
	Metadata: "helloworld.proto",
}

func init() { proto.RegisterFile("helloworld.proto", fileDescriptor0) }

var fileDescriptor0 = []byte{
	// 174 bytes of a gzipped FileDescriptorProto
	0x1f, 0x8b, 0x08, 0x00, 0x00, 0x09, 0x6e, 0x88, 0x02, 0xff, 0xe2, 0x12, 0xc8, 0x48, 0xcd, 0xc9,
	0xc9, 0x2f, 0xcf, 0x2f, 0xca, 0x49, 0xd1, 0x2b, 0x28, 0xca, 0x2f, 0xc9, 0x17, 0xe2, 0x42, 0x88,
	0x28, 0x29, 0x71, 0xf1, 0x78, 0x80, 0x78, 0x41, 0xa9, 0x85, 0xa5, 0xa9, 0xc5, 0x25, 0x42, 0x42,
	0x5c, 0x2c, 0x79, 0x89, 0xb9, 0xa9, 0x12, 0x8c, 0x0a, 0x8c, 0x1a, 0x9c, 0x41, 0x60, 0xb6, 0x92,
	0x1a, 0x17, 0x17, 0x54, 0x4d, 0x41, 0x4e, 0xa5, 0x90, 0x04, 0x17, 0x7b, 0x6e, 0x6a, 0x71, 0x71,
	0x62, 0x3a, 0x4c, 0x11, 0x8c, 0x6b, 0xe4, 0xc9, 0xc5, 0xee, 0x5e, 0x94, 0x9a, 0x5a, 0x92, 0x5a,
	0x24, 0x64, 0xc7, 0xc5, 0x11, 0x9c, 0x58, 0x09, 0xd6, 0x25, 0x24, 0xa1, 0x87, 0xe4, 0x02, 0x64,
	0xcb, 0xa4, 0xc4, 0xb0, 0xc8, 0x00, 0xad, 0x50, 0x62, 0x70, 0x32, 0xe0, 0x92, 0xce, 0xcc, 0xd7,
	0x4b, 0x2f, 0x2a, 0x48, 0xd6, 0x4b, 0xad, 0x48, 0xcc, 0x2d, 0xc8, 0x49, 0x2d, 0x46, 0x52, 0xeb,
	0xc4, 0x0f, 0x56, 0x1c, 0x0e, 0x62, 0x07, 0x80, 0xbc, 0x14, 0xc0, 0x98, 0xc4, 0x06, 0xf6, 0x9b,
	0x31, 0x20, 0x00, 0x00, 0xff, 0xff, 0x0f, 0xb7, 0xcd, 0xf2, 0xef, 0x00, 0x00, 0x00,
}

  

 

6、接着,在目錄$GOPATH/src/helloworld/下創建server.go 和client.go,分別用於服務器和客戶端的實現。

 

package main
 
// server.go
 
import (
    "log"
    "net"
 
    "golang.org/x/net/context"
    "google.golang.org/grpc"
    pb "helloworld/helloworld"
)
 
const (
    port = ":50051"
)
 
type server struct {}
 
func (s *server) SayHello(ctx context.Context, in *pb.HelloRequest) (*pb.HelloReply, error) {
    return &pb.HelloReply{Message: "Hello " + in.Name}, nil
}
 
func main() {
    lis, err := net.Listen("tcp", port)
    if err != nil {
        log.Fatal("failed to listen: %v", err)
    }
    s := grpc.NewServer()
    pb.RegisterGreeterServer(s, &server{})
    s.Serve(lis)
}
  

  

  

  

package main
 
//client.go
 
import (
    "log"
    "os"
 
    "golang.org/x/net/context"
    "google.golang.org/grpc"
    pb "helloworld/helloworld"
)
 
const (
    address     = "localhost:50051"
    defaultName = "world"
)
 
func main() {
    conn, err := grpc.Dial(address, grpc.WithInsecure())
    if err != nil {
        log.Fatal("did not connect: %v", err)
    }
    defer conn.Close()
    c := pb.NewGreeterClient(conn)
 
    name := defaultName
    if len(os.Args) >1 {
        name = os.Args[1]
    }
    r, err := c.SayHello(context.Background(), &pb.HelloRequest{Name: name})
    if err != nil {
        log.Fatal("could not greet: %v", err)
    }
    log.Printf("Greeting: %s", r.Message)
}

  

這里需要注意的是包pb是我們之前生成的helloworld.pb.go所在的包,並非必須如上述代碼所示在$GOPATH/src/helloworld/helloworld目錄下。

 

7、最后運行如下代碼進行演示即可



go run server.go
go run client.go

  


  


免責聲明!

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



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