深入解析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)
        }

1.2.1 创建server

我们来看看grpc 怎么做的,创建服务,监听套接字,先看NewServer干了些什么

s := grpc.NewServer()
if err := s.Serve(lis); err != nil {
        log.Fatalf("failed to serve: %v", err)
    }
func NewServer(opt ...ServerOption) *Server {
   opts := defaultServerOptions
   for _, o := range opt {
      o.apply(&opts)
   }
   s := &Server{ //创建服务实例
      lis:      make(map[net.Listener]bool), //监听套接字map
      opts:     opts,
      conns:    make(map[string]map[transport.ServerTransport]bool),
      services: make(map[string]*serviceInfo),
      quit:     grpcsync.NewEvent(),
      done:     grpcsync.NewEvent(),
      czData:   new(channelzData),
   }
   chainUnaryServerInterceptors(s) //一元拦截器
   chainStreamServerInterceptors(s)//stream 模式拦截器
   s.cv = sync.NewCond(&s.mu) //条件变量
   if EnableTracing { 
      _, file, line, _ := runtime.Caller(1)
      s.events = trace.NewEventLog("grpc.Server", fmt.Sprintf("%s:%d", file, line))
   }
​
   if s.opts.numServerWorkers > 0 { //协程池,如果设置协程数量,那么将会以指定协程数量去处理数据
      s.initServerWorkers()
   }
​
   if channelz.IsOn() {
      s.channelzID = channelz.RegisterServer(&channelzServer{s}, "")
   }
   return s
}
  • NewServer 需要传入选项,经常看框架源码的同学都知道干什么用了,相当于go 可以传入零个或者多个参数,并且都是回调函数,来设置grpc 的配置参数
  • 设置拦截器配置,拦截器其实相当于中间件
  • 设置是否开启协程数量,如果设置了数量,那么将会初始化numServerWorkers goroutine 去循环处理数据

1.2.2 grpc 配置

type serverOptions struct {
   creds                 credentials.TransportCredentials //证书,tls 有关
   codec                 baseCodec //默认编解码器
   cp                    Compressor //压缩器
   dc                    Decompressor
   unaryInt              UnaryServerInterceptor //一元服务拦截器
   streamInt             StreamServerInterceptor//stream 服务拦截器
   chainUnaryInts        []UnaryServerInterceptor //拦截器数组
   chainStreamInts       []StreamServerInterceptor//stream拦截器数组
   inTapHandle           tap.ServerInHandle
   statsHandler          stats.Handler
   maxConcurrentStreams  uint32
   maxReceiveMessageSize int
   maxSendMessageSize    int
   unknownStreamDesc     *StreamDesc //未知stream 处理
   keepaliveParams       keepalive.ServerParameters  //连接保活参数
   keepalivePolicy       keepalive.EnforcementPolicy //连接保活策略
   initialWindowSize     int32 //初始滑动窗口大小
   initialConnWindowSize int32 //初始连接滑动窗口大小
   writeBufferSize       int
   readBufferSize        int
   connectionTimeout     time.Duration//连接超时
   maxHeaderListSize     *uint32//http header 列表的最大限制
   headerTableSize       *uint32
   numServerWorkers      uint32 //work 处理协程数量
}
  • 最后初始化的时候回将拦截器数组串成链,后面在细讲的时候再写

默认配置

var defaultServerOptions = serverOptions{
   maxReceiveMessageSize: defaultServerMaxReceiveMessageSize,
   maxSendMessageSize:    defaultServerMaxSendMessageSize,
   connectionTimeout:     120 * time.Second,
   writeBufferSize:       defaultWriteBufSize,
   readBufferSize:        defaultReadBufSize,
}

如何使用配置?

https://github.com/grpc/grpc-go/blob/master/server.go +625

从这里开始有许多设置配置的选项,我们根据需要选择合适的配置,比如,我们要设置消息最大发送字节

s := grpc.NewServer(grpc.MaxSendMsgSize(1000))

1.3 grpc 和客户端通信流程

1.3.1创建连接

https://github.com/grpc/grpc-go/blob/master/server.go +739

  • server 函数为每个连接创建 goroutine
  • goroutines处理grpc 请求并调用注册的handler去响应请求
  • Server 会返回在lis.Accept产生致命错误的时候,当方法返回后lis 将会被关闭
  • server会返回错误,没有调用stop 或者GracefulStop
func (s *Server) Serve(lis net.Listener) error {
   s.mu.Lock()//加锁
   s.printf("serving")
   s.serve = true //将服务器启动了设置为true
   if s.lis == nil {//如果s.lis==nil,代表已经stop 或者已经GracefulStop,为什么这么说了,因为在创建服务器的时候已经make s.lis 了,不可能为nil,只有在停止的时候才会为nil
    
      s.mu.Unlock() //解锁,关闭目前传入的新lis
      lis.Close()
      return ErrServerStopped//返回报错
   }
​
   s.serveWG.Add(1)
   defer func() { //调用defer,这里会阻塞住,等待服务器停止
      s.serveWG.Done()
      if s.quit.HasFired() {
         <-s.done.Done()
      }
   }()
​
   ls := &listenSocket{Listener: lis} //创建封装的listenSocket
   s.lis[ls] = true
​
   if channelz.IsOn() {
      ls.channelzID = channelz.RegisterListenSocket(ls, s.channelzID, lis.Addr().String())
   }
   s.mu.Unlock() //解锁
​
   defer func() { //注册defer,在返回的时候,将lis 关闭,并从server 的map 里面删除
      s.mu.Lock()
      if s.lis != nil && s.lis[ls] {
         ls.Close()
         delete(s.lis, ls)
      }
      s.mu.Unlock()
   }()
​
   var tempDelay time.Duration // how long to sleep on accept failure
​
   for {//正常tc 处理流程
      rawConn, err := lis.Accept()
      if err != nil {
         if ne, ok := err.(interface {
            Temporary() bool
         }); ok && ne.Temporary() {
            if tempDelay == 0 {
               tempDelay = 5 * time.Millisecond
            } else {
               tempDelay *= 2
            }
            if max := 1 * time.Second; tempDelay > max {
               tempDelay = max
            }
            s.mu.Lock()
            s.printf("Accept error: %v; retrying in %v", err, tempDelay)
            s.mu.Unlock()
            timer := time.NewTimer(tempDelay)
            select {
            case <-timer.C:
            case <-s.quit.Done():
               timer.Stop()
               return nil
            }
            continue
         }
         s.mu.Lock()
         s.printf("done serving; Accept = %v", err)
         s.mu.Unlock()
​
         if s.quit.HasFired() {
            return nil
         }
         return err
      }
      tempDelay = 0
     
      s.serveWG.Add(1)
      go func() { //新建goroutine 处理连接信息
         s.handleRawConn(lis.Addr().String(), rawConn)
         s.serveWG.Done() //调用wg.Done
      }()
   }
}

