python与golang通过grpc进行通信

  1. gRPC通过protobuf来定义接口,从而有更加严格的接口约束条件。2. 同时,通过protobuf可以将数据序列化为二进制编码,这会大幅减少需要传输的数据量,从而大幅提高性能。
  2. gRPC可以方便地支持流式通信

以下将通过例子实现go和python之间的gRPC,实现简单地传输一个字符串

  • python:3.5.9
  • golang:1.13.4
  • protobuf:3.11.3

proto定义规则

ProtoBuf3数据结构定义

message 消息结构类型名{

  字段修饰符 数据类型 字段名 = 字段编码值;

}
  1. 字段修饰符
  • singular 默认值,标识成员只有0个或者1个
  • repeated 可以重复0次或多次
  1. 数据类型

image

  1. 字段编码值

用于通信双方识别对方的字段,通过同一份协议的结构体每个编码值对应的数据字段是一样的。取值范围是 1 ~ 2^32

举例

message String{

    string query = 1;
    int32 page_number = 2; // Which page number do we want
    int32 result_per_page = 3; // Number of results to return per page
    enum Corpus {
        UNIVERSAL = 0;
        WEB = 1;
        IMAGES = 2;
        LOCAL = 3;
        NEWS = 4;
        PRODUCTS = 5;
        VIDEO = 6;
    }
    Corpus corpus = 4;
    repeated int 32 num = 7;

}

定义服务

  1. 简单rpc

客户端使用 Stub 发送请求到服务器并等待响应返回,就像平常的函数调用一样,这是一个阻塞型的调用

service 服务类型名 {
  rpc 服务名 (消息类型) returns (返回值消息类型);
}
  1. 服务器端流式RPC

客户端发送请求到服务器,拿到一个流去读取返回的消息序列。客户端读取返回的流,直到里面没有任何消息。

service 服务类型名 {
  rpc 服务名 (消息类型) returns (stream 返回值消息类型);
}
  1. 客户端流式RPC

客户端写入一个消息序列并将其发送到服务器,同样也是使用流。一旦客户端完成写入消息,它等待服务器完成读取返回它的响应。

service 服务类型名 {
  rpc 服务名 (stream 消息类型) returns (返回值消息类型);
}
  1. 双向流式
service 服务类型名 {
  rpc 服务名 (stream 消息类型) returns (stream 返回值消息类型);
}

引用其他proto

// my.proto
import "first.proto";

golang和python编译proto

python编译proto

首先安装grpcio-tools和grpcio

pip install grpcio -i http://mirrors.aliyun.com/pypi/simple/ --trusted-host mirrors.aliyun.com
pip install grpcio-tools -i http://mirrors.aliyun.com/pypi/simple/ --trusted-host mirrors.aliyun.com

然后python编译proto

python -m grpc_tools.protoc --python_out=. --grpc_python_out=. -I. rpc.proto

会生成两个文件

  • rpc_pb2.py 用来和 protobuf 数据进行交互
  • rpc_pb2_grpc 用来和 grpc 进行交互

golang编译proto

首先要安装grpc,由于环境的问题许多go的库难以获取,我将库都转移到了码云上,这样下载十分方便

git clone https://gitee.com/daba0007/grpc-go.git $GOPATH/src/google.golang.org/grpc  
git clone https://gitee.com/daba0007/net.git $GOPATH/src/golang.org/x/net  
git clone https://gitee.com/daba0007/text.git $GOPATH/src/golang.org/x/text  
git clone https://gitee.com/daba0007/sys.git $GOPATH/src/golang.org/x/sys
go get -u github.com/golang/protobuf/{proto,protoc-gen-go} 

git clone https://gitee.com/daba0007/go-genproto.git $GOPATH/src/google.golang.org/genproto  

cd $GOPATH/src/  
go install google.golang.org/grpc 
cp $GOPATH/bin/protoc-gen-go /usr/local/bin

然后编译proto

