grpc 入门(一)–hello world

 

  • 一,从rpc接口的定义说起,下面给一个最简单的grpc示例–hello world

 

在这个rpc横行的世界里,实现一个rpc很重要的一件事就是定义一个好接口,一个好的接口定义会让你省去很多麻烦。熟悉protobuf的人应该知道它所用的结构体都是用.proto文件来描述的:

// Copyright 2015 gRPC authors.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//     http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

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
  //这是一个单例模式,就是一次请求一次应答,即为1:1
//下面我们会介绍 带有stream的rpc接口,N:N
  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;
}

我们按照编写规则定义好rpc后,使用
protoc工具按照对应语言版本生成代码包,我们是grpc要在go语言中应用,自然要编译成golang格式:

#go:generate 
protoc -I ../helloworld --go_out=plugins=grpc:../helloworld ../helloworld/helloworld.proto

上面的接口看起来寥寥数行,但是生成出来的代码很长–helloworld.proto.go:

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} }

func (m *HelloRequest) GetName() string {
    if m != nil {
        return m.Name
    }
    return ""
}

// 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 (m *HelloReply) GetMessage() string {
    if m != nil {
        return m.Message
    }
    return ""
}

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{
    // 175 bytes of a gzipped FileDescriptorProto
    0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 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, 0x14, 0xe4, 0x54, 0x2a, 0x31, 0x38, 0x19, 0x70, 0x49, 0x67, 0xe6,
    0xeb, 0xa5, 0x17, 0x15, 0x24, 0xeb, 0xa5, 0x56, 0x24, 0xe6, 0x16, 0xe4, 0xa4, 0x16, 0x23, 0xa9,
    0x75, 0xe2, 0x07, 0x2b, 0x0e, 0x07, 0xb1, 0x03, 0x40, 0x5e, 0x0a, 0x60, 0x4c, 0x62, 0x03, 0xfb,
    0xcd, 0x18, 0x10, 0x00, 0x00, 0xff, 0xff, 0x0f, 0xb7, 0xcd, 0xf2, 0xef, 0x00, 0x00, 0x00,
}

这时用protoc工具自动生成,你可以去修改,但是关键性的接口一定要吻合,而且它自动生成的代码效率较高,没有特殊情况下无需要修改。而且我们仔细观察可以发现它生成接口自动分为server和client两种模式,我们先分析一下服务端。

  • 服务端解析

其中server端比较关键的一个函数旧时注册服务的函数 RegisterGreeterServer,我们已经将它标红了,这个函数就是我们在main函数中调用注册服务实例的函数。

// Server API for Greeter service

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

这个接口就是我们需要实现的接口,这个不难理解,就像是c++里的callback函数一样,我们现在看看这个注册函数:

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

除了我们自己实现的接口之外,还有一个ServiceDesc:

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",
}

里面包含了注册服务的名字(MethodName)和操作的Handler:

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)
}

在里面它会有调用到我们实现的接口 SayHello,简单的包含关系如下:

grpc.Server---
            |--grpc.ServiceDesc---
                                |--Handler
                                |--Customer Interface
  • 客户端解析

我们分析完了服务端再分析一下客户端,客户端很简单,只有一个发送函数:

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
}

发送后自动返回Reply,这个我们就不深入解读了,下面说一下我们生成的包怎么用?

  • 服务端实现

    //go:generate protoc -I ../helloworld –go_out=plugins=grpc:../helloworld ../helloworld/helloworld.proto

    package main

    import (

    "log"
    "net"
    
    "golang.org/x/net/context"
    "google.golang.org/grpc"
    pb "google.golang.org/grpc/examples/helloworld/helloworld"
    "google.golang.org/grpc/reflection"
    

    )

    const (

    port = ":50051"
    

    )

    // server is used to implement helloworld.GreeterServer.
    type server struct{}

    // SayHello implements helloworld.GreeterServer
    func (s server) SayHello(ctx context.Context, in pb.HelloRequest) (*pb.HelloReply, error) {

    return &pb.HelloReply{Message: "Hello " + in.Name}, nil
    

    }

    func main() {
      #创建一个net.Listener对象,指定协议和端口号

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

      #创建一个空白的grpc server

    s := grpc.NewServer()
    

    永利网址,  #注册服务对应的实例

    pb.RegisterGreeterServer(s, &server{})
    // Register reflection service on gRPC server.
    reflection.Register(s)
    

      #启动grpc服务

    if err := s.Serve(lis); err != nil {
        log.Fatalf("failed to serve: %v", err)
    }
    

    }