该函数作用主要是监听新连接,并分配goroutine来处理每个新连接信息,但是有几个问题我们一起来看看

1、tempDelay及Temporary()

当accept报错会返回这个错误,我们需要处理这个错误,这时候会启动一个定时器,多少秒后再去调用accept,同时监听服务器退出信号。

定时器时间是先5毫秒,然后不断*2,直到最后稳定到1秒钟,之后一直睡眠1秒钟,等待lis 从错误中恢复过来,如果一直出错将会一直进到定时器。

2、服务器是怎么阻塞和怎么退出的

在第一个defer的时候,服务器将会调用done 等待s.quit.HasFired

s.serveWG.Add(1)
defer func() { //调用defer,这里会阻塞住,等待服务器停止
      s.serveWG.Done()
      if s.quit.HasFired() {
         <-s.done.Done()
      }
   }()

HasFired,返回e.fired是否为1

func (e *Event) HasFired() bool {
   return atomic.LoadInt32(&e.fired) == 1
}

在创建连接的时候也调用了serveWG

s.serveWG.Add(1)
      go func() { //新建goroutine 处理连接信息
         s.handleRawConn(lis.Addr().String(), rawConn)
         s.serveWG.Done() //调用wg.Done
      }()

但是我们并没有看到wg.wait(),或者e.fired 什么时候为1的?答案在Stop 以及GracefulStop()身上

  • 在默认的for{}死循环中监听连接,服务器就被阻塞住了,在e.fired变为1(调用stop 系列函数),或者出错就会返回,返回就会调用defer,此时就会执行到<-s.done.Done()这个逻辑,再次阻塞住。
    如果创建grpc Server没有调用stop 系统函数停止服务,那么就会直接退出了,不会进入到<-s.done.Done这个逻辑
  • 停止函数都会做一件事情,调用s.quit.Fire,将e.fired原子交换变成1,然后调用s.serveWG.Wait()等待所有协程处理完任务然后停止。实现优雅退出
  • 停止函数会处理一些停止逻辑,清理不用的数据,在最后关闭s.done.Done,这样阻塞在<-s.done.Done()上面的主协程就会真正退出了。

1.3.2stop(停止)

https://github.com/grpc/grpc-go/blob/master/server.go +1701

​
func (s *Server) Stop() {
   s.quit.Fire() //
​
   defer func() { //最后执行等待所有协程退出,并关闭done channel
      s.serveWG.Wait()
      s.done.Fire()
   }()
​
   s.channelzRemoveOnce.Do(func() {
      if channelz.IsOn() {
         channelz.RemoveEntry(s.channelzID)
      }
   })
​
   s.mu.Lock()  //加锁将servers 的监听连接和连接列表获取处理
   listeners := s.lis
   s.lis = nil
   conns := s.conns
   s.conns = nil
    //当GracefulStop 和 Stop并发执行的时候,打断GracefulStop阻塞在条件变量上面,让GracefulStop继续执行完
   s.cv.Broadcast()
   s.mu.Unlock()
​
   for lis := range listeners { //关闭所有监听连接
      lis.Close()
   }
   for _, cs := range conns { //关闭所有连接
      for st := range cs {
         st.Close()
      }
   }
   if s.opts.numServerWorkers > 0 { //停止协程池
      s.stopServerWorkers()
   }
​
   s.mu.Lock()
   if s.events != nil { //清除事件
      s.events.Finish()
      s.events = nil
   }
   s.mu.Unlock()
}
​
  • 调用这个马上会关闭所有连接和listeners(简单粗暴)
  • rpc客户端将会收到连接错误

1.3.3GracefulStop(优雅的停止)

https://github.com/grpc/grpc-go/blob/master/server.go +1747

