syntax = "proto3"; // 这是个proto3的文件
message HelloRequest{ // 创建数据对象
string name = 1; // name表示名称,编号是1
}
安装grpcio和grpcio-tools库
pip install grpcio #安装grpc
pip install grpcio-tools #安装grpc tools
生成proto的python文件
python -m grpc_tools.protoc --python_out=. --grpc_python_out=. -I. helloworld.proto
python -m grpc_tools.protoc
:使用grpc_tools包中的protoc命令进行代码生成。--python_out=.
:指定生成的Python代码的存放位置为当前目录。--grpc_python_out=.
:指定生成的gRPC代码的存放位置为当前目录。-I.
:指定搜索.proto文件的路径为当前目录总结起来,该命令的作用是将当前目录下的helloworld.proto文件生成对应的Python代码,并将生成的代码存放在当前目录中
syntax = "proto3";
// The greeting service definition.
service Greeter {
// Sends a greeting 下面就是暴露出来的一些方法
rpc SayHello (HelloRequest) returns (HelloReply) {} // 定义返回什么类型就要返回什么类型
rpc SayHelloStreamReply (HelloRequest) returns (stream HelloReply) {}
rpc SayHelloBidiStream (stream HelloRequest) returns (stream HelloReply) {}
}
// The request message containing the user's name.
message HelloRequest {
string name = 1;
}
// The response message containing the greetings
message HelloReply {
string message = 1;
}
生成出来的文件就直接使用他
生成指令:
#好像是版本不兼容了 protoc -I . helloworld.proto --go_out=plugins=grpc:.
protoc -I . --go_out=. --go-grpc_out=. ./hello.proto
样例:
syntax = "proto3";
option go_package = ".;proto"; // 这个是必须加的
// The greeting service definition.
service Greeter {
// Sends a greeting 下面就是暴露出来的一些方法
rpc SayHello (HelloRequest) returns (HelloReply) {} // 定义返回什么类型就要返回什么类型
}
// The request message containing the user's name.
message HelloRequest {
string name = 1;
}
// The response message containing the greetings
message HelloReply {
string message = 1;
}
https://blog.csdn.net/neve_give_up_dan/article/details/126920398
https://github.com/grpc/grpc-go/issues/3794
https://blog.csdn.net/Mirale/article/details/122736894
UnimplementedXxxServer的作用:https://blog.csdn.net/Canon_in_D_Major/article/details/108135724
https://zhuanlan.zhihu.com/p/660634947
option go_package = "common/stream/proto/v1";
表示生成的Go代码文件应该位于common/stream/proto/v1
这个包路径下。换句话说,生成的Go代码文件将被放置在common/stream/proto/v1
目录下,并且其package声明会是v1
grpc的流模式主要有三种:
proto文件:stream_proto.proto
syntax = "proto3";
option go_package = ".;proto";
service Greeter {
// 服务端流模式:客户端是流,服务端不是流
rpc GetStream(StreamReqData) returns (stream StreamResData); // 服务端流模式
rpc PostStream(stream StreamReqData) returns (StreamResData); // 客户端流模式
rpc AllStream(stream StreamReqData) returns (stream StreamResData); // 双向流模式
}
message StreamReqData{
string data = 1;
}
message StreamResData{
string data = 1;
}
server.go
package main
import (
"GoRpc_quick/stream_grpc_test/proto"
"fmt"
"google.golang.org/grpc"
"net"
"sync"
"time"
)
const PORT = ":8080"
type server struct {
proto.UnimplementedGreeterServer
}
// 服务端流模式
func (s *server) GetStream(req *proto.StreamReqData, streamServer proto.Greeter_GetStreamServer) error {
i := 0
for true {
streamServer.Send(&proto.StreamResData{
Data: fmt.Sprintf("%v\n + %v", time.Now().Unix(), req.Data),
})
time.Sleep(time.Second)
if i++; i > 10 {
break
}
}
return nil
}
// 客户端流模式
func (s *server) PostStream(streamServer proto.Greeter_PostStreamServer) error {
for {
recv, err := streamServer.Recv()
if err != nil {
fmt.Println(err)
break
}
fmt.Println(recv.Data)
}
return nil
}
// 双向流模式
func (s *server) AllStream(streamServer proto.Greeter_AllStreamServer) error {
wg := sync.WaitGroup{}
wg.Add(2)
go func() { // 负责receive
defer wg.Done()
for {
recv, _ := streamServer.Recv()
fmt.Println("收到客户端消息:", recv.Data)
}
}()
go func() {
defer wg.Done()
for i := 0; i < 10; i++ {
streamServer.Send(&proto.StreamResData{Data: "我是服务器"})
time.Sleep(time.Second)
}
}()
wg.Wait()
return nil
}
func main() {
listener, err := net.Listen("tcp", PORT)
if err != nil {
panic(err)
}
s := grpc.NewServer()
proto.RegisterGreeterServer(s, &server{})
err = s.Serve(listener)
if err != nil {
panic("failed to start grpc")
}
}
client.go
package main
import (
"GoRpc_quick/stream_grpc_test/proto"
"context"
"fmt"
"google.golang.org/grpc"
"sync"
"time"
)
func main() {
conn, err := grpc.Dial("localhost:8080", grpc.WithInsecure())
if err != nil {
panic(err)
}
defer conn.Close()
// 服务端流模式(客户端接收流)
c := proto.NewGreeterClient(conn)
stream, _ := c.GetStream(context.Background(), &proto.StreamReqData{Data: "alice"})
for {
recv, err := stream.Recv() // 实际上就是socket编程
if err != nil {
fmt.Println(err.Error())
break
}
fmt.Println(recv)
}
// 客户端流模式,客户端发送流
postStream, err := c.PostStream(context.Background())
for i := 0; i < 10; i++ {
_ = postStream.Send(&proto.StreamReqData{Data: fmt.Sprintf("客户端流模式 + %d", i)})
time.Sleep(time.Second)
}
// 双向流模式
allStream, _ := c.AllStream(context.Background())
wg := sync.WaitGroup{}
wg.Add(2)
go func() { // 负责receive
defer wg.Done()
for {
recv, _ := allStream.Recv()
fmt.Println("收到服务器消息:", recv.Data)
}
}()
go func() {
defer wg.Done()
for i := 0; i < 10; i++ {
allStream.Send(&proto.StreamReqData{Data: "我是客户端"})
time.Sleep(time.Second)
}
}()
wg.Wait()
}
基本类型会有对应
一个标量消息字段可以含有一个如下的类型——该表格展示了定义于.proto文件中的类型,以及与之对应的、在自动生成的访问类中定义的类型:
.proto Type | Notes | Python Type | Go Type |
---|---|---|---|
double | float | float64 | |
float | float | float32 | |
int32 | 使用变长编码,对于负值的效率很低,如果你的域有可能有负值,请使用sint64替代 | int | int32 |
uint32 | 使用变长编码 | int | uint32 |
uint64 | 使用变长编码 | int | uint64 |
sint32 | 使用变长编码,这些编码在负值时比int32高效的多 | int | int32 |
sint64 | 使用变长编码,有符号的整型值。编码时比通常的int64高效。 | int | int64 |
fixed32 | 总是4个字节,如果数值总是比总是比228大的话,这个类型会比uint32高效。 | int | uint32 |
fixed64 | 总是8个字节,如果数值总是比总是比256大的话,这个类型会比uint64高效。 | int | uint64 |
sfixed32 | 总是4个字节 | int | int32 |
sfixed64 | 总是8个字节 | int | int64 |
bool | bool | bool | |
string | 一个字符串必须是UTF-8编码或者7-bit ASCII编码的文本。 | str | string |
bytes | 可能包含任意顺序的字节数据。 | str | []byte |
protobuf是会有一个默认值,即使不传,也会有默认值
当一个消息被解析的时候,如果被编码的信息不包含一个特定的singular元素,被解析的对象锁对应的域被设置位一个默认值,对于不同类型指定如下:
client.py
# 方式1
with grpc.insecure_channel("localhost:8080") as channel:
stub = helloword_pb2_grpc.GreeterStub(channel)
hello_request = helloword_pb2.HelloRequest(id=[1, 2, 3,]) # 先实例化不赋值
hello_request.name = "bob"
res: helloword_pb2.HelloReply = stub.SayHello(hello_request)
print(res.message)
# 方式2
with grpc.insecure_channel("localhost:8080") as channel:
stub = helloword_pb2_grpc.GreeterStub(channel)
hello_request = helloword_pb2.HelloRequest() # 先实例化不赋值
hello_request.name = "bob"
hello_request.id.extend([1, 2]) # 这个对象已经有默认值创建好了,我们把他当做一个list来进行操作
hello_request.id.append(4)
res: helloword_pb2.HelloReply = stub.SayHello(hello_request)
print(res.message)
// The response message containing the greetings
message HelloReply {
string message = 1;
message Result { // 在里面定义message
string name = 1;
string url = 2;
}
repeated Result data = 2; // 定义一个message数组
}
py中的嵌套调用:
from grpc_hello.proto.helloword_pb2 import HelloReply # 使用嵌套里面的数据
res = HelloReply.Result()
# 从哪嵌套的从哪调用
go中的嵌套调用:
import "GoRpc_quick/grpc_test/proto"
proto.Pong{} // 直接调用
// 使用枚举类型,使得类型只能是下面的某一个
enum Gender{
MALE = 0;
FEMALE = 1;
}
// The request message containing the user's name.
message HelloRequest {
string name = 1;
string url = 2;
Gender g = 3;
map mp = 4;
google.protobuf.Timestamp addTime = 5; // 时间戳
}
现在已经有官方的api了https://grpc.github.io/grpc/python/grpc_asyncio.html
这里使用grpclib库来实现:
安装依赖包
pip install grpclib
# 生成对应文件
python -m grpc_tools.protoc -I. --python_out=. --grpclib_python_out=. helloworld.proto
metadata使得client和server能够为对方提供关于本次调用的一些信息,就像一次http请求的RequestHeader和ResponseHeader一样。http中header的生命周周期是一次http请求,那么metadata的生命周期就是一次RPC调用
MD 类型实际上是map,key是string,value是string类型的slice。
type MD map[string][]string
创建的时候可以像创建普通的map类型一样使用new关键字进行创建:
//第一种方式
md := metadata.New(map[string]string{"key1": "val1", "key2": "val2"})
//第二种方式 key不区分大小写,会被统一转成小写。
md := metadata.Pairs(
"key1", "val1",
"key1", "val1-2", // "key1" will have map value []string{"val1", "val1-2"}
"key2", "val2", // 使用逗号
)
md := metadata.Pairs("key", "val")
// 新建一个有 metadata 的 context
ctx := metadata.NewOutgoingContext(context.Background(), md)
// 单向 RPC
response, err := client.SomeRPC(ctx, someRequest)
func (s *server) SomeRPC(ctx context.Context, in *pb.SomeRequest) (*pb.SomeResponse, err) {
md, ok := metadata.FromIncomingContext(ctx)
// do something with metadata
}
proro
syntax = "proto3";
option go_package = ".;proto";
// The greeting service definition.
service Greeter {
// Sends a greeting 下面就是暴露出来的一些方法
rpc SayHello (HelloRequest) returns (HelloReply) {} // 定义返回什么类型就要返回什么类型
}
// The request message containing the user's name.
message HelloRequest {
string name = 1;
}
// The response message containing the greetings
message HelloReply {
string message = 1;
}
server.go
type Server struct {
*proto.UnimplementedGreeterServer
}
// 参数设置是硬性规定的
func (s *Server) SayHello(ctx context.Context, req *proto.HelloRequest) (*proto.HelloReply, error) {
md, ok := metadata.FromIncomingContext(ctx)
if !ok {
fmt.Println("get metadata error")
}
// 遍历一下这个string的map
for k, v := range md {
fmt.Println(k, v)
}
if nameSlice, ok := md["name"]; ok { // 判断map中是否含有元素
fmt.Println(nameSlice)
for i, e := range nameSlice {
fmt.Println(i, e)
}
}
return &proto.HelloReply{
Message: "Go hello " + req.Name,
}, nil
}
func main() {
// 一样的新建server
g := grpc.NewServer()
// 注册服务
proto.RegisterGreeterServer(g, &Server{})
listen, err := net.Listen("tcp", "0.0.0.0:8080")
if err != nil {
panic("failed to listen: " + err.Error())
}
err = g.Serve(listen)
if err != nil {
panic("failed to start grpc: " + err.Error())
}
}
client.go
func main() {
conn, err := grpc.Dial("127.0.0.1:8080", grpc.WithInsecure())
if err != nil {
panic(err)
}
defer conn.Close()
c := proto_bak.NewGreeterClient(conn)
//md := metadata.Pairs("timestamp", time.Now().Format(times))
md := metadata.New(map[string]string{
"name": "alice",
"pwd": "12345",
})
ctx := metadata.NewOutgoingContext(context.Background(), md)
r, err := c.SayHello(ctx, &proto_bak.HelloRequest{
Name: "bob",
})
if err != nil {
panic(err)
}
fmt.Println(r.Message)
}
server.py
import grpc
from grpc_metadata_test.proto import helloword_pb2, helloword_pb2_grpc
if __name__ == '__main__':
with grpc.insecure_channel("localhost:50051") as channel:
stub = helloword_pb2_grpc.GreeterStub(channel)
hello_request = helloword_pb2.HelloRequest() # 先实例化不赋值
hello_request.name = "bob"
rsp, call = stub.SayHello.with_call(
hello_request,
metadata=(
('name', 'bobby'),
('pwd', '123456')
)
)
# call 能拿到服务器发回来的东西
print(rsp.message)
client.py
import grpc
from concurrent import futures
from grpc_metadata_test.proto import helloword_pb2, helloword_pb2_grpc
class Greeter(helloword_pb2_grpc.GreeterServicer):
def SayHello(self, request, context):
# 一样的遍历metadata的map
for k, v in context.invocation_metadata():
print(k, v)
return helloword_pb2.HelloReply(message=f'Python 你好{request.name}')
if __name__ == '__main__':
# 1. 实例化server
server = grpc.server(futures.ThreadPoolExecutor(max_workers=10)) # 设置十个线程
# 2. 注册逻辑到server中
helloword_pb2_grpc.add_GreeterServicer_to_server(Greeter(), server)
# 3. 启动server
server.add_insecure_port('[::]:50051')
server.start()
server.wait_for_termination()
server.go
type Server struct {
*proto.UnimplementedGreeterServer
}
// 参数设置是硬性规定的
func (s *Server) SayHello(ctx context.Context, req *proto.HelloRequest) (*proto.HelloReply, error) {
return &proto.HelloReply{
Message: "Go hello " + req.Name,
}, nil
}
func main() {
// 定义拦截器的函数
interceptor := func(ctx context.Context, req interface{}, info *grpc.UnaryServerInfo, handler grpc.UnaryHandler) (resp interface{}, err error) {
fmt.Println("接收到了一个新的清秀")
res, err := handler(ctx, req) // handler是原本的调用逻辑
fmt.Println("请求已经完成")
return res, err
}
opt := grpc.UnaryInterceptor(interceptor) // 参数是一个函数,所以参数必须一致(go认参数来区分函数
// 一样的新建server
g := grpc.NewServer(opt) // 放进去
// 注册服务
proto.RegisterGreeterServer(g, &Server{})
listen, err := net.Listen("tcp", "0.0.0.0:8080")
if err != nil {
panic("failed to listen: " + err.Error())
}
err = g.Serve(listen)
if err != nil {
panic("failed to start grpc: " + err.Error())
}
}
client.go
func main() {
interceptor := func(ctx context.Context, method string, req, reply interface{}, cc *grpc.ClientConn, invoker grpc.UnaryInvoker, opts ...grpc.CallOption) error {
start := time.Now()
err := invoker(ctx, method, req, reply, cc, opts...)
fmt.Printf("耗时: %s\n", time.Since(start))
return err
} // 拦截器逻辑
opt := grpc.WithUnaryInterceptor(interceptor) // 需要在dial(拨号)的时候传入参数
conn, err := grpc.Dial("127.0.0.1:8080", grpc.WithInsecure(), opt)
if err != nil {
panic(err)
}
defer conn.Close()
c := proto.NewGreeterClient(conn)
r, err := c.SayHello(context.Background(), &proto.HelloRequest{
Name: "bob",
})
if err != nil {
panic(err)
}
fmt.Println(r.Message)
}
server.py
class Greeter(helloword_pb2_grpc.GreeterServicer):
def SayHello(self, request, context):
for k, v in context.invocation_metadata():
print(k, v)
return helloword_pb2.HelloReply(message=f'Python 你好{request.name}')
# 调用拦截器逻辑需要继承该抽象类,并且实现这个抽象类的方法
class LogInterceptors(grpc.ServerInterceptor):
def intercept_service(self, continuation, handler_call_details):
print("请求开始")
print(type(handler_call_details))
rsp = continuation(handler_call_details) # 相当于handler
print("请求结束")
return rsp
if __name__ == '__main__':
# 实例化一个inter
interceptor = LogInterceptors()
# 1. 实例化server
server = grpc.server(futures.ThreadPoolExecutor(max_workers=10), interceptors=(interceptor,)) # 设置十个线程
# 并传进去interceptor的tuple或者list, 最后一定要带个,
# 2. 注册逻辑到server中
helloword_pb2_grpc.add_GreeterServicer_to_server(Greeter(), server)
# 3. 启动server
server.add_insecure_port('[::]:50051')
server.start()
server.wait_for_termination()
client.py
客户端的拦截器
# 第二个参数需要继承重写
class DefaultInterceptor(grpc.UnaryUnaryClientInterceptor):
def intercept_unary_unary(self, continuation, client_call_details, request):
# 拦截器业务逻辑
start = datetime.now()
rsp = continuation(client_call_details, request)
print((datetime.now() - start).microseconds / 1000, "ms")
return rsp
if __name__ == '__main__':
# 实例化对象
default_intercept = DefaultInterceptor()
with grpc.insecure_channel("localhost:50051") as channel:
# 需要对channel进行操作
intercept_channel = grpc.intercept_channel(channel, default_intercept)
stub = helloword_pb2_grpc.GreeterStub(intercept_channel)
hello_request = helloword_pb2.HelloRequest() # 先实例化不赋值
hello_request.name = "bob"
rsp, call = stub.SayHello.with_call(
hello_request,
metadata=(
('name', 'bobby'),
('pwd', '123456')
)
)
# call 能拿到服务器发回来的东西
print(rsp.message)
docs:protoc-gen-validate/docs.md at main · bufbuild/protoc-gen-validate · GitHub
每个字段要满足一个验证规则
编译命令:
protoc \
-I . \
-I path/to/validate/ \
--go_out=":../generated" \
--validate_out="lang=go:../generated" \
example.proto
protoc -I . --go_out=. --go-grpc_out=. --validate_out="lang=go:." ./hello.proto
需要去拷贝一份validate.proto
放到文件中:protoc-gen-validate/validate/validate.proto at main · bufbuild/protoc-gen-validate · GitHub
proto
syntax = "proto3";
import "validate.proto";
option go_package=".;proto";
service Greeter {
rpc SayHello (Person) returns (Person);
}
message Person {
uint64 id = 1 [(validate.rules).uint64.gt = 999];
string email = 2 [(validate.rules).string.email = true];
string Mobile = 3 [(validate.rules).string = {
pattern: "^(?:(?:\\+|00)86)?1[3-9]\\d{9}$",max_bytes: 256,}];
}
server.go
package main
import (
"context"
"google.golang.org/grpc/codes"
"google.golang.org/grpc/status"
"net"
"google.golang.org/grpc"
"GoRpc_quick/grpc_validate_test/proto"
)
type Server struct {
*proto.UnimplementedGreeterServer
}
func (s *Server) SayHello(ctx context.Context, request *proto.Person) (*proto.Person,
error) {
return &proto.Person{
Id: 32,
}, nil
}
type Validator interface {
Validate() error
}
func main() {
//p := new(proto.Person)
//p.Id = 1000
//err := p.Validate() // 会返回一个error来判断合法性
//if err != nil {
// panic(err)
//}
var interceptor grpc.UnaryServerInterceptor
interceptor = func(ctx context.Context, req interface{}, info *grpc.UnaryServerInfo, handler grpc.UnaryHandler) (resp interface{}, err error) {
// 在拦截器中判断完整性,继续处理请求
if r, ok := req.(Validator); ok { // 实例化为Validator,用多态的方法调用Validate方法
if err := r.Validate(); err != nil {
return nil, status.Error(codes.InvalidArgument, err.Error())
}
}
return handler(ctx, req)
}
var opts []grpc.ServerOption
opts = append(opts, grpc.UnaryInterceptor(interceptor))
g := grpc.NewServer(opts...)
proto.RegisterGreeterServer(g, &Server{})
lis, err := net.Listen("tcp", "0.0.0.0:50051")
if err != nil {
panic("failed to listen:" + err.Error())
}
err = g.Serve(lis)
if err != nil {
panic("failed to start grpc:" + err.Error())
}
}
client.go
package main
import (
"GoRpc_quick/grpc_validate_test/proto"
"context"
"fmt"
"google.golang.org/grpc"
)
// 本质上封装了metadata的封装
type customCredential struct {
}
func main() {
var opts []grpc.DialOption
opts = append(opts, grpc.WithInsecure())
conn, err := grpc.Dial("127.0.0.1:50051", opts...)
if err != nil {
panic(err)
}
defer conn.Close()
c := proto.NewGreeterClient(conn)
r, err := c.SayHello(context.Background(), &proto.Person{
Id: 1000,
Email: "[email protected]",
Mobile: "18888888888",
})
if err != nil {
panic(err)
}
fmt.Println(r.Id)
}
客户端处理:
import grpc
from grpc_error_test.proto import helloword_pb2, helloword_pb2_grpc
if __name__ == '__main__':
with grpc.insecure_channel("localhost:8080") as channel:
stub = helloword_pb2_grpc.GreeterStub(channel)
hello_request = helloword_pb2.HelloRequest() # 先实例化不赋值
hello_request.name = "bob"
try:
res: helloword_pb2.HelloReply = stub.SayHello(hello_request, timeout=3)
except grpc.RpcError as e:
d = e.details()
print(d)
status_code = e.code() # 获取code
print(status_code.name)
print(status_code.value)
print(res.message)
服务端处理
import grpc
from concurrent import futures
from grpc_error_test.proto import helloword_pb2, helloword_pb2_grpc
class Greeter(helloword_pb2_grpc.GreeterServicer):
def SayHello(self, request, context):
context.set_code(grpc.StatusCode.NOT_FOUND)
context.set_details("记录不存在")
return helloword_pb2.HelloReply(message=f'Python 你好{request.name}')
if __name__ == '__main__':
# 1. 实例化server
server = grpc.server(futures.ThreadPoolExecutor(max_workers=10)) # 设置十个线程
# 2. 注册逻辑到server中
helloword_pb2_grpc.add_GreeterServicer_to_server(Greeter(), server)
# 3. 启动server
server.add_insecure_port('[::]:8080')
server.start()
server.wait_for_termination()
客户端处理
func main() {
conn, err := grpc.Dial("127.0.0.1:8080", grpc.WithInsecure())
if err != nil {
panic(err)
}
defer conn.Close()
c := proto.NewGreeterClient(conn)
// 设置3超时
ctx, cancel := context.WithTimeout(context.Background(), 3*time.Second)
defer cancel()
_, err = c.SayHello(ctx, &proto.HelloRequest{
Name: "bob",
})
if err != nil { // 捕捉并解析错误
st, ok := status.FromError(err)
if !ok {
panic("error解析失败")
}
fmt.Println(st.Message())
fmt.Println(st.Code())
}
//fmt.Println(r.Message)
}
服务端处理:
package main
import (
"GoRpc_quick/grpc_error_test/proto"
"context"
"google.golang.org/grpc"
"google.golang.org/grpc/codes"
"google.golang.org/grpc/status"
"net"
)
type Server struct {
*proto.UnimplementedGreeterServer
}
// 参数设置是硬性规定的
func (s *Server) SayHello(ctx context.Context, req *proto.HelloRequest) (*proto.HelloReply, error) {
return &proto.HelloReply{
Message: "Go hello " + req.Name,
}, status.Error(codes.InvalidArgument, "未实现") // 返回错误码和描述
}
func main() {
// 一样的新建server
g := grpc.NewServer()
// 注册服务
proto.RegisterGreeterServer(g, &Server{})
listen, err := net.Listen("tcp", "0.0.0.0:8080")
if err != nil {
panic("failed to listen: " + err.Error())
}
err = g.Serve(listen)
if err != nil {
panic("failed to start grpc: " + err.Error())
}
}