相较于Client端的复杂处理流程,Server端相对来说简单了很多,核心就是创建个TCP套接字并监听,收到客户端连接请求则起个go协程处理,子协程根据请求中的服务名和方法名调用对应的服务方法处理,处理完成之后则返回响应。整个过程不涉及服务发现和负载均衡,因此代码相对简洁。
下面以gRPC-Go 1.54.0-dev版本中 examples/helloworld为例,先看看pb的服务定义:
package helloworld;
// The greeting service definition.
service Greeter {
// Sends a greeting
rpc 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)
}
// 创建grpcServer
s := 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请求的分析,再来看本篇就会发现简单许多。