func (s *Server) GracefulStop() {
   s.quit.Fire() //先标识服务器关闭
   defer s.done.Fire()//最后执行关闭done channel
​
   s.channelzRemoveOnce.Do(func() {
      if channelz.IsOn() {
         channelz.RemoveEntry(s.channelzID)
      }
   })
   s.mu.Lock()
   if s.conns == nil { //连接为空直接返回
      s.mu.Unlock()
      return
   }
​
   for lis := range s.lis { //关闭所有监听套接,这样就不会有新连接到来了
      lis.Close()
   }
   s.lis = nil
   if !s.drain { //如果连接没有排干,那么就循环处理连接的数据
      for _, conns := range s.conns {
         for st := range conns {
            st.Drain()
         }
      }
      s.drain = true //如果所有连接都处理完了,那么将已排干设置为true
   }
​
  //等待所有线程退出,这里只能确定,不会有新连接到来
   s.mu.Unlock()
   s.serveWG.Wait()
   s.mu.Lock()
​
   for len(s.conns) != 0 {
      s.cv.Wait() //等待条件满足被唤醒
   }
   s.conns = nil
   if s.events != nil {
      s.events.Finish()
      s.events = nil
   }
   s.mu.Unlock()
}
  • 调用s.serveWG.Wait()等待所有协程处理完退出
  • 因为s.conns不等于0,说明连接没有处理完,需要s.cv.Wait() 等待所有连接处理完,条件变量被唤醒的地方有两处
    1、https://github.com/grpc/grpc-go/blob/master/server.go +1721,这种只发生在并发调用GracefulStop和Stop 的时候,当调用stop 时是直接暴力关闭所有连接,并将s.conns置为nil,此时唤醒的话,GracefulStop就能继续执行了,不会一直阻塞等待。
    2、https://github.com/grpc/grpc-go/blob/master/server.go +1035,每条连接在建立连接后都会defer调用removeConn,每次调用完removeConn都会调用Broadcast唤醒,当条件不满足时,又会继续等待,直到s.conns为nil,所有连接停止。
  • 优雅的停止grpc服务,先停止创建新连接,然后等待所有连接处理完
  • st.Drain就是发送goaway 消息代表离开
func (t *http2Server) Drain() {
   t.mu.Lock()
   defer t.mu.Unlock()
   if t.drainChan != nil {
      return
   }
   t.drainChan = make(chan struct{})
   t.controlBuf.put(&goAway{code: http2.ErrCodeNo, debugData: []byte{}, headsUp: true})
}

1.3.4处理连接

回到上面为每个连接创建协程,来看看在协程处理了什么事情

go func() {
   s.handleRawConn(lis.Addr().String(), rawConn)
   s.serveWG.Done()
}()

handleRawConn

func (s *Server) handleRawConn(lisAddr string, rawConn net.Conn) {
   if s.quit.HasFired() { //如果服务器被退出了,那么关闭连接,返回
      rawConn.Close()
      return
   }
    //设置连接超时
   rawConn.SetDeadline(time.Now().Add(s.opts.connectionTimeout))
​
  //完成http2 握手
   st := s.newHTTP2Transport(rawConn)
    //去掉连接超时
   rawConn.SetDeadline(time.Time{})
   if st == nil { //如果握手失败,st 为nil,直接返回
      return
   }
​
   if !s.addConn(lisAddr, st) {
      return
   }
   go func() {
      s.serveStreams(st)
      s.removeConn(lisAddr, st)
   }()
}
  • 创建一个http2 服务器的实例st,在这个创建过程会完成http2 的握手,http2 的握手过程我会在下一章http2 协议深入讲解
  • addConn将地址和st 添加到s.conns里面去
func (s *Server) addConn(addr string, st transport.ServerTransport) bool {
   s.mu.Lock()
   defer s.mu.Unlock()
   if s.conns == nil { //已经为空,说明服务器已经关闭了,直接返回
      st.Close()
      return false
   }
   if s.drain {
      // Transport added after we drained our existing conns: drain it
      // immediately.
      st.Drain()
   }
​
   if s.conns[addr] == nil {
      // Create a map entry if this is the first connection on this listener.
      s.conns[addr] = make(map[transport.ServerTransport]bool)
   }
   s.conns[addr][st] = true
   return true
}
  • 再开个协程去处理stream,处理完stream移除所有连接,这里可能疑问,为什么要再开个协程?
    官方说新开个协程去专门处理没有io 的数据连接处理,但是会提高效率吗?各位同鞋欢迎在评论区写出你的答案。
  • 如果这里开协程处理就不会阻塞在业务中,就会返回调用s.serveWG.Done(),所以调用s.serveWG.wait()不阻塞了,并不代表所有协程都已经完数据了

1.3.5建立流,并处理流

serveStreams

上面完成http2 的握手后,开了个协处理流的数据,参数是上面创建的http2 服务器实例。

func (s *Server) serveStreams(st transport.ServerTransport) {
   defer st.Close() //在最后关闭这个http2 服务器实例
   var wg sync.WaitGroup
​
   var roundRobinCounter uint32 //
   st.HandleStreams(func(stream *transport.Stream) {
      wg.Add(1)
      if s.opts.numServerWorkers > 0 {
         data := &serverWorkerData{st: st, wg: &wg, stream: stream}
         select {
         case s.serverWorkerChannels[atomic.AddUint32(&roundRobinCounter, 1)%s.opts.numServerWorkers] <- data: //将数据打包放在workers协程池的管道
         default: //如果没有可用的workers协程,
            //走默认逻辑,自己创建一个goroutine 去处理数据
            go func() {
               s.handleStream(st, stream, s.traceInfo(st, stream))
               wg.Done()
            }()
         }
      } else {
         go func() {
            defer wg.Done()
            s.handleStream(st, stream, s.traceInfo(st, stream))
         }()
      }
   }, func(ctx context.Context, method string) context.Context {//trace信息
      if !EnableTracing {
         return ctx
      }
      tr := trace.New("grpc.Recv."+methodFamily(method), method)
      return trace.NewContext(ctx, tr)
   })
   wg.Wait() //等待协程处理完成
}
  • 可用分两部分来看这个serveStreams函数,一个是走协程池,一个是自己创建goroutine处理流数据
  • 调用http2 服务器实例的HandleStreams,这个函数的作用是解码http2 的data 帧,将数据封装成stream流,然后用传进去的回调函数处理stream。

1.3.6grpc 协程池是怎么做的?

先回到我们创建服务器的时候,有个创建协程池的函数。当设置协程work数量后,将会初始化协程池。

https://github.com/grpc/grpc-go/blob/master/server.go +583

if s.opts.numServerWorkers > 0 {
   s.initServerWorkers()
}

