grpc使用protobuf编解码,使用http2传输数据.我们通过一个示例来看下如何编写grpc客户端和服务端代码.注意代码分为两部分,一部分是protobuf生成的stub,一部分是需要用户实际编写的代码.以下分别用protobuf客户端,protobuf服务端以及grpc客户端和grpc服务端指代.
protobuf
具体安装和生成protobuf代码细节可以参考链接
protobuf定义文件内容如下:
1 | syntax = "proto3"; |
比较关键的语法是package-定义包名,service-定义服务名,包+服务的路径下可以定义各种可供rpc调用的函数.上述proto文件的SayHello函数在grpc服务端注册路径为/helloworld.Greeter/SayHello.接着定义函数原型及入参和出参类型结构体,而message定义一个具体的类型结构体.
客户端
我们看下protobuf客户端代码:
1 | type GreeterClient interface { |
通过protobuf客户端代码可以猜测grpc客户端代码只需首先生成一个grpc.ClientConn结构,然后调用NewGreeeterClient生成一个greeterClient结构,即可调用SayHello方法.SayHello方法最终调用的是grpc.ClientConn结构的invoke方法,调用路径为/helloworld.Greeter/Sayhello,可以猜想到,grpc服务端肯定在这个路径会注册一个钩子函数去执行,并且该钩子函数需要在grpc服务端自己定义.
protobuf客户端生成的只是一个wrapper,我们完全可以直接按如下方法调用:
1 | cc.Invoke(ctx, "/helloworld.Greeter/SayHello", in, out, opts...) |
看下grpc客户端实际的代码:
1 | package main |
只保留关键路径,可以看到,grpc.Dial生成一个grpc.ClientConn,其他步骤与上文想法一致
服务端
我们看下protobuf服务端代码:
1 | type GreeterServer interface { |
通过protobuf服务端可以猜想grpc服务端关键结构体有grpc.ServiceDesc,grpc.MethodDesc,grpcStreamDesc.
首先需要grpc服务端生成一个grpc.Server指针,并且实现GreeterServer接口(即实现SayHello方法),然后调用RegisterGreeterServer即可将其注册到grpc服务端.
我们看到grpc.ServiceDesc中的Methods字段保存的即为方法SayHello和具体的钩子函数,回调钩子函数为_Greeter_SayHello_Handler,该函数也是protobuf服务端代码,其关键步骤如下:
- 使用dec解析输入
- 如果interceptor为空,即不存在拦截器,则直接调用grpc服务端GreeterServer的SayHello方法并且返回
- 否则返回interceptor(ctx, in, info, handler),interceptor为函数,handler为grpc服务端GreeterServer的SayHello方法,可以猜测到interceptor函数先将输入处理完后最后调用handler处理
grpc服务端实际代码如下:
1 | package main |
同理,grpc服务端生成一个grpc.Server并且注册之后,调用Serve函数,并且传入一个Listener,即完成了该代码逻辑.
后记
接下来会首先分析grpc源码中设计客户端的grpc.ClientConn结构体和grpc.Dial()函数生成该结构体的过程以及关键的CientConn的invoke()函数如何发起一个请求
1 | grpc.Dial() |
服务端grpc.Server结构体,grpc.ServiceDesc,grpc.MethodDesc,grpc.StreamDesc以及grpc.NewServer()生成一个Server结构体,Server结构体的RegisterService()函数如何注册一个服务,以及Serve()函数如何提供服务(接收请求,解析请求,通过注册服务的回调函数处理请求,返回相应).
1 | grpc.NewServer() |