protoc --go_out=plugins=grpc:. rpc.proto

这样会生成一个rpc.pb.go的文件,用来和 protobuf 数据进行交互

Demo

定义proto

通过protobuf定义接口HelloService和数据类型String

// rpc.proto
syntax = "proto3";

package go_protoc;

// 定义数据类型String
message String {
    string value = 1;
}

// 定义接口HelloService
service HelloService {
    rpc Hello (String) returns (String);
}

Go做服务端,python做客户端

  1. go Server
// 编译出来的proto文件位于$GOPATH/src/go_protoc
package main

import (
    "context"
    "go_protoc"
    "google.golang.org/grpc"
    "log"
    "net"
)


// 定义一个对象来处理接收到的protobuf
type HelloServiceImpl struct{}

func (p *HelloServiceImpl) Hello(ctx context.Context, args *go_protoc.String,) (*go_protoc.String, error) {
    // 通过编译出来的rpc.pb.go解析String类型数据
    reply := &go_protoc.String{Value: "hello:" + args.GetValue()}
    return reply, nil
}


func main() {
    // 定义一个grpc
    grpcServer := grpc.NewServer()
    // 通过编译出来的rpc.pb.go的HelloService接口定义一个服务RegisterHelloServiceServer
    go_protoc.RegisterHelloServiceServer(grpcServer, new(HelloServiceImpl))
    // 定义监听端口1234
    lis, err := net.Listen("tcp", "192.168.1.146:1234")
    if err != nil {
        log.Fatal(err)
    }
    // 开启监听
    grpcServer.Serve(lis)
}

  1. python Client
import rpc_pb2
import grpc
import rpc_pb2_grpc

# 连接 rpc 服务器
channel = grpc.insecure_channel("192.168.1.146:1234")
# 调用rpc服务,通过编译出来的rpc_pb2_grpc的HelloService接口定义HelloServiceStub接口,接收来自channel的数据
stub = rpc_pb2_grpc.HelloServiceStub(channel)
# 通过接口的rpc获取String类型数据,并获取值
response = stub.Hello(rpc_pb2.String(value="test"))
print("Greeter client received: " + response.value)

python做服务端,go做客户端

  1. python Server
from concurrent import futures
import time
import rpc_pb2
import grpc
import rpc_pb2_grpc

class Hello(rpc_pb2_grpc.HelloServiceServicer):
    # 实现 proto 文件中定义的 rpc 调用
    def Hello(self, request, context):
        return rpc_pb2.String(value = "hello {msg}".format(msg = request.value))

# 定义开启4个线程处理接收到的请求
server = grpc.server(futures.ThreadPoolExecutor(max_workers=4))
# 将编译出来的rpc_pb2_grpc的add_HelloServiceServicer_to_server函数添加到server中
rpc_pb2_grpc.add_HelloServiceServicer_to_server(Hello(), server)
 
# 定义服务端端口1234
server.add_insecure_port("192.168.1.146:1234")
server.start()

# 长期监听
try:
    while True:
        time.sleep(60 * 60 * 24)
except KeyboardInterrupt:
    server.stop(0)

  1. go Client
package main

import (
    "fmt"
    "go_protoc"
    "google.golang.org/grpc"
    "log"
    "golang.org/x/net/context"
)

func main() {
    // 连接服务端接口
    conn, err := grpc.Dial("192.168.1.146:1234", grpc.WithInsecure())
    if err != nil {
        log.Fatal(err)
    }
    defer conn.Close()

    // 通过编译rpc.pb.go得到NewHelloServiceClient函数来处理连接
    client := go_protoc.NewHelloServiceClient(conn)
    // 通过编译rpc.pb.go得到的Hello服务来发送数据类型为String的数据
    reply, err := client.Hello(context.Background(), &go_protoc.String{Value: "test"})
    if err != nil {
        log.Fatal(err)
    }
    fmt.Println(reply.GetValue())
}