深入解析grpc源码2-客户端与服务器通信流程

前面已经讲了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)
        }
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值