一、什么是 Protobuf
Protobuf是Protocol Buffers的简称,Google公司开发的一种数据描述语言,用于描述一种轻便高效的结构化数据存储格式
跨语言、可扩展的序列化结构数据格式
开发者可以通过Protobuf附带的工具生成代码并实现将结构化数据序列化的功能。
Protobuf中最基本的数据单元是message,类似于Go中的结构体,在message中可以嵌套message或其它的基础数据类型的成员。
.proto文件定义
syntax = "proto3"; //指定使用proto3语法
message SearchRequest {
//保留字段
reserved 2, 15, 9 to 11;
reserved "foo", "bar";
//字段出现次数--字段类型--字段名--唯一字段编号(不可重复)
string query = 1;
int32 page_number = 2;
int32 result_per_page = 3;
repeated string snippets = 4;
//枚举
enum Corpus {
UNIVERSAL = 0;
WEB = 1;
IMAGES = 2;
LOCAL = 3;
NEWS = 4;
PRODUCTS = 5;
VIDEO = 6;
}
Corpus corpus = 4;
//message嵌套
message Result {
string url = 1;
string title = 2;
repeated string snippets = 3;
}
Result results = 10;
}
- singular 可以有0或者1个
- repeated 可以有任意多个该字段值
- 保留字段,指定保留的字段编号和字段名称。如果未来有人用了这些字段标识那么在编译时protocol buffer的编译器会报错。
意思是,这些字段和编号不可以用了,常用于当你删掉或者注释掉message中的一个字段时,未来其他开发者在更新message定义时避免再重用之前的字段编号。防止他们意外载入了老版本的数据
定义服务,类似于定义一个接口,声明其包含的方法
service SearchService {
rpc Search (SearchRequest) returns (SearchResponse);
}
生成代码
下载protoc编译器
protoc --proto_path=IMPORT_PATH --go_out=DST_DIR
指定输入和输出目录路径,--language_out 指定对应语言版本, 生成对应版本代码文件,如go语言版本的 .pd.go 文件
二、代码案列
1、创建proto文件(spider.proto)
syntax = "proto3"; // 协议为proto3
package spider; // 包名
// 发送请求
message SendAddress {
// 发送的参数字段
// 参数类型 参数名 标识号(不可重复)
string address = 1; // 要请求的地址
string method = 2; // 请求方式
}
// 返回响应
message GetResponse {
// 接收的参数字段
// 参数类型 参数名 标识号
int32 httpCode = 1; // http状态码
string response = 2; // 返回体
}
// 定义服务,可定义多个服务,每个服务可多个接口
service GoSpider {
// rpc请求 请求的函数 (发送请求参数) returns (返回响应的参数)
rpc GetAddressResponse (SendAddress) returns (GetResponse);
}
2、编译生成 spider.pb.go 代码文件
3、编写服务端
package main
import (
"context"
"google.golang.org/grpc"
"google.golang.org/grpc/reflection"
"io/ioutil"
"net"
"net/http"
"server/spider" //编译后的 spider.pb.go 代码文件
)
type server struct{} //声明一个结构体,并实现服务接口中方法
const (
// Address 监听地址
Address string = "localhost:8080"
// Method 通信方法
Method string = "tcp"
)
// 接收client端的请求,函数名需保持一致
// ctx参数必传
func (s *server) GetAddressResponse(ctx context.Context, a *spider.SendAddress) (*spider.GetResponse, error) {
// 逻辑写在这里
switch a.Method {
case "get", "Get", "GET":
// 演示微服务用,故只写get示例
status, body, err := get(a.Address)
if err != nil {
return nil, err
}
res := spider.GetResponse{
HttpCode: int32(status),
Response: body,
}
return &res, nil
}
return nil, nil
}
func get(address string) (s int, r string, err error) {
// get请求
resp, err := http.Get(address)
if err != nil {
return
}
defer resp.Body.Close()
s = resp.StatusCode
body, err := ioutil.ReadAll(resp.Body)
if err != nil {
return
}
r = string(body)
return
}
func main() {
// 监听本地端口
listener, err := net.Listen(Method, Address)
if err != nil {
return
}
s := grpc.NewServer() // 创建GRPC
spider.RegisterGoSpiderServer(s, &server{}) // 在GRPC服务端注册服务
reflection.Register(s) // 在GRPC服务器注册服务器反射服务,这个和CLI有关(便于开发时界面调试) 本身和服务无关
// Serve方法接收监听的端口,每到一个连接创建一个ServerTransport,和server的goroutine
// 这个goroutine读取GRPC请求,调用已注册的处理程序进行响应
err = s.Serve(listener)
if err != nil {
return
}
}
4、编写客户端
package main
import (
"client/spider"
"context"
"google.golang.org/grpc"
)
import "fmt"
const (
// Address server端地址
Address string = "localhost:8080"
)
func main() {
// 连接服务器
conn, err := grpc.Dial(Address, grpc.WithInsecure())
if err != nil {
fmt.Println(err)
return
}
defer conn.Close()
// 连接GRPC
c := spider.NewGoSpiderClient(conn)
// 创建要发送的结构体
req := spider.SendAddress{
Address: "http://www.baidu.com",
Method: "get",
}
// 调用server的注册方法
r, err := c.GetAddressResponse(context.Background(), &req)
if err != nil {
fmt.Println(err)
return
}
// 打印返回值
fmt.Println(r)
}