gRPC是Google的RPC框架,开源、高性能、跨语言,基于HTTP/2通讯协议和Protocol Buffer 3数据序列化协议。
过程如下图所示:
调用的双方可以使用完全不同的两种语言来实现,分别实现client端和server端,按照约定的protobuf协议进行交互。client端会保存与server端的长连接对象或叫存根,通过这个存根可以直接调用服务端的方法。而服务端则实现了proto中指定的服务接口。
proto的安装
1、protoc-gen-go下载:
- go get github.com/golang/protobuf/protoc-gen-go
会直接生成protoc-gen-go 二进制文件到GOPATH的bin目录。
2、protoc下载:https://github.com/google/protobuf/releases
找到对应平台win/linux/mac的下载包,复制二进制文件protoc到GOPATH的bin目录。
定义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目录不在环境变量中,需要设置。
安装grpc包
- go get google.golang.org/grpc
实现grpc的server端
1、接口实现,即对外提供的服务API。
- 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)
- }
- go run main.go //或指定-host "localhost:9001"
实现grpc的client端
- 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","")
- // 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)
- }
启动client端:
- go run main.go