gRPC 入门程序的分析


gRPC Server 解析


gRPC Server 连接建立的过程

gRPC Server 端的 main 函数程序代码如下所示:

func main() {
    	lis, err := net.Listen("tcp", port)
    	if err != nil {
        		log.Fatalf("failed to listen: %v", err)
    	}
    	s := grpc.NewServer()
    	pb.RegisterGreeterServer(s, &server{})
    	if err := s.Serve(lis); err != nil {
        		log.Fatalf("failed to serve: %v", err)
    	}
}

从上面的程序代码可知, gRPC server 端的连接建立过程主要包括以下三步:

(1)创建 server;

(2)server 的注册;

(3)调用 Serve 监听端口并处理请求。


创建 server

server 的创建调用的 NewServer() 函数,其函数的源码如下所示:

func NewServer(opt ...ServerOption) *Server {
    	opts := defaultServerOptions
    	for _, o := range opt {
        		o.apply(&opts)
    	}
    	s := &Server{
        		lis:    make(map[net.Listener]bool),
        		opts:   opts,
        		conns:  make(map[transport.ServerTransport]bool),
        		m:      make(map[string]*service),
        		quit:   grpcsync.NewEvent(),
        		done:   grpcsync.NewEvent(),
        		czData: new(channelzData),
    	}
    	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 channelz.IsOn() {
        		s.channelzID = channelz.RegisterServer(&channelzServer{s}, "")
    	}
    	return s
}

该函数的核心是创建了一个 server 结构体,然后为该结构体的属性赋值,其结构体的定义如下:

// Server is a gRPC server to serve RPC requests.
type Server struct {
    	// serverOptions 是描述协议的各种参数选项,包括发送和接收的消息大小、buffer 大小等,跟 http Headers 类似
    	opts serverOptions
    	// 一个互斥锁
    	mu     sync.Mutex // guards following
    	// listener map
    	lis    map[net.Listener]bool
    	// connections map
   	 	conns  map[transport.ServerTransport]bool
    	// server 是否在处理请求的一个状态位
    	serve  bool
    	drain  bool
    	cv     *sync.Cond          // signaled when connections close for GracefulStop
    	// service map
    	m      map[string]*service // service name -> service info
    	events trace.EventLog
    	quit               *grpcsync.Event
    	done               *grpcsync.Event
    	channelzRemoveOnce sync.Once
    	serveWG            sync.WaitGroup // counts active Serve goroutines for GracefulStop
    	channelzID int64 // channelz unique identification number
    	czData     *channelzData
}

server 结构体里比较重要的是三个 map 表分别用来存放多个 listener 、connection 和 service,其它字段是为了实现协议描述或者并发控制的功能,其中 m 字段这个结构主要包含了 MethodDesc 和 StreamDesc 这两个 map ,其具体的定义如下:

type service struct {
    	server interface{} // the server for service methods
    	md     map[string]*MethodDesc
    	sd     map[string]*StreamDesc
    	mdata  interface{}
}

server 的注册

server 的注册调用了 RegisterGreeterServer() 方法,该方法是定义在 pb.go 文件中,具体的定义如下所示:

func RegisterGreeterServer(s *grpc.Server, srv GreeterServer) {
    	s.RegisterService(&_Greeter_serviceDesc, srv)
}

这个方法调用了 server 的 RegisterService() 方法,然后传入了一个 ServiceDesc 的数据结构,其结构的定义如下:

var _Greeter_serviceDesc = grpc.ServiceDesc{
    	ServiceName: "hello.Greeter",
    	HandlerType: (*GreeterServer)(nil),
    	Methods: []grpc.MethodDesc{
        		{
            			MethodName: "SayHello",
            			Handler:    _Greeter_SayHello_Handler,
        		},
       		 	{
           	 			MethodName: "SayHelloAgain",
            			Handler:    _Greeter_SayHelloAgain_Handler,
        		},
    	},
    	Streams:  []grpc.StreamDesc{},
    	Metadata: "hello.proto",
}

RegisterService() 方法主要是调用了 register() 方法,该方法则按照方法名为 key ,将方法注入到 server 的 service map 中,server 对不同 RPC 请求的处理是根据 service 中不同的 serviceName 从 service map 中取出不同的 handler 进行处理,具体过程参考如下的程序源码:

func (s *Server) RegisterService(sd *ServiceDesc, ss interface{}) {
    	ht := reflect.TypeOf(sd.HandlerType).Elem()
    	st := reflect.TypeOf(ss)
    	if !st.Implements(ht) {
        		grpclog.Fatalf("grpc: Server.RegisterService found the handler of type %v that does not satisfy %v", st, ht)
    	}
    	s.register(sd, ss)
}
func (s *Server) register(sd *ServiceDesc, ss interface{}) {
    	s.mu.Lock()
    	defer s.mu.Unlock()
    	s.printf("RegisterService(%q)", sd.ServiceName)
    	if s.serve {
        		grpclog.Fatalf("grpc: Server.RegisterService after Server.Serve for %q", sd.ServiceName)
    	}
    	if _, ok := s.m[sd.ServiceName]; ok {
        		grpclog.Fatalf("grpc: Server.RegisterService found duplicate service registration for %q", sd.ServiceName)
    	}
    	srv := &service{
        		server: ss,
        		md:     make(map[string]*MethodDesc),
        		sd:     make(map[string]*StreamDesc),
        		mdata:  sd.Metadata,
    	}
    	for i := range sd.Methods {
        		d := &sd.Methods[i]
        		srv.md[d.MethodName] = d
    	}
    	for i := range sd.Streams {
        		d := &sd.Streams[i]
        		srv.sd[d.StreamName] = d
    	}
    	s.m[sd.ServiceName] = srv
}

Serve 过程

在 C/S 模式下,客户端与服务端的通信基本是 server 通过死循环的方式在某一个端口实现监听,然后 client 对这个端口发起连接请求,握手成功后建立连接,然后 server 处理 client 发送过来的请求数据,根据请求类型和请求参数,调用不同的 handler 进行处理,回写响应数据。

故 server 端主要是了解其如何实现监听,如何为请求分配不同的 handler 和 回写响应数据。

从上面程序分析知 server 调用了 Serve() 方法来进行处理,关键部分的程序源码如下:

for {
        rawConn, err := lis.Accept()
        ......
        s.serveWG.Add(1)
        go func() {
            s.handleRawConn(rawConn)
            s.serveWG.Done()
        }()
}

从以上的源码部分实现了监听的过程,server 的监听是通过一个死循环调用了 lis.Accept() 来进行端口监听,新起协程调用了 handleRawConn() 这个方法,关键部分的程序源码如下:

func (s *Server) handleRawConn(rawConn net.Conn) {
    	...
    	conn, authInfo, err := s.useTransportAuthenticator(rawConn)
    	...
    	// Finish handshaking (HTTP2)
    	st := s.newHTTP2Transport(conn, authInfo)
    	if st == nil {
        		return
    	}
    	...
    	go func() {
        		s.serveStreams(st)
        		s.removeConn(st)
    	}()
}

可以看到在 handleRawConn() 方法里实现了 http 的 handshake ,gRPC 是基于 HTTP2 实现的,通过一个新的协程调用了 serveStreams() 方法,关键部分的程序源码如下:

func (s *Server) serveStreams(st transport.ServerTransport) {
    	defer st.Close()
    	var wg sync.WaitGroup
    	st.HandleStreams(func(stream *transport.Stream) {
        		wg.Add(1)
        		go func() {
            			defer wg.Done()
            			s.handleStream(st, stream, s.traceInfo(st, stream))
        		}()
    			}, func(ctx context.Context, method string) context.Context {
        				if !EnableTracing {
            			return ctx
        		}
        	tr := trace.New("grpc.Recv."+methodFamily(method), method)
        	return trace.NewContext(ctx, tr)
    	})
    	wg.Wait()
}

它主要调用了 handleStream() 方法,该方法关键部分的程序源码如下:

func (s *Server) handleStream(t transport.ServerTransport, stream *transport.Stream, trInfo *traceInfo) {
    	sm := stream.Method()
    	...
    	service := sm[:pos]
    	method := sm[pos+1:]
    	srv, knownService := s.m[service]
    	if knownService {
        		if md, ok := srv.md[method]; ok {
            			s.processUnaryRPC(t, stream, srv, md, trInfo)
            			return
        		}
        		if sd, ok := srv.sd[method]; ok {
            			s.processStreamingRPC(t, stream, srv, sd, trInfo)
            			return
        		}
    	}
    	...
}