initServerWorkers

func (s *Server) initServerWorkers() {
   s.serverWorkerChannels = make([]chan *serverWorkerData, s.opts.numServerWorkers)
   for i := uint32(0); i < s.opts.numServerWorkers; i++ {
      s.serverWorkerChannels[i] = make(chan *serverWorkerData)
      go s.serverWorker(s.serverWorkerChannels[i])
   }
}
  • 作用是提前创建协程处理将要到来的连接,避免在调用runtime.morestack的时候花费更多时间
  • 原理是提前创建numServerWorkers数量的WorkerChannels,然后在循环里面,为每个channel 开辟空间,注意没有缓冲,将会阻塞。
    最后每个索引再起一个协程将channel 传进去。

serverWorkerData结构如下,包含流,http2 服务器实例,和一个sync.WaitGroup

type serverWorkerData struct {
   st     transport.ServerTransport
   wg     *sync.WaitGroup
   stream *transport.Stream
}

下面是serverWorker实现,总共起numServerWorkers个协程调用该函数,并且每个协程有它们自己的channel。

​
//serverWorkerResetThreshold定义栈重置的频率,每n 个请求,就会重置一次,会在它的地方重新生成goroutine。worker可以重置它自己的栈,这样做是防止太大的栈永远在内存里面(因为在协程执行中,可能处理业务分配了很大的栈)。 2^16是每个goroutine的栈至少存活的秒数在典型的工作载荷中,假设QPS 几百请求每秒。
const serverWorkerResetThreshold = 1 << 16
func (s *Server) serverWorker(ch chan *serverWorkerData) {
 
   threshold := serverWorkerResetThreshold + grpcrand.Intn(serverWorkerResetThreshold)
   for completed := 0; completed < threshold; completed++ {
      data, ok := <-ch
      if !ok { //这里代表该channel 已经关闭,不明白可以看我写的chan 源码解析
         return
      }
      s.handleStream(data.st, data.stream, s.traceInfo(data.st, data.stream))
      data.wg.Done()
   }
   go s.serverWorker(ch)
}
  • 在一个for 循环里面,调用<-ch阻塞获取数据,因为ch 是没有缓冲的,所以是阻塞获取,当ch 被关闭的时候,就退出这个协程return。如果有数据到来,就调用 s.handleStream处理数据,最后调用data.wg.Done标识这个任务处理完,因为在管道消息发送方还调用wait等着。
  • 因为是个循环,每处理一个数据completed就会+1,直到超过threshold,才会退出循环。
    threshold为一个默认值(1 << 16)+一个随机值0~(1 << 16)之间,这样做的目的是为了避免,所有协程在同一时间退出循环,造成都重新生成goroutine,增加服务器压力,所以选择一个随机时间。
  • 在completed超过 threshold,也就是处理数据超过这个值后就会通过go 重新开辟协程(重置栈)继续下轮循环。
  • serverWorker可以处理不同的请求,并且避免了栈分配的昂贵代价。参考:https://github.com/golang/go/issues/18138

回到上面的问题,如何将数据分配给每个协程,这里用到了roundRobin算法,对worker数量取模,模的下标就是数组的索引,然后根据索引从数组里面取协程对应的channel,将数据发送到channel。然后等待的协程就会处理这个请求了。

if s.opts.numServerWorkers > 0 {
         data := &serverWorkerData{st: st, wg: &wg, stream: stream}
         select {
         case s.serverWorkerChannels[atomic.AddUint32(&roundRobinCounter, 1)%s.opts.numServerWorkers] <- data: //将数据打包放假workers协程池的管道
         default: //如果没有可用的workers协程,
            //走默认逻辑,自己创建一个goroutine 去处理数据
            go func() {
               s.handleStream(st, stream, s.traceInfo(st, stream))
               wg.Done()
            }()
         }
      }

关闭这些worker也很简单,关闭这些channel就行了,然后for 循环就会因为关闭channel return。还记得调用stop 暴力关闭的时候,也会调用这个函数去停掉这些worker协程。

func (s *Server) stopServerWorkers() {
    for i := uint32(0); i < s.opts.numServerWorkers; i++ {
        close(s.serverWorkerChannels[i])
    }
}
​

如何设置worker数量

在创建grpc server 的时候设置

s := grpc.NewServer(grpc.MaxSendMsgSize(1000),
        grpc.NumStreamWorkers(10))

https://github.com/grpc/grpc-go/blob/master/server.go +505

func NumStreamWorkers(numServerWorkers uint32) ServerOption {
   return newFuncServerOption(func(o *serverOptions) {
      o.numServerWorkers = numServerWorkers
   })
}
  • 实验建议和cup 数量保持一致表现最好,但是需要更多测试,不传这个默认值为0,代表不开启

1.3.7处理steam数据流程

https://github.com/grpc/grpc-go/blob/master/server.go +1586

终于来到怎么解码服务方法和数据的流程了。肝的有点累,不过http2 内容更多,各位同学如果有收获的话,不要忘记点赞。

