前面已经讲了grpc 基础使用、protobuf 使用及原理 ,今天开始逐步探究grpc-go 源码实现,grpc 底层通信协议是对http2 协议的封装,grpc 并没有使用golang 官方实现的http2,而是自己实现了http2,引用了官方的帧解析器和其他枚举值,http2在其他语言实现大同小异。
一般rpc 协议会包含请求方法,请求参数,请求参数在grpc被称为消息,内容是protobuf二进制内容,方法会携带在http2 的header 里面,最后服务器解包,获取方法路由到相应的handler函数。
1服务器
来看看创建服务器的例子
package main
import (
"context"
"flag"
"fmt"
"log"
"net"
"google.golang.org/grpc"
pb "google.golang.org/grpc/examples/helloworld/helloworld"
)
var (
port = flag.Int("port", 50051, "The server port")
)
// server is used to implement helloworld.GreeterServer.
type server struct {
pb.UnimplementedGreeterServer
}
// SayHello implements helloworld.GreeterServer
func (s *server) SayHello(ctx context.Context, in *pb.HelloRequest) (*pb.HelloReply, error) {
log.Printf("Received: %v", in.GetName())
return &pb.HelloReply{Message: "Hello " + in.GetName()}, nil
}
func main() {
flag.Parse()
lis, err := net.Listen("tcp", fmt.Sprintf("localhost:%d", *port))
if err != nil {
log.Fatalf("failed to listen: %v", err)
}
s := grpc.NewServer()
pb.RegisterGreeterServer(s, &server{})
log.Printf("server listening at %v", lis.Addr())
if err := s.Serve(lis); err != nil {
log.Fatalf("failed to serve: %v", err)
}
}
创建一个服务的流程可以总结为下面几步
- 创建监听套接字
- 创建grpc server 实例对象
- 向 grpc server 注册自己定义的服务(可以注册多个服务实例)
- 启动server
1.1自定义服务
pb.RegisterGreeterServer(s, &server{}) 这句代码就是将自己的服务注册进grpc 服务器里面,pb 是自己生成的代码,还记得我们在protobuf自己定义的service吗,grpc 插件会给我们生成这个服务,和这个注册函数。
// The greeting service definition.
service Greeter {
// Sends a greeting
rpc SayHello (HelloRequest) returns (HelloReply) {}
}
下面是注册函数,参数s 代表grpc 注册服务,srv 代表我们自己定义的服务,必须实现上面定义的接口SayHello
func RegisterGreeterServer(s grpc.ServiceRegistrar, srv GreeterServer) {
s.RegisterService(&Greeter_ServiceDesc, srv)
}
srv 接口
type GreeterServer interface {
// Sends a greeting
SayHello(context.Context, *HelloRequest) (*HelloReply, error)
mustEmbedUnimplementedGreeterServer()
}
所以我们需要自己定义服务实现这个接口
// server is used to implement helloworld.GreeterServer.
type server struct {
pb.UnimplementedGreeterServer
}
// SayHello implements helloworld.GreeterServer
func (s *server) SayHello(ctx context.Context, in *pb.HelloRequest) (*pb.HelloReply, error) {
log.Printf("Received: %v", in.GetName())
return &pb.HelloReply{Message: "Hello " + in.GetName()}, nil
}
go 语言有个特点,字段是匿名结构体时可以继承,所以server是继承pb.UnimplementedGreeterServer的方法,但是 SayHello并没有实现,里面默认返回错误,如果我们不自己实现SayHello去覆盖继承的方法就会报错。
// UnimplementedGreeterServer must be embedded to have forward compatible implementations.
type UnimplementedGreeterServer struct {
}
func (UnimplementedGreeterServer) SayHello(context.Context, *HelloRequest) (*HelloReply, error) {
return nil, status.Errorf(codes.Unimplemented, "method SayHello not implemented")
}
1.1.1注册函数作用
接下来我们来看看RegisterService作用,其中sd 是服务元信息,由grpc 插件自动生成,ss 就是我们自定义的服务了
https://github.com/grpc/grpc-go/blob/master/server.go +625
// RegisterService registers a service and its implementation to the gRPC
// server. It is called from the IDL generated code. This must be called before
// invoking Serve. If ss is non-nil (for legacy code), its type is checked to
// ensure it implements sd.HandlerType.
func (s *Server) RegisterService(sd *ServiceDesc, ss interface{}) {
if ss != nil {
ht := reflect.TypeOf(sd.HandlerType).Elem() //反射获取该sd 的类型
st := reflect.TypeOf(ss) //返回获取ss 的类型
if !st.Implements(ht) { //这里会判断ss 是否实现了sd.类型
logger.Fatalf("grpc: Server.RegisterService found the handler of type %v that does not satisfy %v", st, ht)
}
}
s.register(sd, ss) //调用真正的注册函数
}
- 注册函数获取反射值并检查是否实现了指定的接口类型
- ServiceDesc由grpc 插件自动生成,里面定义了service相关的信息
1.1.2ServiceDesc是什么?有什么作用
下面这是ServiceDesc结构
// ServiceDesc represents an RPC service's specification.
type ServiceDesc struct {
ServiceName string //每个自定义服务的服务名,采用protobuf 里面定义的服务名
HandlerType interface{} //自定义服务必须实现的接口,就是前面的GreeterServer,protobuf 定义的service接口
Methods []MethodDesc //该服务上面的一元rpc 处理方法
Streams []StreamDesc //该服务上面的处理steam 的方法
Metadata interface{} //元素数据,proto 信息,编译时的相对proto 的位置
}
// MethodDesc represents an RPC service's method specification.
type MethodDesc struct {
MethodName string
Handler methodHandler
}
// StreamDesc represents a streaming RPC service's method specification. Used
// on the server when registering services and on the client when initiating
// new streams.
type StreamDesc struct {
// StreamName and Handler are only used when registering handlers on a
// server.
StreamName string // the name of the method excluding the service
Handler StreamHandler // the handler called for the method
// ServerStreams and ClientStreams are used for registering handlers on a
// server as well as defining RPC behavior when passed to NewClientStream
// and ClientConn.NewStream. At least one must be true.
ServerStreams bool // indicates the server can perform streaming sends
ClientStreams bool // indicates the client can perform streaming sends
}
StreamDesc:https://github.com/grpc/grpc-go/blob/master/stream.go +58
MethodDesc :https://github.com/grpc/grpc-go/blob/master/server.go +84
ServiceDesc:https://github.com/grpc/grpc-go/blob/master/server.go +90
下面是自动生成ServiceDesc,里面包含些默认信息,不同的grpc 模式生成的ServiceDesc结果不一样,其中所有的stream 模式的方法,服务器都是生成Streams方法。
steam和普通Methods除了handler不同,stream还多了两个bool值,用来标识客户端或者服务端可以用流模式发送数据
ServerStreams bool // indicates the server can perform streaming sends
ClientStreams bool // indicates the client can perform streaming sends
下面是一元rpc 的desc
var Greeter_ServiceDesc = grpc.ServiceDesc{
ServiceName: "helloworld.Greeter",
HandlerType: (*GreeterServer)(nil),
Methods: []grpc.MethodDesc{
{
MethodName: "SayHello",
Handler: _Greeter_SayHello_Handler,
},
},
Streams: []grpc.StreamDesc{},
Metadata: "examples/helloworld/helloworld/helloworld.proto",
}
服务端流模式
var _PropService_serviceDesc = grpc.ServiceDesc{
ServiceName: "PropService",
HandlerType: (*PropServiceServer)(nil),
Methods: []grpc.MethodDesc{},
Streams: []grpc.StreamDesc{
{
StreamName: "UserProp",
Handler: _PropService_UserProp_Handler,
ServerStreams: true,
},
},
Metadata: "protos/item_update.proto",
}
- 服务端流只有服务端可以发stream数据,所以 ServerStreams为true
客户端流模式
var _DataService_serviceDesc = grpc.ServiceDesc{
ServiceName: "DataService",
HandlerType: (*DataServiceServer)(nil),
Methods: []grpc.MethodDesc{},
Streams: []grpc.StreamDesc{
{
StreamName: "DataUpload",
Handler: _DataService_DataUpload_Handler,
ClientStreams: true,
},
},
Metadata: "protos/data.proto",
}
- 客户端流只有客户端可以发steam数据,所以 ClientStreams为true
、双向流模式
var _BattleService_serviceDesc = grpc.ServiceDesc{
ServiceName: "BattleService",
HandlerType: (*BattleServiceServer)(nil),
Methods: []grpc.MethodDesc{},
Streams: []grpc.StreamDesc{
{
StreamName: "Battle",
Handler: _BattleService_Battle_Handler,
ServerStreams: true,
ClientStreams: true,
},
},
Metadata: "protos/battle.proto",
}
- 双向流两端都是可以发stream数据的,所以ServerStreams和ClientStreams都是true
注意到一元rpc 的ServiceName和其他不一样,为helloworld.Greeter,这是因为一元rpc 命令定义了包名package helloworld,所以ServiceDesc的ServiceName为"protobuf包名.protobuf 定义服务名"
1.1.2 register做了些什么骚操作
func (s *Server) register(sd *ServiceDesc, ss interface{}) {
s.mu.Lock() //加锁,解锁
defer s.mu.Unlock()
s.printf("RegisterService(%q)", sd.ServiceName)
if s.serve { //检查服务示否启动,避免用户在启动服务再注册,产生bug
logger.Fatalf("grpc: Server.RegisterService after Server.Serve for %q", sd.ServiceName)
}
if _, ok := s.services[sd.ServiceName]; ok {//服务注册了就panic,和http 的路由一样,最后panic,防止产生bug
logger.Fatalf("grpc: Server.RegisterService found duplicate service registration for %q", sd.ServiceName)
}
info := &serviceInfo{//创建服务serviceInfo 对象
serviceImpl: ss,
methods: make(map[string]*MethodDesc),
streams: make(map[string]*StreamDesc),
mdata: sd.Metadata,
}
for i := range sd.Methods {//将一元rpc方法复制到info.methods
d := &sd.Methods[i]
info.methods[d.MethodName] = d
}
for i := range sd.Streams {//将stream 相关方法复制到info. streams
d := &sd.Streams[i]
info.streams[d.StreamName] = d
}
s.services[sd.ServiceName] = info //最后以服务的名为key,info 为值添加到map
}
- 在注册中加了锁,看来该方法是支持并发注册的,在项目开发中可以在不同模块进行注册不同服务
- 注册前的检查,对服务启动后注册或已经注册panic
- 生成serviceInfo信息,将方法名和对应的handler添加到里面,最后再将info添加到整个服务map,如果到时候客户端将这些东西传过来,是不是可以根据服务名+方法名去调用handler?看到这里差不多整个调用流程就很清晰了,和http 没啥区别,路由+handler组合。只是去找handler 和向handler传入的数据不一样而已。
1.2 创建grpc 服务
grpc 底层也是tcp,创建一个tcp 流程再简单不过了,下面是个简单的例子,循环监听listen 套接字,当有新连接到来,创建goroutine 进行处理数据。
for {
rawConn, err := lis.Accept()
if err != nil {}
go handle(rawConn)
}

最低0.47元/天 解锁文章
2551

被折叠的 条评论
为什么被折叠?



