RPC与Protobuf(三)

 

Protobuf介绍

  Protobuf 是Protocol Buffers 的简称,它是谷歌公司开发的一种数据描述语言, 2008开源时定位类似于XML、JSON等描述语言,通过附带的工具生成代码并实现结构化数据的功能,但我们更关注的是Protobuf作为接口规范的描述语言,可以作为设计安全的跨语言RPC接口的基础工具。

 

Protobuf入门

  对于没有用过Protobuf的,建议先从官网了解基本用法,下面我们结合Protobuf和RPC结合一起使用,通过Protobuf来最终保证RPC的接口规范和安全。Protobuf中最基本的数据单元是message,是类似于Go语言中结构体的存在,在message中可以嵌套message或其他的基础数据类型成员。

 

简单实例

文件目录

---rpc_demo
│  client.go
│  go.mod
│  go.sum
│  service.go
│
└─proto
        hello.pb.go
        hello.proto

首先在项目目录下创建一个proto目录用于存放proto文件,创建简单的helllo.proto文件:

syntax = "proto3";  // 声明语法

package proto;  // 可根据自己当前包结构自定义

message String {   // message 关键字定义了一个 String类型,且只有一个字符串类型的value成员,该成员编号为1来代替名字,这就是为什么protobuf体积小的原因,别的数据描述语言(json、xml)都是通过成员名字标识,而Portobuf通过唯一编号,不过不便于查阅
    string value = 1;
}

进入到proto文件目录下,使用以下命令生成相应的语言脚本:protoc  --go_out=. hello.proto 生成相应的接口文件。然后我们可以根据生成的go文件,写服务端和客户端代码,先写出服务端代码。

注意:Protobuf核心代码是使用C++编写的,在官方的protobuf编译器中并不支持Go语言,要想基于hello.proto文件生成相应的go代码,我们需要下载相应的插件: go get github.com/golang/protobuf/protoc-gen-go 命令安装,最后通过protoc  --go_out=. hello.proto会生成相应的go文件代码

package main

import (
   "log"
   "net"
   "net/rpc"
   "rpc_protobuf/proto"
)

type HelloService struct {}

// 实现rpc服务的具体方法,注意接受的参数要满足接口的要求标准,因为目前调用的都是结构体,所以采用指针的参数
func (p *HelloService) Hello (request *proto.String, reply *proto.String) error {
   reply.Value = "hello: " + request.GetValue()
   return nil
}

func main() {
   // 注册rpc服务
   rpc.RegisterName("HelloService", new(HelloService))
   // 监听tcp服务
   listenr, err :=  net.Listen("tcp",":1234")
   if err != nil {
      log.Fatal("Listen error:",err)
   }
   log.Println("service starting....")
   // 有相关数据请求则接受,并将连挂在rpc服务上
   conn, err := listenr.Accept()
   if err != nil {
      log.Fatal("Accept error",err)
   }

   rpc.ServeConn(conn)
}

  

最后写出客户端代码:

package main

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

func main() {
   // 拨号本端1234端口服务
   client, err := rpc.Dial("tcp", "localhost:1234")
   if err != nil {
      log.Fatal("TCP error:", err)
   }

   // 统一采用接口提供的参数,和请求参数
   var reply = &proto.String{}
   var param = &proto.String{
      Value: "Wang",
   }

   // 调用远程服务
   err = client.Call("HelloService.Hello", &param, &reply)
   if err != nil {
      log.Fatal(err)
   }

   fmt.Println(reply)
}

  

(注:在使用Goland IDE的使用,如果我们使用了go mod,在setting中的Go modules选项中的enable Go Moudle(vgo) integration 和 vendoring mode 需要调整当导包出现问题时)

我们分别启动服务端、客户端就可以查看相应的结果了,这里我们简单的使用了rpc和protobuf,之后会介绍怎么下载protoc、protobuf等

分析:

下面我们分析下生成的hello.pb.go文件(截取部分):

package proto  // 包名
....

// 实现了一个字符类型,根据proto文件生成的
type String struct {
	Value                string   `protobuf:"bytes,1,opt,name=value,proto3" json:"value,omitempty"`
	XXX_NoUnkeyedLiteral struct{} `json:"-"`
	XXX_unrecognized     []byte   `json:"-"`
	XXX_sizecache        int32    `json:"-"`
}

....
// 用于给String类型取值的,在服务可以通过这个方法进行参数的获取,每个成员都会有Get方法
func (m *String) GetValue() string {
	if m != nil {
		return m.Value
	}
	return ""
}

  

真正的价值

上面我们已经讲解过通过复杂的操作来结合rpc和protobuf,我们使用protobuf去定义输入和输出参数以及实现的结构体,但是大家有没有发现,其实生成的hello.pb.go中是没有服务函数的只有一些接口约束,那我们能不能将RPC中的服务也定义在Protobuf中?这样我们就实现了用Protobuf定义语言无关的RPC服务接口,这才是真正的价值。

这就属于Portobuf自定制的范围了,有兴趣可以阅读《Go语言高级编程》,之前的部分文章也是摘取于这本书中。

 

posted @ 2020-04-21 18:01  独角兕大王  阅读(852)  评论(0编辑  收藏  举报