func (s *Server) handleStream(t transport.ServerTransport, stream *transport.Stream, trInfo *traceInfo) {
   sm := stream.Method() //获取http2 解码出来的方法
   if sm != "" && sm[0] == '/' {
      sm = sm[1:]
   }
   pos := strings.LastIndex(sm, "/")
   if pos == -1 {
      if trInfo != nil {
         trInfo.tr.LazyLog(&fmtStringer{"Malformed method name %q", []interface{}{sm}}, true)
         trInfo.tr.SetError()
      }
      errDesc := fmt.Sprintf("malformed method name: %q", stream.Method())
      if err := t.WriteStatus(stream, status.New(codes.Unimplemented, errDesc)); err != nil {
         if trInfo != nil {
            trInfo.tr.LazyLog(&fmtStringer{"%v", []interface{}{err}}, true)
            trInfo.tr.SetError()
         }
         channelz.Warningf(logger, s.channelzID, "grpc: Server.handleStream failed to write status: %v", err)
      }
      if trInfo != nil {
         trInfo.tr.Finish()
      }
      return
   }
   service := sm[:pos] //获取服务名
   method := sm[pos+1:]//获取方法名
  //这里根据服务名获取我们注册的时候添加进去的服务器
   srv, knownService := s.services[service]
   if knownService { //如果获取到了,再从methods获取方法,如果方法获取到了,调用一元rpc 方法处理函数processUnaryRPC
      if md, ok := srv.methods[method]; ok {
         s.processUnaryRPC(t, stream, srv, md, trInfo)
         return
      }
       //如果上面没有获取到,再从streams方法里面找一找,找到了,处理,然后return
      if sd, ok := srv.streams[method]; ok {
         s.processStreamingRPC(t, stream, srv, sd, trInfo)
         return
      }
   }
   //如果是未知服务,去看看设没设置未知服务的处理方法,如果有的话,调用并return
   if unknownDesc := s.opts.unknownStreamDesc; unknownDesc != nil {
      s.processStreamingRPC(t, stream, nil, unknownDesc, trInfo)
      return
   }
    //来到这里说明已经出错了,未知的服务,也没设置未知服务的处理方式
   var errDesc string
    //生成错误描述信息
   if !knownService {
      errDesc = fmt.Sprintf("unknown service %v", service)
   } else {
      errDesc = fmt.Sprintf("unknown method %v for service %v", method, service)
   }
    //如果trace信息不为空,打印
   if trInfo != nil {
      trInfo.tr.LazyPrintf("%s", errDesc)
      trInfo.tr.SetError()
   }
    //调用http2 服务实例报错
   if err := t.WriteStatus(stream, status.New(codes.Unimplemented, errDesc)); err != nil {
      if trInfo != nil {
         trInfo.tr.LazyLog(&fmtStringer{"%v", []interface{}{err}}, true)
         trInfo.tr.SetError()
      }
      channelz.Warningf(logger, s.channelzID, "grpc: Server.handleStream failed to write status: %v", err)
   }
    //最后完成trInfo
   if trInfo != nil {
      trInfo.tr.Finish()
   }
}
  • stream.Method()获取客户端请求的服务和方法,这里先剧透下,在http2 怎么解析这个method的。
    https://github.com/grpc/grpc-go/blob/master/internal/transport/http2_server.go +403
    case ":path":
    s.method = hf.Value
    在前面的例子中, hf.Value为/helloworld.Greeter/SayHello,前面代表服务,后面代表服务的方法
  • 根据pos取"/"位置,如果 pos == -1代表这个是错误的,处理一系列错误
  • 根据解码出来的方法和服务寻找handler,如果找到了直接处理并返回,如果没有找到则找unknow service的处理方法
  • 最后没找到就直接响应http2 的错误

1.3.8处理processUnaryRPC

https://github.com/grpc/grpc-go/blob/master/server.go +1125

方法比较多,错误处理及监控指标采集代码直接简化成文字,具体内容读者感兴趣可自行查看源码

func (s *Server) processUnaryRPC(t transport.ServerTransport, stream *transport.Stream, info *serviceInfo, md *MethodDesc, trInfo *traceInfo) (err error) {
//..............状态
​
    
    //comp and cp由于压缩,decomp and dc由于解压,如果comp and decomp同时设置,他们是一样的效果
    //但是,
    var comp, decomp encoding.Compressor
    var cp Compressor
    var dc Decompressor
​
     //如果dc被设置匹配流的压缩,就使用它,要不然的话为decomp去找一个匹配的注册的压缩器
​
    if rc := stream.RecvCompress(); s.opts.dc != nil && s.opts.dc.Type() == rc {
        dc = s.opts.dc
    } else if rc != "" && rc != encoding.Identity {
        decomp = encoding.GetCompressor(rc)
        if decomp == nil {
            st := status.Newf(codes.Unimplemented, "grpc: Decompressor is not installed for grpc-encoding %q", rc)
            t.WriteStatus(stream, st)
            return st.Err()
        }
    }
​
    // If cp is set, use it.  Otherwise, attempt to compress the response using
    // the incoming message compression method.
    //
    // NOTE: this needs to be ahead of all handling, https://github.com/grpc/grpc-go/issues/686.
    if s.opts.cp != nil {
        cp = s.opts.cp
        stream.SetSendCompress(cp.Type())
    } else if rc := stream.RecvCompress(); rc != "" && rc != encoding.Identity {
        // Legacy compressor not specified; attempt to respond with same encoding.
        comp = encoding.GetCompressor(rc)
        if comp != nil {
            stream.SetSendCompress(rc)
        }
    }
​
    var payInfo *payloadInfo
    if sh != nil || binlog != nil {
        payInfo = &payloadInfo{}
    }
    d, err := recvAndDecompress(&parser{r: stream}, stream, dc, s.opts.maxReceiveMessageSize, payInfo, decomp)
    
    df := func(v interface{}) error {
        if err := s.getCodec(stream.ContentSubtype()).Unmarshal(d, v); err != nil {
            return status.Errorf(codes.Internal, "grpc: error unmarshalling request: %v", err)
        }
        if sh != nil {
            sh.HandleRPC(stream.Context(), &stats.InPayload{
                RecvTime:   time.Now(),
                Payload:    v,
                WireLength: payInfo.wireLength + headerLen,
                Data:       d,
                Length:     len(d),
            })
        }
        .......
        return nil
    }
    ctx := NewContextWithServerTransportStream(stream.Context(), stream)
    reply, appErr := md.Handler(info.serviceImpl, ctx, df, s.opts.unaryInt)
    //处理错误
    ............
if err := s.sendResponse(t, stream, reply, cp, opts, comp); err != nil {
        if err == io.EOF {
            // The entire stream is done (for unary RPC only).
            return err
            
        }
   // .............错误处理
   
}
// ....日志处理
 // .............错误处理
}
  • 获取消息的压缩和解压器,然后将数据正确解压出来
  • md.Handler(info.serviceImpl, ctx, df, s.opts.unaryInt)调用handler处理解码的数据,以前面的一元rpc handler 为例,下面是protoc 为我们生成的handler,根据服务和方法找到这个函数以后,就开始调用这个函数。其中dec为上面的df 函数,做了一件事情,就是获取数据的解码器,在这个例子中是protobuf,然后将数据解码进请求,跟json.Unmarshal(d, v)是一样的作用,这里只不过解码protobuf 数据而已。
