RPC及gRPC入门体验

本文介绍了gRPC的基本概念和特性,包括它作为一个高性能、跨语言的RPC框架,以及其基于HTTP/2和ProtoBuf的设计。通过一个简单的RPC服务端和客户端示例,阐述了RPC的工作原理。接着,通过gRPC的helloworld例子,展示了如何定义服务、生成代码以及运行服务端和客户端。最后,详细说明了如何实践gRPC,包括定义Arith服务、编译.proto文件以及实现服务端和客户端的代码。
摘要由CSDN通过智能技术生成

Table of Contents

gRPC简介

gRPC是一个高性能、通用的开源RPC框架,其由Google主要面向移动应用开发并基于HTTP/2协议标准而设计,基于ProtoBuf(Protocol Buffers)序列化协议开发,且支持众多开发语言。 gRPC提供了一种简单的方法来精确地定义服务和为iOS、Android和后台支持服务自动生成可靠性很强的客户端功能库。 客户端充分利用高级流和链接功能,从而有助于节省带宽、降低TCP连接次数、节省CPU使用和电池寿命。

grpc

RPC(Remote Procedure Call,远程过程调用)是一种通过网络从远程计算机程序上请求服务,而不需要了解底层网络细节的应用程序通信协议。RPC协议构建于TCP或UDP,或者是HTTP上。允许开发者直接调用另一台服务器上的程序,而开发者无需另外的为这个调用过程编写网络通信相关代码,使得开发网络分布式程序在内的应用程序更加容易

RPC采用客户端-服务器端的工作模式,请求程序就是一个客户端,而服务提供程序就是一个服务器端。当执行一个远程过程调用时,客户端程序首先先发送一个带有参数的调用信息到服务端,然后等待服务端响应。在服务端,服务进程保持睡眠状态直到客户端的调用信息到达。当一个调用信息到达时,服务端获得进程参数,计算出结果,并向客户端发送应答信息。然后等待下一个调用。

gRPC特性

  • 服务定义简单
  • 跨语言和跨平台
  • 启动快,伸缩性好
  • 支持服务端和客户端双向流

RPC实践

在认识gRPC之前,我们先来看看一个简单的RPC服务端和客户端具体是怎样的。 下面以Go语言的net/rpc包为例实现一个提供乘法和除法运算服务的RPC服务端和RPC客户端。

服务端上的RPC服务可简单理解为一组可被客户端调用的方法,这些方法的形式必须如下:

func (t *T) MethodName(argType T1, replyType *T2) error
  • 该方法所属的类型T必须是导出的(即类型名首字母大写)
  • 方法是导出的(即方法名首字母大写)
  • 方法带有两个参数,T1和T2(T2必须是指针类型,用于写入该方法的返回结果),且T1和T2都是导出类型或内建类型
  • 方法返回类型为error

服务端实现:

package main

import (
    "net"
    "net/http"
    "net/rpc"
    "log"
    "errors"
)

type Args struct {
   
    A, B int
}

type Quotient struct {
   
    Quo, Rem int
}

type Arith int

func (t *Arith) Multiply(args *Args, reply *int) error {
   
    *reply = args.A * args.B
    return nil
}

func (t *Arith) Divide(args *Args, quo *Quotient) error {
   
    if args.B == 0 {
   
        return errors.New("divide by zero")
    }
    quo.Quo = args.A / args.B
    quo.Rem = args.A % args.B
    return nil
}


func main() {
   
    arith := new(Arith)
    rpc.Register(arith)
    rpc.HandleHTTP()
    l, e := net.Listen("tcp", ":2345")
    if e != nil {
   
        log.Fatal("listen error:", e)
    }
    log.Println("rpcserver listening at 127.0.0.1:2345")
    http.Serve(l, nil)
}

上面的代码主要有两部分:

服务的定义和实现:

Arith将作为服务名,该服务包含两个方法可被远程调用,Multiply和Divide。这两个函数都遵循rpc包要求的形式,通过结构体来传送1个或多个函数的参数,然后将函数调用的结果写入用于存放响应内容的结构体中。

服务的监听和处理:

  • rpc.Register方法注册Arith的一个实例,使得Arith服务对外可调用
  • HandleHTTP方法用于注册一个HTTP handler来处理RPC消息
  • net.Listen设置使用的协议及端口,并返回一个Listener对象l用于监听新建立的连接
  • http.Serve启动一个http server处理Listener对象l接收到的连接