这段代码里面的grpc.NewServer没有填入任何的ServerOption,可以说是最简单的方式了。

 

  • 客户端实现

    package main

    import (

    "log"
    "os"
    
    "golang.org/x/net/context"
    "google.golang.org/grpc"
    pb "google.golang.org/grpc/examples/helloworld/helloworld"
    

    )

    const (

    address     = "localhost:50051"
    defaultName = "world"
    

    )

    func main() {

    // Set up a connection to the server.
    conn, err := grpc.Dial(address, grpc.WithInsecure())
    if err != nil {
        log.Fatalf("did not connect: %v", err)
    }
    defer conn.Close()
    c := pb.NewGreeterClient(conn)
    
    // Contact the server and print out its response.
    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.Fatalf("could not greet: %v", err)
    }
    log.Printf("Greeting: %s", r.Message)
    

    }

 

工程的下载地址:https://github.com/grpc/grpc-go/tree/master/examples

 下附一些grpc api的简介:

package credentials

import "google.golang.org/grpc/credentials"

Package credentials implements various credentials supported by gRPC
library, which encapsulate all the state needed by a client to
authenticate with a server and make various assertions, e.g., about the
client’s identity, role, or whether it is authorized to make a
particular call.

数据包凭证(Package
credentials)实现了gRPC库所支持的各种证书,它为了便与服务器进行认证,封装了客户端所需的所有状态,并作出各种断言(例如关于客户端的身份,角色或者是否被授权进行特定的呼叫)。

func Creds

func Creds(c credentials.TransportCredentials) ServerOption

Creds returns a ServerOption that sets credentials for server
connections.

Creds函数返回一个ServerOption, 用于设置服务器链接证书

func WithTransportCredentials

func WithTransportCredentials(creds credentials.TransportCredentials) DialOption

WithTransportCredentials returns a DialOption which configures a
connection level security credentials (e.g., TLS/SSL).

WithTransportCredentials函数返回一个
DialOption,用于配置一个传输层的安全证书

func NewServer

func NewServer(opt ...ServerOption) *Server

NewServer creates a gRPC server which has no service registered and has
not started to accept requests yet.

NewServer 创建一个没有注册service,也没有启动去接收请求的gRpc服务。

如果没有输入任何ServerOption那它就是一个没有任何权限限制的gRpc.

func (*Server) Serve

func (s *Server) Serve(lis net.Listener) error

Serve accepts incoming connections on the listener lis, creating a new
ServerTransport and service goroutine for each. The service goroutines
read gRPC requests and then call the registered handlers to reply to
them. Serve returns when lis.Accept fails with fatal errors. lis will be
closed when this method returns. Serve will return a non-nil error
unless Stop or GracefulStop is called.

Serve接受監聽器上的傳入連接,為每個服務器創建一個新的ServerTransport和服務配置。服務例程讀取gRPC請求,然後調用註冊的處理程序來回复它們。服務返回lis.Accept失敗,致命錯誤。當此方法返回時,lis將被關閉。
Serve將返回一個非零錯誤,除非Stop或GracefulStop被調用。

func (*Server) RegisterService

func (s *Server) RegisterService(sd *ServiceDesc, ss interface{})

RegisterService registers a service and its implementation to the gRPC
server. It is called from the IDL generated code. This must be called
before invoking Serve.

RegisterService將服務及其實現註冊到gRPC服務器。它是從IDL生成的代碼中調用的。這必須在調用Serve之前調用。

 

func Dial

func Dial(target string, opts ...DialOption) (*ClientConn, error)

Dial creates a client connection to the given target.