func _HelloService_SayHello_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {
   //创建一个请求的实例
    in := new(HelloRequest)
    //将数据解码进请求
   if err := dec(in); err != nil {
      return nil, err
   }
    //如果传入进来的拦截器不为空,那么直接调用我们自定义的SayHello方法将请求传进去处理,并将响应结果返回
   if interceptor == nil {
      return srv.(HelloServiceServer).SayHello(ctx, in)
   }
    //如果拦截器不为空,那么我们就封装下,将请求和服务信息还有handler传进拦截器
   info := &grpc.UnaryServerInfo{
      Server:     srv,
      FullMethod: "/nihao.HelloService/SayHello",
   }
   handler := func(ctx context.Context, req interface{}) (interface{}, error) {
      return srv.(HelloServiceServer).SayHello(ctx, req.(*HelloRequest))
   }
   return interceptor(ctx, in, info, handler)
}

sendResponse

sendResponse将handler处理完的消息响应给客户端

func (s *Server) sendResponse(t transport.ServerTransport, stream *transport.Stream, msg interface{}, cp Compressor, opts *transport.Options, comp encoding.Compressor) error {
    //将传进来的msg数据进行编码
    data, err := encode(s.getCodec(stream.ContentSubtype()), msg)
    if err != nil {
        channelz.Error(logger, s.channelzID, "grpc: server failed to encode response: ", err)
        return err
    }
    //将数据进行压缩
    compData, err := compress(data, cp, comp)
    if err != nil {
        channelz.Error(logger, s.channelzID, "grpc: server failed to compress response: ", err)
        return err
    }
    //获取消息头和载荷
    hdr, payload := msgHeader(data, compData)
    // TODO(dfawley): should we be checking len(data) instead?
    if len(payload) > s.opts.maxSendMessageSize {
        return status.Errorf(codes.ResourceExhausted, "grpc: trying to send message larger than max (%d vs. %d)", len(payload), s.opts.maxSendMessageSize)
    }
    //将消息通过t 写回到客户端
    err = t.Write(stream, hdr, payload, opts)
    if err == nil && s.opts.statsHandler != nil {
        s.opts.statsHandler.HandleRPC(stream.Context(), outPayload(false, msg, data, payload, time.Now()))
    }
    return err
}
  • msgHeader对数据进行再编码msgHeader,逻辑为:
    1、如果有数据有压缩了,将头部数据第一个字节设为压缩模式,剩下4个字节用来代表压缩数据长度。
    2、如果没有压缩,头部第一个数据为没有压缩,剩下4个字节代表未压缩数据的长度,因为网络数据都是大端模式,所以要使用binary.BigEndian.PutUint32将数据放入hdr
const (
   payloadLen = 1
   sizeLen    = 4
   headerLen  = payloadLen + sizeLen
)
​
// msgHeader returns a 5-byte header for the message being transmitted and the
// payload, which is compData if non-nil or data otherwise.
func msgHeader(data, compData []byte) (hdr []byte, payload []byte) {
   hdr = make([]byte, headerLen)
   if compData != nil {
      hdr[0] = byte(compressionMade)
      data = compData
   } else {
      hdr[0] = byte(compressionNone)
   }
​
   // Write length of payload into buf
   binary.BigEndian.PutUint32(hdr[payloadLen:], uint32(len(data)))
   return hdr, data
}

1.3.9处理processStreamingRPC

https://github.com/grpc/grpc-go/blob/master/server.go +1415

方法比较多,错误处理及监控指标采集代码直接简化成文字,具体内容读者感兴趣可自行查看源码