其中,关键部分的代码如下所示:

srv, knownService := s.m[service]

根据 serviceName 从 server 中的 service map (即 m 这个字段)里面取出 handler 进行处理, hello world 这个 demo 的请求不涉及到 stream ,所以直接取出 handler ,然后传给 processUnaryRPC() 这个方法进行处理,该方法关键部分的程序源码如下:

if md, ok := srv.md[method]; ok {
    	s.processUnaryRPC(t, stream, srv, md, trInfo)
    	return
}

其中, processUnaryRpc() 方法关键部分的程序源码如下:

func (s *Server) processUnaryRPC(t transport.ServerTransport, stream *transport.Stream, srv *service, md *MethodDesc, trInfo *traceInfo) (err error) {
    	...
    	sh := s.opts.statsHandler
    	if sh != nil {
        		beginTime := time.Now()
        		begin := &stats.Begin{
            			BeginTime: beginTime,
        		}
        		sh.HandleRPC(stream.Context(), begin)
        		defer func() {
            			end := &stats.End{
                		BeginTime: beginTime,
                		EndTime:   time.Now(),
            	}
            	if err != nil && err != io.EOF {
                		end.Error = toRPCErr(err)
            	}
            	sh.HandleRPC(stream.Context(), end)
        	}()
    	}
    	...
    	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
        		}
        		if s, ok := status.FromError(err); ok {
            			if e := t.WriteStatus(stream, s); e != nil {
                				grpclog.Warningf("grpc: Server.processUnaryRPC failed to write status: %v", e)
            			}
        		} else {
            			switch st := err.(type) {
            					case transport.ConnectionError:
                						// Nothing to do here.
            					default:
                						panic(fmt.Sprintf("grpc: Unexpected error (%T) from sendResponse: %v", st, st))
            			}
        		}
        	if binlog != nil {
            		h, _ := stream.Header()
            		binlog.Log(&binarylog.ServerHeader{
                			Header: h,
            		})
            		binlog.Log(&binarylog.ServerTrailer{
                			Trailer: stream.Trailer(),
                			Err:     appErr,
            		})
        	}
        	return err
    	}
    	...
}


从该程序源码可以发现 handler 对 RPC 的处理,处理的关键部分源码如下:

