文章目录
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 的结构体里。

1752

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



