什么叫着rpc(Remote Procedure Call)?,字面意思远程过程调用,简单的理解是一个节点请求另一个节点提供的服务。

本地调用远程的接口整个过程就像调用本地接口一样。当然中间少不了客户端与服务端的通信,消息数据的序列化与反序列化。

 

gRPC是Google的RPC框架,开源、高性能、跨语言,基于HTTP/2通讯协议和Protocol Buffer 3数据序列化协议。

过程如下图所示:

grpc支持长链接吗 grpc长连接实现_grpc

调用的双方可以使用完全不同的两种语言来实现,分别实现client端和server端,按照约定的protobuf协议进行交互。client端会保存与server端的长连接对象或叫存根,通过这个存根可以直接调用服务端的方法。而服务端则实现了proto中指定的服务接口。

proto的安装

要编译proto文件生成go代码需要两个工具

protoc :用于编译(其他语言只需要protoc足以)
protoc-gen-go : 用于生成go语言的文件(go语言专用插件)

1、protoc-gen-go下载:

 

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

会直接生成protoc-gen-go 二进制文件到GOPATH的bin目录。 
2、protoc下载:https://github.com/google/protobuf/releases 
找到对应平台win/linux/mac的下载包,复制二进制文件protoc到GOPATH的bin目录。

go环境安装这里不再叙述,

grpc 引入包地址google.golang.org/grpc

举例如下proto文件

syntax = "proto3";
package protos;
// 要获取的数据结构
 message User{
     int32 id = 1;
     string name = 2;
 }// 请求数据结构
 message UserReq{
     int32 id = 1;
 }// 定义服务,关键字'service',方法关键字'rpc'
 service IUserService {
     // 单一请求应答,一对一
     rpc Get (UserReq) returns (User);
     // 服务端流式应答,一对多,可用于下载
     rpc GetList (UserReq) returns (stream User);
     // 客户端流式请求,多对一,可用于上传
     rpc WaitGet(stream UserReq) returns (User);
     // 双向流式请求应答,支持HTTP/2.0
     rpc LoopGet(stream UserReq) returns (stream User);
 }

编译proto生成go文件

这里要使用grpc插件选项:

protoc --go_out=plugins=grpc:. 路径/*.proto

会在同目录下生成.pb.go文件。如果不识别protoc,则说明GOPATH的bin目录不在环境变量中,需要设置。

 

接口实现 服务端

package service
import (
     "fmt"
     "io"
     "log"
     "protos"
     "strconv"    "golang.org/x/net/context"
 )//对外提供的工厂函数
 func NewUserService() *UserService {
     return &UserService{}
 }//**************************************************************
 // 接口实现,接口定义是在proto生成的.pb.go文件中
 //**************************************************************// 接口实现对象,属性成员根据而业务自定义
 type UserService struct {
 }// Get接口方法实现
 func (this *UserService) Get(ctx context.Context, req *protos.UserReq) (*protos.User, error) {
     return &protos.User{Id: 1, Name: "shuai"}, nil
 }// GetList接口方法实现
 func (this *UserService) GetList(req *protos.UserReq, stream protos.IUserService_GetListServer) error {
     fmt.Println(*req)
     // 流式返回多条数据
     for i := 0; i < 5; i++ {
         stream.Send(&protos.User{Id: int32(i), Name: "我是" + strconv.Itoa(i)})
     }
     return nil
 }// WaitGet接口方法实现
 func (this *UserService) WaitGet(reqStream protos.IUserService_WaitGetServer) error {
     for { // 接收流式请求并返回单一对象
         userReq, err := reqStream.Recv()
         if err != io.EOF {
             fmt.Println("流请求~", *userReq)
         } else {
             return reqStream.SendAndClose(&protos.User{Id: 100, Name: "shuai"})
         }
     }
 }//双向流:请求流和响应流异步
 func (this *UserService) LoopGet(reqStream protos.IUserService_LoopGetServer) error {
     for {
         userReq, err := reqStream.Recv()
         if err == io.EOF { //请求结束
             return nil
         }
         if err != nil {
             return err
         }
         if err = reqStream.Send(&protos.User{Id: userReq.Id, Name: "shuai"}); err != nil {
             return err
         }
     }
 }


2.启动服务

package main
import (
     "flag"
     "fmt"
     "net"
     "proj/service" // 实现了服务接口的包service
     "protos" // 此为自定义的protos包,存放的是.proto文件和对应的.pb.go文件    "google.golang.org/grpc"
 )var (
     // 命令行参数-host,默认服务监听端口在9000
     addr = flag.String("host", "127.0.0.1:9000", "")
 )func main() {
     // 开启服务监听
     lis, err := net.Listen("tcp", *addr)
     if err != nil {
         fmt.Println("listen error!")
         return
     }
     // 创建一个grpc服务
     grpcServer := grpc.NewServer()
     // 重点:向grpc服务中注册一个api服务,这里是UserService,处理相关请求
     protos.RegisterIUserServiceServer(grpcServer, service.NewUserService())
     // 可以添加多个api
     // TODO...    // 启动grpc服务
     grpcServer.Serve(lis)
 }

客户端

package main
import (
     "flag"
     "fmt"
     "io"
     "log"
     "protos" // 此为自定义的protos包,存放的是.proto文件和对应的.pb.go文件    "golang.org/x/net/context"
    "google.golang.org/grpc"
 )var (
     // 命令行参数-host,默认地址本机9000端口
     addr = flag.String("host", "127.0.0.1:9000", "")
     // UserService服务存根,可直接调用服务方法
     userCli protos.IUserServiceClient
 )func main() {
     // 创建grpc的服务连接
     conn, err := grpc.Dial(*addr)
     if err != nil {
         log.Fatal("failed to connect : ", err)
     }
     // 存根
     userCli = protos.NewIUserServiceClient(conn)    // 测试前三种数据传递方式,第四种省略(二三结合)
     TestGet()
     TestGetList()
     TestWaitGet()
 }func TestGet() {
     // 测试调用方法Get,返回user对象
     user, err := userCli.Get(context.Background(), &protos.UserReq{Id: 1})
     if err != nil {
         fmt.Printf("Get connect failed :%v", err)
         return
     }
     log.Println("Get响应数据:", *user)
 }func TestGetList() {
     // 测试调用方法GetList,返回一个Stream流,循环获取多个user对象
     recvStream, err := userCli.GetList(context.Background(), &protos.UserReq{Id: 1})
     if err != nil {
         fmt.Printf("GetList connect failed :%v", err)
         return
     }
     for {
         user, err := recvStream.Recv()
         if err == io.EOF {
             break
         }
         log.Println("GetList获取的一条响应数据:", *user)
     }
 }func TestWaitGet() {
     // 测试调用方法WaitGet,传入多条请求数据,返回一个user对象
     sendStream, err := userCli.WaitGet(context.Background())
     if err != nil {
         fmt.Printf("WaitGet connect failed :%v", err)
         return
     }
     for i := 0; i < 5; i++ { // 一次传入5条请求数据
         if err = sendStream.Send(&protos.UserReq{Id: int32(i)}); err != nil {
             fmt.Printf("WaitGet send failed :%v", err)
             return
         }
     }
     // 服务端接受全部请求数据后,返回一个user对象
     user, err := sendStream.CloseAndRecv()
     if err != nil {
         fmt.Printf("WaitGet recv failed :%v", err)
         return
     }
     log.Println("WaitGet响应数据:", *user)
 }