```go
sh := s.opts.statsHandler
sh.HandleRPC(stream.Context(), begin)
sh.HandleRPC(stream.Context(), end)

同时也看到了 response 的回写,具体的程序代码如下:

s.sendResponse(t, stream, reply, cp, opts, comp)

至此,从以上的程序源码可以看见 server 端对整个请求和监听、handler 处理和 response 回写的过程。


gRPC Client 解析


gRPC Client 连接建立的过程

gRPC Client 端的 main 函数程序代码如下所示:

func main() {
    	// Set up a connection to the server.
    	conn, err := grpc.Dial(address, 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.
    	name := defaultName
    	if len(os.Args) > 1 {
        		name = os.Args[1]
    	}
    	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.Message)
}

从上面的程序代码可知,gRPC Client 端的连接建立主要分为以下的三步:

1)创建一个客户端连接 conn ;

2)通过一个 conn 创建一个客户端;

3)发起 RPC 调用。


创建一个客户端连接 conn

通过 Dial() 方法创建一个 conn ,再调用了 DialContext() 方法,具体的程序代码如下:

func Dial(target string, opts ...DialOption) (*ClientConn, error) {
    	return DialContext(context.Background(), target, opts...)
}

DialContext() 方法先实例化了一个 ClientConn 的结构体,然后主要为 ClientConn 结构体中 dopts 的各个属性进行初始化赋值,具体的程序代码如下:

cc := &ClientConn{
        target:            target,
        csMgr:             &connectivityStateManager{},
        conns:             make(map[*addrConn]struct{}),
        dopts:             defaultDialOptions(),
        blockingpicker:    newPickerWrapper(),
        czData:            new(channelzData),
        firstResolveEvent: grpcsync.NewEvent(),
}

其中,ClientConn 的结构的定义如下:

type ClientConn struct {
    	ctx    context.Context
    	cancel context.CancelFunc
    	target       string
    	parsedTarget resolver.Target
    	authority    string
    	dopts        dialOptions
    	csMgr        *connectivityStateManager
    	balancerBuildOpts balancer.BuildOptions
    	blockingpicker    *pickerWrapper
    	mu              sync.RWMutex
    	resolverWrapper *ccResolverWrapper
    	sc              *ServiceConfig
    	conns           map[*addrConn]struct{}
    	// Keepalive parameter can be updated if a GoAway is received.
    	mkp             keepalive.ClientParameters
    	curBalancerName string
    	balancerWrapper *ccBalancerWrapper
    	retryThrottler  atomic.Value
    	firstResolveEvent *grpcsync.Event
   	 	channelzID int64 // channelz unique identification number
    	czData     *channelzData
}

从上面的程序代码可知,dialOptions 其实是对客户端属性的一些设置(包括压缩解压缩、是否需要认证、超时时间、是否重试等信息)具体初始化了以下的属性:

connectivityStateManager
type connectivityStateManager struct {
    	mu         sync.Mutex
    	state      connectivity.State
    	notifyChan chan struct{}
    	channelzID int64
}

连接的状态管理器,每个连接具有 “IDLE”、“CONNECTING”、“READY”、“TRANSIENT_FAILURE”、“SHUTDOW N”、“Invalid-State” 这几种状态。

pickerWrapper 是对 balancer.Picker 的一层封装,balancer.Picker 其实是一个负载均衡器,它里面只有一个 Pick() 方法,它返回一个 SubConn 连接,pickerWrapper 和 Picker 接口的定义如下所示:

// pickerWrapper
type pickerWrapper struct {
    	mu         sync.Mutex
    	done       bool
    	blockingCh chan struct{}
    	picker     balancer.Picker
    	// The latest connection happened.
    	connErrMu sync.Mutex
    	connErr   error
}
type Picker interface {
    	Pick(ctx context.Context, opts PickOptions) (conn SubConn, done func(DoneInfo), err error)
}

在分布式环境下,可能会存在多个 client 和多个 server ,client 端在发起一个 RPC 调用之前,需要通过 balancer 去寻找 server 的 address ,balancer 的 Picker 类返回一个 SubConn ,SubConn 里包含了多个 server 的 address ,若返回的 SubConn 是 “READY” 状态,gRPC 会发送 RPC 请求,否则则会阻塞,等待 UpdateBalancerState() 方法更新连接的状态并且通过 picker 获取一个新的 SubConn 连接。

channelz 主要用来监测 server 和 channel 的状态,具体实现 参考 ,其初始化的程序代码如下:

if channelz.IsOn() {
        if cc.dopts.channelzParentID != 0 {
            	cc.channelzID = channelz.RegisterChannel(&channelzChannel{cc}, cc.dopts.channelzParentID, target)
            	channelz.AddTraceEvent(cc.channelzID, &channelz.TraceEventDesc{
                		Desc:     "Channel Created",
                		Severity: channelz.CtINFO,
                		Parent: &channelz.TraceEventDesc{
                    			Desc:     fmt.Sprintf("Nested Channel(id:%d) created", cc.channelzID),
                    			Severity: channelz.CtINFO,
                		},
            	})
        } else {
            	cc.channelzID = channelz.RegisterChannel(&channelzChannel{cc}, 0, target)
            	channelz.AddTraceEvent(cc.channelzID, &channelz.TraceEventDesc{
                		Desc:     "Channel Created",
                		Severity: channelz.CtINFO,
            	})
        }
        cc.csMgr.channelzID = cc.channelzID
}

Authentication 是对认证信息的初始化校验,具体内容 参考 ,其关键的程序代码如下:

if !cc.dopts.insecure {
        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
        }
    	} else {
        		if cc.dopts.copts.TransportCredentials != nil || cc.dopts.copts.CredsBundle != nil {
            			return nil, errCredentialsConflict
        		}
        		for _, cd := range cc.dopts.copts.PerRPCCredentials {
            			if cd.RequireTransportSecurity() {
                				return nil, errTransportCredentialsMissing
            			}
        		}
		}
}

Dialer 是发起 RPC 请求的调用器,dialer 中实现了对 RPC 请求调用的具体细节,dialer 中包括了连接建立、地址解析、服务发现、长连接等等具体策略的实现,关键的程序源码如下:

if cc.dopts.copts.Dialer == nil {
        cc.dopts.copts.Dialer = newProxyDialer(
            	func(ctx context.Context, addr string) (net.Conn, error) {
                		network, addr := parseDialTarget(addr)
                		return (&net.Dialer{}).DialContext(ctx, network, addr)
            	},
        	)
}

以上的代码部分只是简单进行了地址的规则解析,DialContext() 方法中有如下一行代码:

addrs, err := d.resolver().resolveAddrList(resolveCtx, "dial", network, address, d.LocalAddr)

可以看到通过 dialer 的 resolver 来进行服务发现,通过 DialContext() 方法可以看出,这里的 dial 有两种请求方式(一种是 dialParallel , 另一种是 dialSerial)。dialParallel 发出两个完全相同的请求,采用第一个返回的结果,抛弃掉第二个请求;dialSerial 发出一串(多个)请求,然后采取第一个返回的请求结果( 成功或者失败)。

scChan 是 dialOptions 中的一个属性,是一个 ServiceConfig 类型的一个 channel,其具体的定义如下:

scChan  <-chan ServiceConfig

ServiceConfig 是服务提供方约定的一些参数, client 提供给 server 一个可以通过 channel 来修改这些参数的入口, client 的某些属性是可以被 server 修改,关键的程序源码如下所示:

if cc.dopts.scChan != nil {
        // Try to get an initial service config.
        select {
        		case sc, ok := <-cc.dopts.scChan:
            			if ok {
                				cc.sc = &sc
                				scSet = true
            			}
        		default:
        }
}

通过一个 conn 创建一个客户端

通过一个 conn 创建客户端的代码如下:

c := pb.NewGreeterClient(conn)

这一步其实是 pb 文件中生成的代码,创建一个 greeterClient 的客户端,具体的程序代码如下:

type greeterClient struct {
        cc *grpc.ClientConn
}
func NewGreeterClient(cc *grpc.ClientConn) GreeterClient {
     	return &greeterClient{cc}
}

发起 RPC 调用

在创建 Dialer 时已经将请求的 target 解析成了 address ,这一步是向指定 address 发起 RPC 请求,具体的程序源码如下:

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
}

SayHello() 方法是通过调用 Invoke 的方法去发起 RPC 调用, Invoke() 方法的程序代码如下所示:

func (cc *ClientConn) Invoke(ctx context.Context, method string, args, reply interface{}, opts ...CallOption) error {
    	// allow interceptor to see all applicable call options, which means those
    	// configured as defaults from dial option as well as per-call options
    	opts = combine(cc.dopts.callOptions, opts)
    	if cc.dopts.unaryInt != nil {
        		return cc.dopts.unaryInt(ctx, method, args, reply, cc, invoke, opts...)
    	}
    	return invoke(ctx, method, args, reply, cc, opts...)
}

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)
}

在 invoke() 方法里有 sendMsg 和 recvMsg 接口,这两个接口在 clientStream 中被实现了。clientStream 中定义的 sendMsg 关键代码如下:

func (cs *clientStream) SendMsg(m interface{}) (err error) {
    	...
    	// load hdr, payload, data
    	hdr, payload, data, err := prepareMsg(m, cs.codec, cs.cp, cs.comp)
    	if err != nil {
        		return err
    	}
    	...
    	op := func(a *csAttempt) error {
        		err := a.sendMsg(m, hdr, payload, data)
        		// nil out the message and uncomp when replaying; they are only needed for
        		// stats which is disabled for subsequent attempts.
        	m, data = nil, nil
        	return err
    	}
}

从以上程序代码中可知,准备数据后调用 csAttempt 这个结构体中的 sendMsg() 方法,该方法的程序代码如下:

func (a *csAttempt) sendMsg(m interface{}, hdr, payld, data []byte) error {
    	cs := a.cs
    	if a.trInfo != nil {
        		a.mu.Lock()
        		if a.trInfo.tr != nil {
            			a.trInfo.tr.LazyLog(&payload{sent: true, msg: m}, true)
        		}
        		a.mu.Unlock()
    	}
    	if err := a.t.Write(a.s, hdr, payld, &transport.Options{Last: !cs.desc.ClientStreams}); err != nil {
        		if !cs.desc.ClientStreams {
           	 			// For non-client-streaming RPCs, we return nil instead of EOF on error
            			// because the generated code requires it.  finish is not called; RecvMsg()
            			// will call it with the stream's status independently.
            			return nil
        		}
        		return io.EOF
    	}
    	if a.statsHandler != nil {
        		a.statsHandler.HandleRPC(cs.ctx, outPayload(true, m, data, payld, time.Now()))
    	}
    	if channelz.IsOn() {
       		 	a.t.IncrMsgSent()
    	}
    	return nil
}

从以上的程序代码可知,最终通过 a.t.Write 发出的数据写操作,a.t 是一个 ClientTransport 类型,所以最终是通过 ClientTransport 这个结构体的 Write() 方法发送数据。发送数据是通过 ClientTransport 的 Write() 方法,接收数据是调用了 Read() 方法,具体的程序代码如下:

func (a *csAttempt) recvMsg(m interface{}, payInfo *payloadInfo) (err error) {
    	...
    	err = recv(a.p, cs.codec, a.s, a.dc, m, *cs.callInfo.maxReceiveMessageSize, payInfo, a.decomp)
    	...
    	if a.statsHandler != nil {
        		a.statsHandler.HandleRPC(cs.ctx, &stats.InPayload{
            			Client:   true,
            			RecvTime: time.Now(),
            			Payload:  m,
            			// TODO truncate large payload.
            			Data:       payInfo.uncompressedBytes,
            			WireLength: payInfo.wireLength,
            			Length:     len(payInfo.uncompressedBytes),
        		})
    	}
    	...
}
func recv(p *parser, c baseCodec, s *transport.Stream, dc Decompressor, m interface{}, maxReceiveMessageSize int, payInfo *payloadInfo, compressor encoding.Compressor) error {
    		d, err := recvAndDecompress(p, s, dc, maxReceiveMessageSize, payInfo, compressor)
    	...
}

recvAndDecompress() 方法调用了 recvMsg ,具体的程序代码如下:

func recvAndDecompress(p *parser, s *transport.Stream, dc Decompressor, maxReceiveMessageSize int, payInfo *payloadInfo, compressor encoding.Compressor) ([]byte, error) {
    	pf, d, err := p.recvMsg(maxReceiveMessageSize)
    	...
}

func (p *parser) recvMsg(maxReceiveMessageSize int) (pf payloadFormat, msg []byte, err error) {
    	if _, err := p.r.Read(p.header[:]); err != nil {
        		return 0, nil, err
    	}
    	...
}

从以上的程序代码可知,最终调用了 p.r.Read() 方法,p.r 是一个 io.Reader 类型。接收数据是调用 io.Reader ,在 Serve() 方法 的 handleRawConn() 方法中,newHttp2Transport 创建了一个 Http2Transport ,然后通过 serveStreams() 方法将这个 Http2Transport 层层透传下去,具体的程序代码如下:

// Finish handshaking (HTTP2)
st := s.newHTTP2Transport(conn, authInfo)
if st == nil {
    	return
}
rawConn.SetDeadline(time.Time{})
if !s.addConn(st) {
    	return
}
go func() {
    	s.serveStreams(st)
    	s.removeConn(st)
}()

http2Client 的 Write() 方法的程序代码如下::

func (t *http2Server) Write(s *Stream, hdr []byte, data []byte, opts *Options) error {
    	...
    	hdr = append(hdr, data[:emptyLen]...)
    	data = data[emptyLen:]
    	df := &dataFrame{
        		streamID:    s.id,
        		h:           hdr,
        		d:           data,
        		onEachWrite: t.setResetPingStrikes,
    	}
    	if err := s.wq.get(int32(len(hdr) + len(data))); err != nil {
        		select {
        				case <-t.ctx.Done():
            			return ErrConnClosing
        				default:
        		}
        		return ContextErr(s.ctx.Err())
    	}
    	return t.controlBuf.put(df)
}

从以上程序代码可知,最终是把 data 放到了一个 controlBuf 的结构体里。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

물の韜

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值