客户端实现:

package main

import (
    "fmt"
    "net/rpc"
    "log"
)

type Args struct {
   
    A, B int
}

type Quotient struct {
   
    Quo, Rem int
}


var serverAddress string = "127.0.0.1"

func main() {
   
    client, err := rpc.DialHTTP("tcp", serverAddress + ":2345")
    if err != nil {
   
        log.Fatal("dialing:", err)
    }
    // Synchronous call
    args := &Args{
   9,4}
    var reply int
    err = client.Call("Arith.Multiply", args, &reply)
    if err != nil {
   
        log.Fatal("arith error:", err)
    }
    fmt.Printf("Arith: %d * %d=%d\n", args.A, args.B, reply)
    var quotient Quotient
    err = client.Call("Arith.Divide", args, &quotient)
    if err != nil {
   
        log.Fatal("arith error:", err)
    }
    fmt.Printf("Arith: %d / %d = %d remains %d\n", args.A, args.B, quotient.Quo, quotient.Rem)
}

客户端的代码主要有两部分:

定义和服务端Arith服务相同或相似的数据结构:

客户端定义了一样的结构体Args和Quotient,用于调用服务端Arith服务的Multiply方法和Divide方法。事实上,客户端Args和Quotient的定义也不一定要完全一致,但不能有冲突。

例如,以下定义也是可以的

type Args struct {
   
    A, B, C int
}

type Args struct {
   
    A, B *int
}

但以下定义都是不可以的

type Args struct {
   
    A int; B float
}

type Args struct {
   
    C, D int
}

连接服务端及服务调用:

  • 通过rpc.DialHTTP与服务端建立连接并获取Client对象
  • 通过client.Call调用Arith服务的方法,Call方法第一个参数的形式为“服务名.方法名”,服务名和方法名都必须和服务端的定义一致

RPC客户端只要遵循服务端的服务定义的格式就可以像调用本地方法一样调用远程的服务。

代码运行:

上述代码已放在github仓库,可直接下载运行:

$ mkdir -p $GOPATH/src/github.com/linshk
$ cd $GOPATH/src/github.com/linshk
$ git clone https://github.com/linshk/grpc-learning
$ cd grpc-learning
# 以后台方式运行服务端
$ go run rpcserver/main.go &
$ go run rpcclient/main.go
# 客户端输出:
# Arith: 9 * 4=36
# Arith: 9 / 4 = 2 remains 1

gRPC helloworld

进入examples目录查看示例项目

$ cd $GOPATH/src/google.golang.org/grpc/examples/helloworld

helloword目录结构:

  • helloworld
    • helloworld
      • helloworld.pb.go(protoc-gen-go根据helloworld.proto的服务定义自动生成的服务端客户端代码)
      • helloworld.proto (Greeter服务(gRPC服务)的定义)
    • greeter_client
      • main.go(借助helloworld.pb.go构建的gRPC客户端)
    • greeter_server
      • main.go(借助helloworld.pb.go构建的gRPC服务端)
    • mock_helloworld
      • hw_mock.go(MockGen自动生成的mock服务器代码)
      • hw_mock_test.go

首先查看helloworld.proto

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

前面我们手动构建RPC服务端代码时需要先定义服务的类型(Arith),函数参数及计算结果的类型(Args为参数的类型,Int为Multiply的结果类型,Quotient为Divide方法的结果类型),服务可供调用的方法(Multiply和Quotient),而Greeter服务的定义也是如此:

  • 服务的定义:
// The greeting service definition.
service Greeter {
  // Sends a greeting
  rpc SayHello (HelloRequest) returns (HelloReply) {}
}

服务名为Greeter,服务可供调用的方法为SayHello,且SayHello方法的形式与前面的Multiply和Divide有所不同,SayHello返回的返回值即是方法的调用结果。

  • 参数类型及结果类型的定义:
// The request message containing the user's name.
message HelloRequest {
  string name = 1;
}

// The response message containing the greetings
message HelloReply {
  string message = 1;
}

接着再看由该服务定义自动生成的代码helloworld.pb.go

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

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 _
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值