func (s *Server) processStreamingRPC(t transport.ServerTransport, stream *transport.Stream, info *serviceInfo, sd *StreamDesc, trInfo *traceInfo) (err error){
 //..............状态
    //创建stream对象
ctx := NewContextWithServerTransportStream(stream.Context(), stream)
    ss := &serverStream{
        ctx:                   ctx,
        t:                     t,
        s:                     stream,
        p:                     &parser{r: stream},
        codec:                 s.getCodec(stream.ContentSubtype()),
        maxReceiveMessageSize: s.opts.maxReceiveMessageSize,
        maxSendMessageSize:    s.opts.maxSendMessageSize,
        trInfo:                trInfo,
        statsHandler:          sh,
    }
 //状态采集和二进制日志采集
    ...................
    // If dc is set and matches the stream's compression, use it.  Otherwise, try
    // to find a matching registered compressor for decomp.
    if rc := stream.RecvCompress(); s.opts.dc != nil && s.opts.dc.Type() == rc {
        ss.dc = s.opts.dc
    } else if rc != "" && rc != encoding.Identity {
        ss.decomp = encoding.GetCompressor(rc)
        if ss.decomp == nil {
            st := status.Newf(codes.Unimplemented, "grpc: Decompressor is not installed for grpc-encoding %q", rc)
            t.WriteStatus(ss.s, st)
            return st.Err()
        }
    }
    
    // If cp is set, use it.  Otherwise, attempt to compress the response using
    // the incoming message compression method.
    //
    // NOTE: this needs to be ahead of all handling, https://github.com/grpc/grpc-go/issues/686.
    if s.opts.cp != nil {
        ss.cp = s.opts.cp
        stream.SetSendCompress(s.opts.cp.Type())
    } else if rc := stream.RecvCompress(); rc != "" && rc != encoding.Identity {
        // Legacy compressor not specified; attempt to respond with same encoding.
        ss.comp = encoding.GetCompressor(rc)
        if ss.comp != nil {
            stream.SetSendCompress(rc)
        }
    }
​
    ss.ctx = newContextWithRPCInfo(ss.ctx, false, ss.codec, ss.cp, ss.comp)
if info != nil {
        server = info.serviceImpl
    }
    if s.opts.streamInt == nil {
        appErr = sd.Handler(server, ss)
    } else {
        info := &StreamServerInfo{
            FullMethod:     stream.Method(),
            IsClientStream: sd.ClientStreams,
            IsServerStream: sd.ServerStreams,
        }
        appErr = s.opts.streamInt(server, ss, info, sd.Handler)
    }
    //处理错误
    ...................................
}
  • 和一元rpc 很类似,主要区别是调用流的handler和流的拦截器处理方法,这里没有直接解压数据,而是封装成stream对象传入方法,在前面我们已经介绍过了,流的处理是循环调用传进来的stream.Recv 方法或者调用steam.Send 方法。在调用的时候才会使用这些压缩,解压,编码解码器。

2客户端

package main
​
import (
    "context"
    "flag"
    "log"
    "time"
​
    "google.golang.org/grpc"
    pb "google.golang.org/grpc/examples/helloworld/helloworld"
)
​
const (
    defaultName = "world"
)
​
var (
    addr = flag.String("addr", "localhost:50051", "the address to connect to")
    name = flag.String("name", defaultName, "Name to greet")
)
​
func main() {
    flag.Parse()
    // Set up a connection to the server.
    conn, err := grpc.Dial(*addr, grpc.WithInsecure())
    if err != nil {
        log.Fatalf("did not connect: %v", err)
    }
    defer conn.Close()
    c := pb.NewGreeterClient(conn)
​
    // Contact the server and print out its response.
    ctx, cancel := context.WithTimeout(context.Background(), time.Second)
    defer cancel()
    r, err := c.SayHello(ctx, &pb.HelloRequest{Name: *name})
    if err != nil {
        log.Fatalf("could not greet: %v", err)
    }
    log.Printf("Greeting: %s", r.GetMessage())
}
​

2.1 创建grpc 客户端

