gRPC-Go源码解读三 服务端处理流程分析
相较于Client端的复杂处理流程,Server端相对来说简单了很多,核心就是创建个TCP套接字并监听,收到客户端连接请求则起个go协程处理,子协程根据请求中的服务名和方法名调用对应的服务方法处理,处理完成之后则返回响应。整个过程不涉及服务发现和负载均衡,因此代码相对简洁。
下面以gRPC-Go 1.54.0-dev版本中 examples/helloworld为例,先看看pb的服务定义:
package helloworld;// The greeting service definition.
service Greeter {// Sends a greetingrpc SayHello (HelloRequest) returns (HelloReply) {}
}// The request message containing the user's name.
message HelloRequest {string name = 1;
}// The response message containing the greetings
message HelloReply {string message = 1;
}
该文件定义了Greeter服务,只包含一个Unary方法SayHello,请求响应参数都只包含一个字符串。
下面看下服务端的代码部分:
var (port = flag.Int("port", 50051, "The server port")
)// server 用来实现helloworld.GreeterServer,默认继承了UnimplementedGreeterServer
type server struct {pb.UnimplementedGreeterServer
}// SayHello 具体实现,就是将请求中的字符串加个Hello 返回回去
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()// 创建TCP套接字lis, err := net.Listen("tcp", fmt.Sprintf(":%d", *port))if err != nil {log.Fatalf("failed to listen: %v", err)}// 创建grpcServers := grpc.NewServer()// 将server注册到grpcServer里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)}
}
主函数代码不多,就是创建套接字和grpcServer实例,然后注册实际的业务实现,之后就可以接收客户端请求开始服务了。
下面以流程图的方式展示整个调用链路过程,相对于粘贴代码,流程图看起来更直观、简洁。

如图所示,红色部分表示主函数,绿色部分表示处理Client链接的子协程。Server每收到一个TCP连接请求则分配一个协程处理。子协程主要工作就是处理HTTP2 Frame,当收到Header Frame后则根据Header头中携带的path域找到对应Client请求的服务名和方法名,然后从grpcServer中查找程序启动时注册的服务,找到则处理,找不到则返回一个错误消息是"unknown service" 或者"unknown method" Header Frame,关于传输层其它部分的处理(如http2初始化、流量控制、数据读写等)可以参考前面几篇博文。
下面看下请求的抓包例子:

图2显示了Client的请求头,可以看到path域携带了所请求的服务名和方法名。当子协程据此查找注册的服务方法来处理。
gRPC Server 和Client在传输层复用了大部分代码,比如loopy,看了之前对Client请求的分析,再来看本篇就会发现简单许多。