conn, err := grpc.Dial(*addr, grpc.WithInsecure())
​
//创建客户端连接
func Dial(target string, opts ...DialOption) (*ClientConn, error) {
    return DialContext(context.Background(), target, opts...)
}
​
​
func DialContext(ctx context.Context, target string, opts ...DialOption) (conn *ClientConn, err error) {
    cc := &ClientConn{
        target:            target,
        csMgr:             &connectivityStateManager{},
        conns:             make(map[*addrConn]struct{}),
        dopts:             defaultDialOptions(),
        blockingpicker:    newPickerWrapper(),
        czData:            new(channelzData),
        firstResolveEvent: grpcsync.NewEvent(),
    }
    //设置重试节流
    cc.retryThrottler.Store((*retryThrottler)(nil))
    cc.safeConfigSelector.UpdateConfigSelector(&defaultConfigSelector{nil})
    cc.ctx, cc.cancel = context.WithCancel(context.Background())
​
    for _, opt := range opts {
        opt.apply(&cc.dopts)
    }
    //客户端一元rpc 拦截器
    chainUnaryClientInterceptors(cc)
    //stream rpc 拦截器
    chainStreamClientInterceptors(cc)
​
    defer func() {
        if err != nil {
            cc.Close()
        }
    }()
​
    .......................
    //检查证书配置相关
    if cc.dopts.copts.TransportCredentials == nil && cc.dopts.copts.CredsBundle == nil {
        return nil, errNoTransportSecurity
    }
    if cc.dopts.copts.TransportCredentials != nil && cc.dopts.copts.CredsBundle != nil {
        return nil, errTransportCredsAndBundle
    }
    if cc.dopts.copts.CredsBundle != nil && cc.dopts.copts.CredsBundle.TransportCredentials() == nil {
        return nil, errNoTransportCredsInBundle
    }
    transportCreds := cc.dopts.copts.TransportCredentials
    if transportCreds == nil {
        transportCreds = cc.dopts.copts.CredsBundle.TransportCredentials()
    }
    if transportCreds.Info().SecurityProtocol == "insecure" {
        for _, cd := range cc.dopts.copts.PerRPCCredentials {
            if cd.RequireTransportSecurity() {
                return nil, errTransportCredentialsMissing
            }
        }
    }
​
    //设置默认服务器配置
    if cc.dopts.defaultServiceConfigRawJSON != nil {
        scpr := parseServiceConfig(*cc.dopts.defaultServiceConfigRawJSON)
        if scpr.Err != nil {
            return nil, fmt.Errorf("%s: %v", invalidDefaultServiceConfigErrPrefix, scpr.Err)
        }
        cc.dopts.defaultServiceConfig, _ = scpr.Config.(*ServiceConfig)
    }
    cc.mkp = cc.dopts.copts.KeepaliveParams
    
    //设置用户代理标识
    if cc.dopts.copts.UserAgent != "" {
        cc.dopts.copts.UserAgent += " " + grpcUA
    } else {
        cc.dopts.copts.UserAgent = grpcUA
    }
    //设置创建超时
    if cc.dopts.timeout > 0 {
        var cancel context.CancelFunc
        ctx, cancel = context.WithTimeout(ctx, cc.dopts.timeout)
        defer cancel()
    }
    //在最后监听服务Done信号,然后退出
    defer func() {
        select {
        case <-ctx.Done():
            switch {
            case ctx.Err() == err:
                conn = nil
            case err == nil || !cc.dopts.returnLastError:
                conn, err = nil, ctx.Err()
            default:
                conn, err = nil, fmt.Errorf("%v: %v", ctx.Err(), err)
            }
        default:
        }
    }()
​
    scSet := false
    if cc.dopts.scChan != nil {
        // Try to get an initial service config.
        select {
        case sc, ok := <-cc.dopts.scChan:
            if ok {
                cc.sc = &sc
                cc.safeConfigSelector.UpdateConfigSelector(&defaultConfigSelector{&sc})
                scSet = true
            }
        default:
        }
    }
    if cc.dopts.bs == nil {
        cc.dopts.bs = backoff.DefaultExponential
    }
​
    // Determine the resolver to use.
    resolverBuilder, err := cc.parseTargetAndFindResolver()
    if err != nil {
        return nil, err
    }
    cc.authority, err = determineAuthority(cc.parsedTarget.Endpoint, cc.target, cc.dopts)
    if err != nil {
        return nil, err
    }
    channelz.Infof(logger, cc.channelzID, "Channel authority set to %q", cc.authority)
    
    //阻塞等待配置更新完成
    if cc.dopts.scChan != nil && !scSet {
        // Blocking wait for the initial service config.
        select {
        case sc, ok := <-cc.dopts.scChan:
            if ok {
                cc.sc = &sc
                cc.safeConfigSelector.UpdateConfigSelector(&defaultConfigSelector{&sc})
            }
        case <-ctx.Done():
            return nil, ctx.Err()
        }
    }
    if cc.dopts.scChan != nil {
        go cc.scWatcher()
    }
​
    var credsClone credentials.TransportCredentials
    if creds := cc.dopts.copts.TransportCredentials; creds != nil {
        credsClone = creds.Clone()
    }
    //负载均衡选项配置
    cc.balancerBuildOpts = balancer.BuildOptions{
        DialCreds:        credsClone,
        CredsBundle:      cc.dopts.copts.CredsBundle,
        Dialer:           cc.dopts.copts.Dialer,
        Authority:        cc.authority,
        CustomUserAgent:  cc.dopts.copts.UserAgent,
        ChannelzParentID: cc.channelzID,
        Target:           cc.parsedTarget,
    }
​
    // Build the resolver.
    rWrapper, err := newCCResolverWrapper(cc, resolverBuilder)
    if err != nil {
        return nil, fmt.Errorf("failed to build resolver: %v", err)
    }
    cc.mu.Lock()
    cc.resolverWrapper = rWrapper
    cc.mu.Unlock()
​
    //阻塞等待连接配置好
    if cc.dopts.block {
        for {
            cc.Connect()
            s := cc.GetState()
            if s == connectivity.Ready {
                break
            } else if cc.dopts.copts.FailOnNonTempDialError && s == connectivity.TransientFailure {
                if err = cc.connectionError(); err != nil {
                    terr, ok := err.(interface {
                        Temporary() bool
                    })
                    if ok && !terr.Temporary() {
                        return nil, err
                    }
                }
            }
            if !cc.WaitForStateChange(ctx, s) {
                // ctx got timeout or canceled.
                if err = cc.connectionError(); err != nil && cc.dopts.returnLastError {
                    return nil, err
                }
                return nil, ctx.Err()
            }
        }
    }
​
    return cc, nil
}
​

2.2 请求

来看grpc 客户端实例

  • NewGreeterClient通过传进来的grpc 连接初始化greeterClient对象,下面代码由protoc 自动生成。
  • 调用SayHello的时候,调用grpc 客户端的invoke 方法,请求为路径为/helloworld.Greeter/SayHello,数据为传进来的数据
// GreeterClient is the client API for Greeter service.
//
// For semantics around ctx use and closing/ending streaming RPCs, please refer to https://pkg.go.dev/google.golang.org/grpc/?tab=doc#ClientConn.NewStream.
type GreeterClient interface {
   // Sends a greeting
   SayHello(ctx context.Context, in *HelloRequest, opts ...grpc.CallOption) (*HelloReply, error)
}
​
type greeterClient struct {
   cc grpc.ClientConnInterface
}
​
func NewGreeterClient(cc grpc.ClientConnInterface) GreeterClient {
   return &greeterClient{cc}
}
​
func (c *greeterClient) SayHello(ctx context.Context, in *HelloRequest, opts ...grpc.CallOption) (*HelloReply, error) {
   out := new(HelloReply)
   err := c.cc.Invoke(ctx, "/helloworld.Greeter/SayHello", in, out, opts...)
   if err != nil {
      return nil, err
   }
   return out, nil
}

invoke 调用

https://github.com/grpc/grpc-go/blob/master/clientconn.go/call.go +65

func invoke(ctx context.Context, method string, req, reply interface{}, cc *ClientConn, opts ...CallOption) error {
    //创建流
    cs, err := newClientStream(ctx, unaryStreamDesc, cc, method, opts...)
    if err != nil {
        return err
    }
    //调用流发送消息
    if err := cs.SendMsg(req); err != nil {
        return err
    }
    //解码收到的响应
    return cs.RecvMsg(reply)
}

关于grpc 客户端暂时先写这么多,太肝了,移到下下周写了,更新完http2 协议,再回来详细写客户端的逻辑。

告诉大家一个不为人知的知乎技巧:催更按钮和点赞按钮其实是一个

未完待续。。。。。。。

 

  • 7
    点赞
  • 11
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值